Thread-safe semaphore queue

#include <x/semaphore.H>

class testownerObj : public x::semaphore::base::ownerObj {

public:

    // ...

    void process(const x::ref<x::obj> &mcguffin) override;

    // ...
};

typedef x::ref<testownerObj> testowner;

testowner owner=testowner::create();

x::property::value<size_t> maxjobs("maxjobs", 10);

x::semaphore sem(x::semaphore::create(maxjobs));

sem->request(owner, 1);

x::semaphore is one of two semaphore-oriented design patterns. This class implements a semaphore whose size comes from an application property, and with a queue of objects that are waiting to acquire the semaphore. x::semaphore is a reference-counted object. The create() method creates the semaphore, its parameter is a property that specifies the maximum value of the semaphore counter, 10 in the above example.

Acquiring ownership of the semaphore increments the semaphore counter. Releasing ownership decrements the counter. request() submits a request to acquire the semaphore. The first parameter is a x::ref to a subclass of x::semaphore::base::ownerObj, a reference-counted object which defines process(), which gets invoked when the requested ownership' gets acquired.

process() receives a mcguffin. The ownership of the semaphore does not necessarily get released when process() returns, but rather when the mcguffin goes out of scope and gets destroyed. If process() returns without touching the mcguffin, it goes out of scope, gets destroyed, and the acquired ownership of the semaphore gets released automatically upon return from process. If process() stashes the mcguffin away, somewhere, the ownership of the semaphore remains with the mcguffin, and gets released only when mcguffin goes out of scope and gets destroyed.

The second parameter to request() specifies by how much the semaphore gets incremented (not necessarily 1, but t cannot be zero). If this can be done and the semaphore's count remains at or below the maximum value, the ownership gets acquired immediately, and the owner's process() gets invoked immediately.

Otherwise, request() returns, and process() gets invoked later, after other owners release their ownership and the semaphore's counter gets decremented sufficiently. The list of requested owners is maintained in a FIFO-ordered queue.

The semaphore keeps only a weak reference on the requested owners. The owner object can go out of scope and gets destroyed without its process() ever getting invoked.

Every thread that calls request(), and every thread where the last reference to the ownership mcguffin goes out of scope, gets co-opted in processing waiting owners and invoking their process() method. process() should not be a time-consuming operation. Blocking and waiting, in process() should be avoided.

Note

The value of the semaphore counter property may be changed, and the new maximum value takes effect immediately, in general. If the new maximum semaphore counter exceeds the new semaphore count, no more owners get acquired until the semaphore count goes down below the new maximum value. If the new maximum value gets increased, pending owners do not get acquired immediately. This happens the next time a new owner is requested, or an existing ownership mcguffin goes out of scope; at which time as many owners get acquired as possible, given the new maximum semaphore count.

x::semaphore::base::owner owner=
    x::semaphore::base::owner::create(
       []
       (const x::ref<x::obj> &mcguffin)
       {
           // ...
       });

s->request(owner, 1);

The semaphore owner object class supplies a create() that takes a functor and constructs an owner object that invokes the given functor from its process().