stasher::current<classptr>, stasher::currentptr<classptr>: a basic stasher::currentBaseObj subclass

The following example is based on showinventory2.C. This example uses stasher::current; this is an x::ref to a stasher::currentObj subclass that implements its update() and connection_update() methods (there's also a stasher::currentptr that defines an x::ptr):

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

#include <x/strtok.H>

#include <sstream>
#include <vector>

#include "inventory.H"

void show_inventory(const inventoryptr &, bool initial);
void status(stasher::req_stat_t status);

bool has_inventory(const inventoryptr &ptr,
		   const std::string &what,
		   int howmuch);

void showinventory(int argc, char **argv)
{
	if (argc < 2)
		return;

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

	typedef stasher::current<inventoryptr> warehouse_t;
	warehouse_t warehouse=warehouse_t::create();

	x::ref<x::obj> mcguffin=warehouse->manage(manager, client, argv[1]);

	std::cout << "Enter for the current inventory, \"what\" \"howmuch\" to wait until we have it,"
		  << std::endl << "EOF to quit"
		  << std::endl;

	std::string dummy;

	while (!std::getline(std::cin, dummy).eof())
	{
		std::vector<std::string> words;

		x::strtok_str(dummy, " \t\r\n", words);

		warehouse_t::base::current_value_t::lock
			lock(warehouse->current_value);

		if (words.size() == 2)
		{
			int i=-1;

			std::istringstream(words[1]) >> i;

			if (i < 0)
			{
				std::cerr << "Eh?" << std::endl;
				continue;
			}

			std::cout << "Waiting for " << i << " " << words[0]
				  << std::endl;

			lock.wait([&lock, &words, i]
				  {
					  return has_inventory(lock->value,
							       words[0],
							       i);
				  });
		}
		show_inventory(lock->value, lock->isinitial);
		status(lock->connection_status);
	}
}

bool has_inventory(const inventoryptr &ptr,
		   const std::string &what,
		   int howmuch)
{
	if (ptr.null())
	{
		std::cout << "No inventory yet..." << std::endl;
		return false;
	}

	auto iter=ptr->stock.find(what);

	int wehave=iter == ptr->stock.end() ? 0:iter->second;

	std::cout << "We have " << wehave << " of them, now." << std::endl;
	return wehave >= howmuch;
}

void show_inventory(const inventoryptr &ptr, bool initial)
{
	std::cout << (initial ? "Current inventory:":"Updated inventory:")
		  << std::endl;

	if (ptr.null())
	{
		std::cout << "    (none)" << std::endl;
	}
	else
	{
		std::cout << "    "
			  << std::setw(30) << std::left
			  << "Item"
			  << "   "
			  << std::setw(8) << std::right
			  << "Count" << std::setw(0)
			  << std::endl;

		std::cout << "    "
			  << std::setfill('-') << std::setw(30)
			  << ""
			  << "   "
			  << std::setw(8)
			  << "" << std::setw(0) << std::setfill(' ')
			  << std::endl;

		for (auto &item:ptr->stock)
		{
			std::cout << "    "
				  << std::setw(30) << std::left
				  << item.first
				  << "   "
				  << std::setw(8) << std::right
				  << item.second << std::setw(0)
				  << std::endl;
		}
		std::cout << std::setw(75) << std::setfill('=') << ""
			  << std::setw(0) << std::setfill(' ') << std::endl;
	}

}

void status(stasher::req_stat_t status)
{
	std::cout << "Connection status: "
		  << x::tostring(status) << std::endl;
}

int main(int argc, char **argv)
{
	try {
		showinventory(argc, argv);
	} catch (const x::exception &e)
	{
		std::cerr << e << std::endl;
		return 1;
	}
	return 0;
}

showinventory3.C waits for Enter before showing the current inventory. Entering grapes 3 before Enter waits until there are 3 grapes in the inventory, then the current inventory gets shown:

 ./showinventory3 instock
Enter for the current inventory, "what" "howmuch" to wait until we have it,
EOF to quit
<Enter>
Current inventory:
    Item                                Count
    ------------------------------   --------
    apples                                  2
===========================================================================
Connection status: Transaction/request processed
grapes 3<Enter>
Waiting for 3 grapes
We have 0 of them, now.
We have 1 of them, now.
We have 3 of them, now.
Updated inventory:
    Item                                Count
    ------------------------------   --------
    apples                                  2
    grapes                                  3
===========================================================================
Connection status: Transaction/request processed

In this example, there were two updates to the inventory in the interim. First, grapes was 1, then the second update set it to 3.

stasher::current<classptr> (and stasher::currentptr<classptr>) refers to a stasher::currentObj<classptr>, a subclass of stasher::currentBaseObj<classptr>. stasher::currentObj contains a member called current_value, of type stasher::current<classptr>::base::current_value_t.

This is an x::mpcobj, a mutex-protected object with value, a classptr to the current value of the object in the repository (a nullptr if there isn't one), a bool isinitial flag, indicating whether value is the initial object value retrieved from the object repository, or an updated one, and a stasher::req_stat_t connection_status, the latest connection status with the stasher repository server, reported by the manager.

stasher::currentObj<classptr>, implements stasher::currentBaseObj<classptr>'s update() and connection_update() by locking the current_value, updating its contents, and then notifying the lock's condition variable, before releasing the lock. showinventory3.C waits for Enter, locks the current_value, and displays its contents. Until the next Enter, current_value gets automatically updated, by the stasher::currentObj, when the object in the repository changes.

To summarize:

Note

The lock on the current_value should be short and brief. If the object gets updated in the repository, a new object gets constructed, and stasher::currentObj's callback gets invoked. This all happens in the client connection thread. The client connection thread waits to acquire a lock on the current_value, and won't do anything else until it does, and finishes updating the object.

The same thing happens if the connection to the server gets dropped, or it gets reconnected. This results in the connection_update update getting invoked, which also needs to acquire a lock on the current_value, so that the status can get updated.

Note

An application that needs to take special action for an isinitial object value must explicitly subclass the stasher::currentObj, and override the connection_update method. This is because the initial value of the object may or may not be installed before manage() returns, since the connection_update gets called from a different thread. Since an object in a stasher object repository can get updated at any time, this means that it can also get updated before manage() returns as well, before the application has a chance to lock the current_value.

The contract for the created managed object's connection_update() methods guarantees that it gets called with a value that's marked as an initial value, but not when.