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() noexcept {
	}

	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() noexcept
	{
	}

	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 “stasher::client->subscribe(): object subscriptions” that demonstrates the use of get_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.

Note

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 stasher::client->disconnect() gets invoked), that reference goes out of scope and gets destroyed. But this difference is negligible, and this is not really the recommended approach.

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.