stasher::manager->manage_hierarchymonitor(): maintaining an internal snapshot of an object hierarchy

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

#include <x/destroycallbackflag.H>

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

class mymonitorObj : public stasher::managedhierarchymonitorObj {

public:

	std::string hierarchy;

	mymonitorObj(const std::string &hierarchyArg) : hierarchy(hierarchyArg)
	{
		if (hierarchy.size() == 0)
			hierarchy="[root]";
	}

	~mymonitorObj() 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;
	}

	// Initial contents of the hierarchy follow, until the next enumerated
	// call.

	void begin() override
	{
		std::cout << ("=== Initial contents of "
			      + hierarchy + "\n") << std::flush;
	}

	// End of the initial contents of the hierarchy
	void enumerated()
	{
		std::cout << ("=== End of "
			      + hierarchy + "\n") << std::flush;
	}

	// An object in the hierarchy has been added or updated.
	// After enumerated(), this gets called whenever an object in the
	// hierarchy gets changed.

	void updated(const std::string &objname,
		     const x::uuid &objuuid)
	{
		std::cout << (objname + " (" + x::tostring(objuuid)
			      + ")\n") << std::flush;
	}

	// An object in the hierarchy has been removed. Typically follows
	// enumerated(), as needed.
	//
	// It's possible that:
	//
	// - removed() also gets called after begin() but before
	// enumerated(), referring to an object that's already been reported
	// as updated(), or for an object that has not been reported as
	// updated().
	//
	// - During enumeration, updated() gets called more than once with the
	// same objname and uuid.
	//
	// This can happen when a transaction updates the hierarchy while it
	// is getting enumerated.
	//
	// Applications that maintain an internal snapshot of the hierarchy
	// can implement begin() by clearing the internal snapshot, then
	// applying updated/removed callback, as they come in; then when
	// enumerated() gets received, the internal snapshot now matches what's
	// in the repository. Applications should not consider a removed()
	// for an object they do not have a previous update() to be an error
	// condition, neither an update() with the object name and uuid
	// unchanged.

	void removed(const std::string &objname)
	{
		std::cout << (objname + " (removed)\n") << std::flush;
	}
};

void monitor(const std::list<std::string> &hierarchies)
{
	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;

	std::list<x::ref<mymonitorObj> > monitors;
	std::list<x::ref<x::obj> > mcguffins;

	for (auto &hierarchy:hierarchies)
	{
		auto monitor=x::ref<mymonitorObj>::create(hierarchy);

		monitors.push_back(monitor);
		mcguffins.push_back(manager->
				    manage_hierarchymonitor(client,
							    hierarchy,
							    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);

		if (args.empty())
			args.push_back("");

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

	return 0;
}

This is the equivalent of a stasher::manager->manage_object() for hierarchies. This example differs from subscribe.C, that uses subscribe() by what information its callback gets. For a subscribed hierarchy, subscribe()'s updated() callback reports that something about a particular object in the hierarchy has changed: it was either created, updated, or removed, and it's up to the application to try to retrieve it, and report the results. This is what, essentially, a manager does for a managed hierarchy, then updated() for a new or an updated object's uuid, or removed() for a removed object.

Define a subclass of stasher::managedhierarchymonitorObj and implement its methods, as shown in this example. stasher::manager->manage_hierarchymonitor() takes three parameters: a client connection, the name of a hierarchy (an empty string for the top level, root hierarchy, or a hierarchy name without the trailing /), and an x::ref to a subclass of stasher::managedhierarchymonitorObj. As shown in this example, the same instance can be installed for more than one managed object.

stasher::manager->manage_hierarchymonitor() returns a mcguffin that represents the managed hierarchy. There is no explicit un-manage method. The manager stops managing the hierarchy 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_hierarchymonitor() 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 the following example, the connection gets lost, and then reestablished by the manager shortly thereafter.

Run hierarchymonitor.C without any arguments to monitor the top level, root hierarchy. Alternatively, names of one or more hierarchies are given as parameters. The main execution thread sets up the subscriptions, and waits for Enter. All the output comes from the callbacks, as documented in the example.

Start this example, subscribing to one or more objects or hierarchies, then use simpleput.C or stasher to create, update, and remove the objects in the monitored hierarchies. When monitoring more than one hierarchy, their output can get intermixed, naturally:

$ ./hierarchymonitor "" "fruits"
Starting subscriptions, press ENTER to stop
Connection update: Transaction/request processed
=== Initial contents of [root]
Connection update: Transaction/request processed
=== Initial contents of fruits
=== End of [root]
fruits/apple (zzS9QJ2JoDfj9000h8FmJm0000126mO00318n4AS)
=== End of fruits
fruits/banana (_DS9QJ2JoDfj9000h8FmJm0000126mO00318n4AS)
fruits/apple (0TW9QJ2JoDfj9000h8FmJm0000126mO00318n4AS)
fruits/banana (removed)
Connection update: Connection to the server failed
Connection update: Connection to the server failed
Connection update: Transaction/request processed
=== Initial contents of fruits
Connection update: Transaction/request processed
=== Initial contents of [root]
=== End of [root]
fruits/apple (0TW9QJ2JoDfj9000h8FmJm0000126mO00318n4AS)
=== End of fruits

In this example, the top level hierarchy initially has no objects, and the fruits hierarchy has an apple object. The initial list of objects in each hierarchy is intermixed.

Following the initial list of objects in hierarchy, some updates are shown, then the connection to the object repository server breaks, for some reason. Some time later the connection gets reestablished, for both subscriptions. This results in getting an another list of objects in each hierarchy. This is because the list of objects in each hierarchy could've changed, in the mean time (it didn't here, but it could).

The manager object takes care of reconnecting. All the callbacks in this example have certain limitations, see the section called “What asynchronous C++ API methods can and cannot do” for more information.

Note

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