stasher::client
->get_request
(): asynchronous version of get
()#include <iostream> #include <stasher/client.H> #include <x/destroycallbackflag.H> class getCallbackObj : public x::destroyCallbackObj { public: std::string name; stasher::getrequest res; getCallbackObj(const std::string &nameArg, const stasher::getrequest &resArg) : name(nameArg), res(resArg) { } ~getCallbackObj() { } void destroyed() noexcept override { stasher::contents contents=res->getmsg()->objects; if (!contents->succeeded) { std::cerr << "Error: " << x::tostring(contents->errmsg) << std::endl; return; } auto iter=contents->find(name); if (iter == contents->end()) { std::cout << name << " removed" << std::endl; return; } std::string line; std::getline(*iter->second.fd->getistream(), line); std::cout << name << ": " << line << std::endl; } }; class mySubscriberObj : public stasher::client::base::subscriberObj { public: stasher::client client; mySubscriberObj(const stasher::client &clientArg) : client(clientArg) { } ~mySubscriberObj() { } void updated(const std::string &objname, const std::string &suffix) override { std::string name=objname+suffix; stasher::client::base::getreq req =stasher::client::base::getreq::create(); req->openobjects=true; req->objects.insert(name); std::pair<stasher::getrequest, stasher::client::base::request> res=client->get_request(req); res.second->mcguffin() ->addOnDestroy(x::ref<getCallbackObj> ::create(name, res.first)); } }; void monitor(int argc, char **argv) { if (argc < 2) throw EXCEPTION("Usage: monitor {object}+"); stasher::client client=stasher::client::base::connect(); x::destroyCallbackFlag::base::guard subscriber_guard; auto subscriber=x::ref<mySubscriberObj>::create(client); subscriber_guard(subscriber); // Makes sure subscriber gets destroyed before client does, when this // function terminates std::list<x::ref<x::obj> > mcguffins; for (int i=1; i<argc; ++i) { stasher::subscriberesults res=client->subscribe(argv[i], subscriber); if (res->status != stasher::req_processed_stat) throw EXCEPTION(x::tostring(res->status)); mcguffins.push_back(res->mcguffin); auto cancel_mcguffin=res->cancel_mcguffin; // NOT USED std::cout << "Subscribed to " << argv[i] << std::endl; } std::cout << "Monitoring these objects, press ENTER to stop" << std::endl; std::string dummy; std::getline(std::cin, dummy); } int main(int argc, char **argv) { try { monitor(argc, argv); } catch (const x::exception &e) { std::cerr << e << std::endl; exit(1); } return 0; }
This is a modified version of the example in
the section called “
(): object subscriptions” that demonstrates the use of
stasher::client
->subscribeget_request
().
Like subscribe.C
a subscription gets opened for
each object or hierarchy given as an argument to this program, but the
main execution thread then stops and waits for Enter.
When a subscribed object or hierarchy changes and
updated
() gets invoked, it calls
get_request
() (instead of
get
() invoked from the main execution thread,
in the subscribe.C
version).
updated
() attaches a destructor callback
to the request's mcguffin. When the request gets proceed and the destructor
callback gets invoked, the result of the request gets retrieved.
Like subscribe.C
, the object's file descriptor gets
opened and the first line in the object gets printed.
Start this example, subscribing to one or more objects or hierarchies,
then use simpleput.C
or stasher to
create, update, and remove the objects. Each time a subscribed-to object
gets created or updated, this example shows the first line in the object.
As discussed in the section called “What asynchronous C++ API methods can and cannot do”, callbacks and
callback objects, like mySubscriberObj
create
a circular object reference if they have their own
stasher::client
.
Technically this is not completely true, since the
reference to the callback is stored on client connection thread's stack, and not the
stasher::client
-referenced object, and if the
execution thread stops (which it can, if the connection with the
object repository server breaks, or if
()
gets invoked), that reference goes out of scope and gets destroyed.
But this difference is negligible, and this is not really the
recommended approach.
stasher::client
->disconnect
The above example shows the only proper way for callbacks to be able to
have their own stasher::client
: a destructor guard
that makes sure that the callback goes out of scope and gets destroyed
before the stasher::client
.
This order of declaration is important, because this requires that
all the related objects go out of scope and get destroyed in the right
order: the reference to the subscriber callback object first, then
the subscription mcguffins.
This results in the subscription mcguffins going out of scope first, followed by the subscriber callback object reference. This does not result in the immediate destruction of the callback object. Until the client connection thread responds to the destruction of the subscription mcguffins, an internal reference to the callback object, from the client connection thread remains. The destruction of the subscription mcguffins results only in a message getting sent to the client connection thread, and the subscriptions do not get cancelled until the client connection thread processes those messages. This is a quick, but not an instantaneous process.
Without guarding subscriber object's destructor, the
client
would get destroyed first, but because the
subscriber callback object still exists, for the moment, its
stasher::client
becomes the last handle for the
client connection, and when the client connection thread processes the unsubscription
message, all references to the subscriber object go out of scope, the
object gets destroyed, and the last stasher::client
reference's destructor deadlocks waiting for the client connection thread to stop, because
it gets actually invoked from the same thread.
In this example, when the destructor guard gets destroyed its own destrucor waits and makes sure that the subscriber object is fully destroyed, together with its client connection handle. Then it returns, and the client connection handle that gets destroyed will be the last reference to the client connection, and get destroyed properly.