stasher::manager->manage_object(): automatically retrieve an object any time it changes

#include <stasher/client.H>
#include <stasher/manager.H>
#include <stasher/managedhierarchymonitor.H>

#include <x/destroycallbackflag.H>
#include <x/fditer.H>

#include <iterator>
#include <iostream>

// An example of a stasher::managedobjectObj implementation.

class mymanagedobjectObj : public stasher::managedobjectObj {

public:
	mymanagedobjectObj()
	{
	}

	~mymanagedobjectObj() noexcept
	{
	}

	// Invoked when the connection gets established, or breaks.

	void connection_update(stasher::req_stat_t status) override
	{
		std::cout << ("Connection update: " + x::tostring(status)
			      + "\n") << std::flush;
	}

	// Invoked when the object gets created or updated.

	// If the object exists at the time that the subscription got opened,
	// or after reestablishing a connection with the server, this callback
	// gives the object's uuid and its contents.

	void updated(const std::string &objname,
		     const x::uuid &uuid,
		     const x::fd &contents) override
	{
		std::cout << objname << " (" << x::tostring(uuid) << "):"
			  << std::endl;

		std::copy(x::fdinputiter(contents),
			  x::fdinputiter(),
			  std::ostreambuf_iterator<char>(std::cout));
		std::cout << std::endl;
	}

	// Invoked when the object gets removed.

	// This callback gets invoked if the object does not exist at the time
	// that the subscription got opened, or after reestablishing a
	// connection with the server.

	void removed(const std::string &objname) override
	{
		std::cout << objname << ": removed"
			  << std::endl;
	}
};

void monitor(const std::list<std::string> &objects)
{
	x::destroyCallbackFlag::base::guard guard;

	auto client=stasher::client::base::connect();

	guard(client);
	// When we return from this func, make sure this goes away.

	auto manager=stasher::manager::create(L"", "10 seconds");

	std::cout << "Starting subscriptions, press ENTER to stop"
		  << std::endl;

	auto monitor=x::ref<mymanagedobjectObj>::create();
	std::list<x::ref<x::obj> > mcguffins;

	for (auto &name:objects)
	{
		mcguffins.push_back(manager->
				    manage_object(client, name, monitor));
	}

	std::string dummy;

	std::getline(std::cin, dummy);
}

int main(int argc, char **argv)
{
	try {
		std::list<std::string> args(argv+1, argv+argc);

		monitor(args);
	} catch (const x::exception &e)
	{
		std::cerr << e << std::endl;
		exit(1);
	}

	return 0;
}

A managed object goes the next step after a managed subscriber by automatically retrieving the current value of the object when the subscription gets opened and any time the subscribed to object changes. This is for individual objects only, see the section called “stasher::manager->manage_hierarchymonitor(): maintaining an internal snapshot of an object hierarchy” for a comparable interface for subscribed hierarchies.

Define a subclass of stasher::managedobjectObj and implement its methods, as shown in this example. stasher::manager->manage_object() takes three parameters: a client connection, the name of an object and an x::ref to a subclass of stasher::managedobjectObj. As shown in this example, the same instance can be installed for more than one managed object.

Note

manage_object() does not retrieve the object's current value, before returning. It sets up a subscription, and sets the gears in motion. The client connection thread is on the job, and the initial callback to updated() or removed() is pending, and will follow soon (unless the connection with the server is broken, as described below, in which case stay tuned).

To see how this works, run managedobject.C and give one or more object names on the command line. The main execution thread sets up the subscriptions, and waits for Enter. All the output comes from the callbacks, as documented in the example. Use simpleput.C or stasher to create, update, and remove the managed objects:

$ ./managedobject fruits/apple fruits/grape
Starting subscriptions, press ENTER to stop
Connection update: Transaction/request processed
Connection update: Transaction/request processed
fruits/apple (IdAzcA5Ga9YjVW00dTtmJm00000eyWG00318n4AS):
delicious
fruits/grape: removed
fruits/apple (JdAzcA5Ga9YjVW00dTtmJm00000eyWG00318n4AS):
very delicious
fruits/grape (KdAzcA5Ga9YjVW00dTtmJm00000eyWG00318n4AS):
juicy
Connection update: Connection to the server failed
Connection update: Connection to the server failed
Connection update: Transaction/request processed
Connection update: Transaction/request processed
fruits/grape (KdAzcA5Ga9YjVW00dTtmJm00000eyWG00318n4AS):
juicy
fruits/apple (JdAzcA5Ga9YjVW00dTtmJm00000eyWG00318n4AS):
very delicious
fruits/grape: removed

stasher::manager->manage_object() returns a mcguffin that represents the object it's managing. There is no explicit un-manage method. The manager stops managing the object when its mcguffin goes out of scope and gets destroyed.

The connection_update() callback reports the status of the subscription. connection_update() gets invoked shortly after manage_object() returns (if not right before it), and any time the status of the connection with the server changes. stasher::req_processed_stat reports a success in opening the managed object's subscription. A connection_update() of stasher::req_disconnected_stat indicates that the connection with the stasher server was lost. In this example, the connection gets lost, and then reestablished by the manager shortly thereafter.

The manager keeps trying to reconnect, periodically, reporting the result to connection_update(). Other error codes, besides stasher::req_processed_stat and stasher::req_disconnected_stat, indicates a failure to establish a subscription for the managed object, for some other reason.

A connection_update() of stasher::req_processed_stat gets shortly followed by either an updated() or a removed() callback. In the example above, the two messages after the initial pair of Connection update: Transaction/request processed are callbacks reporting the initial value of each object at the time the managed subscription gets established: either the existing object's uuid, and its contents, or that the managed object does not exist (but the subscription is still active, waiting for someone to create the object).

This is the case after the initial stasher::req_processed_stat, after a managed object subscription gets opened, and whenever the manager reconnects to the server after recovering from a broken connection. After the initial updated() or removed(), additional calls to updated() and removed() report whenever the object changes, accordingly. In the example above, there are two updates, one for fruits/apple and one for fruits/grape before the connection with the server gets broken.

Note

It's possible to get two removed() in a row, or two updated() in a row with the same uuid and contents. This occurs in two marginal situations:

  1. Another application updates the managed object at the same time that the managed object subscription gets established, or when the manager reconnects to the server.

  2. The object gets updated quickly two or more times. The first updated value cannot be retrieved, quickly enough, before the second update gets processed. updated() and/or removed() reports the most recent object contents/status, each time.

The application should keep track of the object's state as it knows, and compare it to the latest reported state by updated() or removed(), and proceed accordingly.

The way to look at updated() and removed() is that they report the latest known value or state of the object, every time; and any change to the object triggers an update.

The above example shows a typical output of managing two objects: fruits/apple, which exists already at the time that managedobject.C starts, and fruits/grape which does not.

In this example, the subscriptions for both objects get established, and each individually managed object's connection_update() gets called with stasher::req_processed_stat (reporting Transaction/request processed). This is followed by an updated() call for fruits/apple, and removed() for th fruits/grape that does not exist.

Additional removed() and updated() callbacks follow, as these object get updated periodically. Some time later, the connection with the server breaks, for some reason (reported for each managed object individually); the manager tries again later and reestablished the connection, then gets the current value of each managed object (since it might've changed since the connection was lost).

managedobject.C assumes that these objects contain plain text. updated() reads the object's open file descriptor, and copies them to output.

All managed objects' callbacks have certain limitations, see the section called “What asynchronous C++ API methods can and cannot do” for more information.

Note

Each managed object counts towards the client's connection limits.