Model threading

Methods marked as oneway don't block. For methods not marked as oneway, a client's method call blocks until the server has completed execution or called a synchronous callback (whichever comes first). Server method implementations can call at most one synchronous callback; extra callback calls are discarded and logged as errors. If a method is supposed to return values via callback and doesn't call its callback, this is logged as an error and reported as a transport error to the client.

Threads in passthrough mode

In passthrough mode, most calls are synchronous. However, to preserve the intended behavior that oneway calls don't block the client, a thread is created for each process. For details, see the HIDL overview.

Threads in binderized HALs

To serve incoming RPC calls (including asynchronous callbacks from HALs to HAL users) and death notifications, a threadpool is associated with each process that uses HIDL. If a single process implements multiple HIDL interfaces and/or death notification handlers, its threadpool is shared between all of them. When a process receives an incoming method call from a client, it picks a free thread from the threadpool and executes the call on that thread. If no free thread is available, it blocks until one is available.

If the server has only one thread, then calls into the server are completed in order. A server with more than one thread might complete calls out of order even if the client has only one thread. However, for a given interface object, oneway calls are guaranteed to be ordered (see Server threading model). For a multi-threaded server that hosts multiple interfaces, oneway calls to different interfaces might be processed concurrently with each other or other blocking calls.

Multiple nested calls are sent on the same hwbinder thread. For instance, if a process (A) makes a synchronous call from a hwbinder thread into process (B), and then process (B) makes a synchronous call back into process (A), the call is executed on the original hwbinder thread in (A) which is blocked on the original call. This optimization makes it possible to have a single threaded server able to handle nested calls, but it doesn't extend to cases where the calls travel through another sequence of IPC calls. For instance, if process (B) had made a binder/vndbinder call which called into a process (C) and then process (C) calls back into (A), it can't be served on the original thread in (A).

Server threading model

Except for passthrough mode, server implementations of HIDL interfaces live in a different process than the client and need one or more threads waiting for incoming method calls. These threads are the server's threadpool; the server can decide how many threads it wants running in its threadpool, and can use a threadpool size of one to serialize all calls on its interfaces. If the server has more than one thread in the threadpool, it can receive concurrent incoming calls on any of its interfaces (in C++, this means that shared data must be carefully locked).

One-way calls into the same interface are serialized. If a multi-threaded client calls method1 and method2 on interface IFoo, and method3 on interface IBar, method1 and method2 is always serialized, but method3 can run in parallel with method1 and method2.

A single client thread of execution can cause concurrent execution on a server with multiple threads in two ways:

  • oneway calls don't block. If a oneway call is executed and then a non-oneway is called, the server can execute the oneway call and the non-oneway call simultaneously.
  • Server methods that pass data back with synchronous callbacks can unblock the client as soon as the callback is called from the server.

For the second way, any code in the server function that executes after the callback is called can execute concurrently, with the server handling subsequent calls from the client. This includes code in the server function and automatic destructors that execute at the end of the function. If the server has more than one thread in its threadpool, concurrency issues arise even if calls are coming in from only one single client thread. (If any HAL served by a process needs multiple threads, all HALs have multiple threads because the threadpool is shared per-process.)

As soon as the server calls the provided callback, the transport can call the implemented callback on the client and unblock the client. The client proceeds in parallel with whatever the server implementation does after it calls the callback (which might include running destructors). Code in the server function after the callback is no longer blocking the client (as long as the server threadpool has enough threads to handle incoming calls), but might be executed concurrently with future calls from the client (unless the server threadpool has only one thread).

In addition to synchronous callbacks, oneway calls from a single-threaded client can be handled concurrently by a server with multiple threads in its threadpool, but only if those oneway calls are executed on different interfaces. oneway calls on the same interface are always serialized.

Note: We strongly encourage server functions to return as soon as they have called the callback function.

For example (in C++):

Return<void> someMethod(someMethod_cb _cb) {
    // Do some processing, then call callback with return data
    hidl_vec<uint32_t> vec = ...
    _cb(vec);
    // At this point, the client's callback is called,
    // and the client resumes execution.
    ...
    return Void(); // is basically a no-op
};

Client threading model

The threading model on the client differs between non-blocking calls (functions that are marked with the oneway keyword) and blocking calls (functions that don't have the oneway keyword specified).

Block calls

For blocking calls, the client blocks until one of the following happens:

  • Transport error occurs; the Return object contains an error state that can be retrieved with Return::isOk().
  • Server implementation calls the callback (if there was one).
  • Server implementation returns a value (if there was no callback parameter).

In case of success, the callback function the client passes as an argument is always called by the server before the function itself returns. The callback is executed on the same thread that the function call is made on, so implementers must be careful with holding locks during function calls (and avoid them altogether when possible). A function without a generates statement or a oneway keyword is still blocking; the client blocks until the server returns a Return<void> object.

One-way calls

When a function is marked oneway, the client returns immediately and doesn't wait for the server to complete its function call invocation. At the surface (and in aggregate), this means the function call takes half the time because it is executing half the code, but when writing implementations that are performance sensitive, this has some scheduling implications. Normally, using a one-way call causes the caller to continue to be scheduled whereas using a normal synchronous call causes the scheduler to immediately transfer from the caller to the callee process. This is a performance optimization in binder. For services where the one-way call must be executed in the target process with a high priority, the scheduling policy of the receiving service can be changed. In C++, using libhidltransport's method setMinSchedulerPolicy with the scheduler priorities and policies defined in sched.h ensures that all calls into the service run at least at the set scheduling policy and priority.