Asynchronous eCAP Adapters API

This document describes APIs that allow eCAP adapters to run in parallel with
the host application. This is useful when an adapter has to do I/O, perform
long content analysis, or use 3rd party libraries that do those things.


* For the impatient:

You should not rush when working with threads, but if you want to learn the
hard way, please review how adapter_async from the official eCAP sample adapter
collection implements the following methods:

  bool Adapter::Service::makesAsyncXactions() const;
  void Adapter::Service::suspend(timeval &timeout);
  void Adapter::Service::resume();
  void Adapter::Xaction::resume();
 
Please remember that all libecap methods have to be executed inside the host
application thread (or equivalent). Adapter code may respond to application
requests but may not call the application proactively, from the adapter
thread.


* Terminology: 

This document uses the terms "threaded" and "asynchronous" interchangeably,
even though the latter one is more general and the discussed APIs are meant to
work in any environment that supports parallel execution of multiple code
paths.


* Overall design:

Starting with v1.0, eCAP has APIs to isolate the host application (which may
be threaded or monolithic) from a threaded adapter. The overall design is based
on the idea that one side (the host application) has to be in charge for the
isolation to work. This means that the host should poll the adaptation service
for transactions that became ready. Polling involves three steps:

Wait: The host application has to give the adapter control before the host
    starts sleeping or waiting for I/O so that the adapter can influence the
    length of that wait. This is done by calling Service::suspend().
    Host applications that do not block for a long time should still call this
    method to learn how soon they should ask the adapter about transactions
    that became ready.

Ask: After the wait described above is over, the host application gives the
    adapter control again so that the adapter can tell the host which
    transactions became ready while the host was waiting. This is done by
    calling Service::resume().

Answer: When the host asks about ready transactions (see above), the
    adaptation service can call host::Xaction::resume() and the host will,
    sooner or later, resume working on that transaction by calling its
    adapter::Xaction::resume() method. Why should not the adapter call its own
    transaction's resume() method instead? The host may need to prepare
    internal transaction state for calls such as host::Xaction::useVirgin()
    and doing so for all pending transactions may be prohibitively expensive.

This approach introduces a delay between the time an asynchronous adapter
transaction becomes ready and the time the host application resumes that
adapter transaction. On an idle or threaded server, the adapter can limit that
delay. On a busy monolithic server, the delay should be negligible (because
such a server virtually never sleeps and can poll the adapter after every
"cycle" of work).


* Service::suspend() and Service::resume() calls order and timing:

It is important to note that the Service::suspend() method may be
called independently of the Service::resume() method. The method calls
may not be "paired" in any way, even though it is convenient to think of the
methods that way. Depending on the host application implementation and needs,
either method may be called multiple times before or after the other.

In other words, the adapter must be prepared to receive a Service::suspend()
or Service::resume() call at any time, regardless of the call history.


* void Service::suspend(timeval &timeout)

If the adapter wants the next polling to happen after the specified timeout,
it should not modify the parameter value. Otherwise, the adapter should
decrease the timeout value according to the service preferences. These rules
are meant to make the host application code simpler and faster because the
host may have to iterate several services and because such iterations may run
inside a performance-critical code section. It is a good idea to optimize this
method implementation.

The initial timeout value is determined by the host application and may be as
large as the maximum value allowed by the timeval structure. Note that the
timeout value never goes up!

For example, if a Service decreases timeout to 300ms, then the adapter is
likely to call Service::resume() in about 300ms from the current time.

Again, there is no guarantee that the Service::resume() method will be
called around the timeout expiration time. For example, other adaptation
services may decrease the timeout, prompting the host application to call
Service::resume() a lot sooner, or the host may be overloaded and call
it later.


* void Service::resume()

This method is meant for resuming adaptation transactions that were waiting
for the host application to become active. However, the method must not call
their adapter::Xaction::resume() method directly. Instead, it should call the
corresponding host::Xaction::resume() method and let the host code prepare for
and perform transaction resumption at the host application convenience.
Moreover, a Service::resume() implementation should not call other
host::Xaction methods as the host application may not be ready to handle them.

Needless to say, the adapter has to remember transactions that are ready to be
resumed. The set of remembered transactions may have to be adjusted if the
host application stops any of them by calling adapter::Xaction::stop(). The
adapter must not attempt to resume stopped transactions.

This method may resume zero or more transactions.


* bool Service::makesAsyncXactions() const;

The adapter service must return true if and only if it wants to participate in
asynchronous polling. The host application calls this method at least once
after Service::start() and before sending any transactions to the service.

This method is an optimization -- without it, the host application would have
to poll all services while many adapters do not require the associated
overheads.


* Storing asynchronous transactions.

Asynchronous adapters must store [pointers to] pending transactions because
their state may change before the host application calls any of the
transaction methods. If keeping pending transaction state around is required,
use libecap::shared_ptr and the transaction object will not be destroyed until
both the host application and the adapter no longer use it. Otherwise,
consider using libecap::weak_ptr to get protection from dangling pointers
without delaying transaction object destruction by the host application.

If you decide to store raw transaction pointers, your code must make sure
those raw pointers are properly invalidated if the host application calls
adapter::Xaction::stop() and destroys the transaction object. Doing so
correctly is especially tricky inside asynchronous code, so be careful.

The above does _not_ imply that libecap smart pointers magically solve
thread-safety problems. The thread-safety of TR1 equivalents of
libecap::shared_ptr and libecap::weak_ptr classes is discussed in [1] and [2].
In short, they are as thread-[un]safe as built-in C++ types.

[1] http://gcc.gnu.org/onlinedocs/libstdc++/manual/shared_ptr.html
[2] www.boost.org/doc/libs/release/libs/smart_ptr/shared_ptr.htm#ThreadSafety
