Chapter 9. Asynchronous API

Index

Introduction
What asynchronous C++ API methods can and cannot do
stasher::client->get_request(): asynchronous version of get()
stasher::client->put_request(): asynchronous version of put()
stasher::process_request() template function
Other stasher::client->name_request()s

Introduction

Methods described in Chapter 8, Synchronous API send a message to the client connection thread then wait for the message to get processed, before returning the results.

This is sufficient for implementing simple, basic scripts or batch jobs. For interactive applications stasher's asynchronous API might be a better fit.

Each method described in Chapter 8, Synchronous API, namely stasher::client->get(), stasher::client->put(), stasher::client->getdir(), and stasher::client->subscribe(), has an asynchronous name_request version, that takes the same parameters as the corresponding synchronous method.

The asynchronous name_request returns a std::pair. A std::pair returned by name_request has stasher::namerequest for its first, and a stasher::client::base:request.

This is a consistent naming convention. For example, get_request() returns a std::pair<stasher::getrequest, stasher::client::base::request>, put_request() returns a std::pair<stasher::putrequest, stasher::client::base::request>, and so on. The asynchronous name_request method does not wait for the request to be processed, it returns immediately. std::pair's second, a stasher::client::base:request, is an x::ref to an object that indicates the status of the request, using the following methods:

bool stasher::client::base:request->done()

done() returns true when the request is processed, and the return value in std::pair's first is valid.

void stasher::client::base:request->wait()

If the requedt is not processed, wait() waits until it does. After wait() returns, done() always returns true.

x::ref<x::obj> stasher::client::base:request->mcguffin()

mcguffin() returns a reference to an opaque object that represents the request's mcguffin. The client connection thread has an internal reference on the underlying object, which gets released when the client connection thread processes the request. If no other strong references to the object remain, it goes out of scope and gets destroyed, invoking any destructor callbacks.

Note

The callbacks get invoked by the client connection thread. They should not throw exceptions, block, or take an excessive time to return.

The stasher::namerequest in std::pair's first, contains a single getmsg() method that returns the results of the request, the same return value that the synchronous name() would return. stasher's synchronous API is just a wrapper for the asynchronous API. Here's all of stasher::client->get():

stasher::getresults stasher::clientObj::get(const stasher::getreq &contents_req)
{
    std::pair<stasher::getrequest, stasher::client::base::request> req=get_request(contents_req);

    req.second->wait();
    return req.first->getmsg();
}

All other synchronous methods work the same way. As an alternative to wait(), use mcguffin() and attach a destructor callback to it, which gets invoked when the request completes.

Note

For consistent semantics, after the request is already done mcguffin() returns a dummy mcguffin, without retaining a reference on it. When the caller attaches a destructor callback to it, afer the dummy mcguffin goes out of scope and gets destroyed, the destructor callback gets triggered, since the request has completed, already.

Note that, as mentioned previously, destructor callacks usually get invoked by the client connection thread. They should not throw exceptions, or do something that takes a while.

What asynchronous C++ API methods can and cannot do

Callbacks that get executed by a client connection thread cannot invoke synchronous C++ methods, but they can invoke asynchronous methods, since the asynchronous methods simply send a message to the client connection thread and return. Synchronous methods send the message and wait for a response from the threads, so a thread deadlock occurs when the same thread invokes the synchronous method.

Note that this applies to the updated() callback that gets invoked by stasher::client->subscribe, and to destructor callbacks attached to asynchronous requests' mcguffins, since they're usually invoked by the client connection thread.

In general, callback objects must not have a strong reference on stasher::client. There are at least two reasons for that.

  1. In most cases, the client connection thread thread has a strong reference on the callback object. If that object, in turn has a stasher::client, this creates a circular reference.

    stasher::client is an x::ref itself. When the last reference to the underlying object gets destroyed, the underlying object's destructor stops the client connection thread thread if it's running, disconnecting from the object repository. The circular reference prevents this from happening.

  2. Using a weak reference is usually insufficient, since a strong reference gets recovered from the weak one, whenever the weak reference needs to be used in any way. Setting aside the fact that a circular reference exists now exists, if all other instances of stasher::client referring to this connection go out of scope and get destroyed, this one becomes the last stasher::client, and when it goes out of scope and gets destroyed, the underlying object's destructor attempts to stop the client connection thread, and waits until it does.

    If this happens during one of the callbacks which gets executed by the client connection thread, this results in a thread deadlock, since the client connection thread is essentially waits for itself to stop.

The recommended approach is for the callbacks to do little beyond sending a message to another thread, or recording their invocation in some queue or an intermediate object, of some sort. Other threads or objects with their own stasher::client can have a reference to the same intermediate queue or object, without creating a circular reference.

Under very controlled situations, it's possible for a callback to have its own stasher::client. See the section called “stasher::client->get_request(): asynchronous version of get()” for an example.