Index
The manager methods described in Chapter 10, Asynchronous connection manager provide stable means for automatically retrieving objects from stasher when the objects get updated. The objects get retrieved as read-only file descriptors.
LIBCXX provides templates that serialize and deserialize class instances to file descriptors. This part introduces templates and methods that get combined with the manager interface into a set of templates and classes that deserialize the current value of an object in a stasher object repository.
The manager notifies the templates when the object in the object repository changes. The templates and methods generate code that retrieve and deserialize the raw file descriptor into some class instance.
The class must be
a reference-counted object that's virtually subclassed from
x::obj
. See LIBCXX's documentation for more
information.
The class must implement two non-default constructors.
The first constructor that takes two parameters,
an x::uuid
and a file descriptor. This
constructor gets called when the object is retrieved from the
repository. The constructor, presumably, gets to read the file
descriptor and initialize the class instance from its contents.
Although LIBCXX's serialization and deserialization templates are
a convenient way to do so, their usage is not required. The
constructor gets the raw file descriptor, and it's up to the
constructor to read what it needs to read from it.
If the constructor throws an exception, the presumed reason is that
the object file in the object repository got corrupted or cannot
be parsed (created by a previous version of the application, or
something along the lines). It's preferrable for the first
constructor to handle that, but if that gets handled by throwing
an exception, the second non-default constructor gets called,
which takes only a x::uuid
parameter.
The intention here is to initialize a default object instance that replaces the unparsable object in the repository, but to do that the existing object's uuid must be known. For that reason, the constructor gets called with the existing object's uuid, with the presumption that whenever the instantiated object gets serialized later, its valid, serialized contents replace the unparsable object in the repository.
These constructors get typically invoked from a client connection thread callback. As such, it has certain limitations, see the section called “What asynchronous C++ API methods can and cannot do” for more information.
In most cases the class also needs to implement a default constructor.
The examples in this chapter use the following class as an example:
#ifndef inventory_H #define inventory_H #include <x/obj.H> #include <x/ref.H> #include <x/ptr.H> #include <x/uuid.H> #include <x/fd.H> #include <x/fditer.H> #include <x/serialize.H> #include <x/deserialize.H> #include <x/exception.H> #include <map> #include <iostream> // An inventory of stuff class inventoryObj : virtual public x::obj { public: // This is the inventory, key product name, int is the number in stock. std::map<std::string, int> stock; // uuid of this object in the repository x::uuid uuid; // Default constructor inventoryObj() {} // Destructor ~inventoryObj() {} // Copy constructor inventoryObj(const inventoryObj &o) : stock(o.stock), uuid(o.uuid) { } // Serialization function template<typename iter_type> void serialize(iter_type &iter) { iter(stock); } // Construct from an object in the repository inventoryObj(const x::uuid &uuidArg, const x::fd &fd) : uuid(uuidArg) { x::fdinputiter b(fd), e; x::deserialize::iterator<x::fdinputiter> iter(b, e); serialize(iter); } // The above constructor threw an exception, so the manager calls this // one. We should report an error somehow, the inventory object must be // corrupted, but this is just an example. inventoryObj(const x::uuid &uuidArg) : uuid(uuidArg) { } }; typedef x::ref<inventoryObj> inventory; typedef x::ptr<inventoryObj> inventoryptr; #endif
This is a fairly straightforward, simple class, with a serialization function, and a constructor that employs the serialization function to deserialize the existing object from a file descriptor. An instance of this class gets stored in an object in the stasher object repository. Use the following program to update this object:
#include <stasher/client.H> #include "inventory.H" #include <sstream> #include <iterator> std::string apply_updates(const inventory &i, int argc, char **argv); void setinventory(int argc, char **argv) { if (argc < 2) return; auto client=stasher::client::base::connect(); stasher::client::base::transaction tran= stasher::client::base::transaction::create(); // Get the existing object, if it exists, first. stasher::client::base::getreq req =stasher::client::base::getreq::create(); req->openobjects=true; req->objects.insert(argv[1]); stasher::contents contents=client->get(req)->objects; if (!contents->succeeded) throw EXCEPTION(contents->errmsg); auto existing=contents->find(argv[1]); if (existing == contents->end()) { // Object did not exist, create a new one. std::string i=apply_updates(inventory::create(), argc, argv); if (i.empty()) return; // New one is empty, nothing to do tran->newobj(argv[1], i); } else { // Update an existing object. auto i=apply_updates(inventory::create(existing->second.uuid, existing->second.fd), argc, argv); // If there's no inventory at all, delete it, else update it. if (i.empty()) tran->delobj(argv[1], existing->second.uuid); else tran->updobj(argv[1], existing->second.uuid, i); } // Note that this is a basic, simple example, that doesn't check for // req_rejected_stat, so if two updates occur at the same time, one // is going to get tossed out. This program is for demo purposes only. stasher::putresults results=client->put(tran); if (results->status != stasher::req_processed_stat) throw EXCEPTION(x::tostring(results->status)); std::cout << "Updated" << std::endl; } // Apply updates to the inventory in an existing object. std::string apply_updates(const inventory &i, int argc, char **argv) { for (int n=2; n+1<argc; n += 2) { int count=0; std::istringstream(argv[n+1]) >> count; std::string name(argv[n]); if (count == 0) i->stock.erase(name); else i->stock[name]=count; } // Now, serialize it into a string. std::string buf; if (i->stock.empty()) return buf; // Empty inventory, return an empty string. typedef std::back_insert_iterator<std::string> ins_buf_iter_t; ins_buf_iter_t iter(buf); x::serialize::iterator<ins_buf_iter_t> serialize(iter); i->serialize(serialize); return buf; } int main(int argc, char **argv) { try { setinventory(argc, argv); } catch (const x::exception &e) { std::cerr << e << std::endl; return 1; } return 0; }
The first parameter to setinventory is the name of an object for this inventory class. The remaining parameters is a list of “name” and “count” parameters. setinventory adds them or replace them in the inventory object. For example:
$ ./setinventory instock apples 2 bananas 3
This puts a key of “apples” with the value of 2, and
“bananas” with the value of 3 into
inventoryObj
's std::map
(a 0 count removes that key from the std::map
).
If an object named “instock” does not exist, it gets
created, otherwise its existing contents are read and updated.
This simplistic example does not handle the
stasher::req_rejected_stat
error indicating that
something else updated the same object at the same time. For these
simple examples it's presumed that
only setinventory updates them.