Async Sessions

The problem

The functions of the low-level IPC mechanism of HelenOS that perform IPC requests (such as ipc_call_async_0()) operate on phones (they are passed a phone capability handle). In any non-trivial IPC protocol there are operations that consist of more than one IPC call (e.g. VFS_IN_READ) performed either in parallel or in series. (Here we call an operation a set of IPC calls that causes the server to perform a well-defined action). For such protocol it is not possible to perform several operations concurrently on the same phone, because the calls from different operations would get mixed up.

For illustration, consider the following client API pseudocode:

int phone;

int server_connect(void)
    /* Connect phone to the service */
    phone = ipc_connect_me_to_iface(PHONE_NS, INTERFACE_SOME_SRV, SERVICE_SOME_SRV, 0);

int server_read(int arg, void *buf, size_t bufsize)
    /* Send first request R1 (including operation code). */
    ipc_call_async_1(phone, SOME_SRV_READ, NULL, server_read_callback);

    /* Send second request (IPC_M_DATA_READ) */
    ipc_data_read_start(phone, buf, bufsize, NULL, server_read_data_callback);

    /* Wait for the callbacks to be called */

Were we to call server_read() from two different threads or fibrils A and B concurrently, the server fibril would see some mixture of the sequence (AR1, AR2) with (BR1, BR2), e.g. (AR1, BR1, AR2, BR2), which would be incorrect from the server point of view. Note that the server has no way of telling which request belongs to which operation.

We often want to allow such function to be called concurrently in different threads or fibrils. server_read() can block so we cannot serialize calls to it. If we took a character device as an example, we often want to allow one fibril to write while another fibril is blocked in a read operation.

Enter sessions

A session is a logical datapath from a client to a server which can support concurrent operations. Each operation is performed in the context of an exchange. An exchange can support an IPC operation (or, optionally, a sequence of operations), but, in any case, only one operation is performed in an exchange at any time. (The session/connection terminology is borrowed from iSCSI and exchange is borrowed from Fibre Channel transport layer).

The async framework abstracts from explicitly creating sessions by wrapping the low-level phone handles into async_sess_t automatically (by the means if calling async_connect_me_to_iface(), async_callback_receive(), etc.). Whenever the client needs to perform an operation, it starts an exchange by calling async_exchange_begin() and performs the operation in context of that exchange. Then the client ends the exchange by calling async_exchange_end().

We can amend the example above as follows:

async_sess_t *session;

int server_connect()
    /* Create an exchange on the naming service session */
    async_exch_t *exch = async_exchange_begin(ns_session);

    /* Connect a session to the service */
    session = async_connect_me_to_iface(exch, INTERFACE_SOME_SRV, SERVICE_SOME_SRV, 0);

    /* End the naming service exchange */

int server_read(int arg, void *buf, size_t bufsize)
    /* Begin the exchange */
    async_exch_t *exch = async_exchange_begin(session);

    /* Send first request R1 (including operation code). */
    aid_t req = async_send_1(exch, SOME_SRV_READ, arg);

    /* Send second request (IPC_M_DATA_READ) and wait for result. */
    async_data_read_start(exch, buf, bufsize);

    /* End the exchange */

    /* Wait for R1 reply. */
    int rc;
    async_wait_for(req, &rc);

Now we can run server_read() in parallel in any number of fibrils/threads. Each IPC request has a context (the exchange) so the different parallel operations will not mix up.

Behind the scenes the async framework will do the magic necessary to distinguish the exchanges. Depending on the implementation of the server and the interface type, the async framework either enforces serialization of exchanges via locking or clones the session phone and uses as many separate physical connections as there are concurrent exchanges. It caches the open connections to avoid setting them up and tearing them down too often.

Last modified 6 years ago Last modified on 2018-06-21T14:45:40Z
Note: See TracWiki for help on using the wiki.