Blocking safe by default (Implementation Details)
As described in the section Blocking Safe By Default, ServiceTalk, by default, allows users to write blocking code when interacting with ServiceTalk. This document describes the details of the implementation and is addressed to audiences who intend to know the internals of how this is achieved.
|It is not required to read this document if you just want to use ServiceTalk.|
Everything inside ServiceTalk is somehow connected to one of the three asynchronous sources, viz.,
Completable. Since these sources are the building blocks for program control flow if they provide safety
guarantees for blocking code execution these guarantees apply outside the scope of preventing blocking code from
executing on an EventLoop thread. This approach is designed to make the task of ensuring we don’t block the EventLoop
threads less error-prone, and also allows for certain optimizations around thread context propagation and re-use.
An asynchronous source has two important decisions to make about thread usage:
Which thread or executor will be used to do the actual task related to a source. eg: for an HTTP client, the task is to send an HTTP request and read the HTTP response.
Which thread or executor will be used to interact with the
Subscribercorresponding to its `Subscription`s.
Part 1. above is not governed by the
and hence sources are free to use any thread. ServiceTalk typically will use Netty’s
EventLoop to perform the actual
Part 2. defines all the interactions using the ReactiveStreams specifications, i.e. all methods in
Subscription. The ReactiveStreams specification requires that signals are not delivered
but doesn’t have any restrictions about which threads are used. This means the same thread maybe used for all signal
deliveries for a given
Subscriber, but it is also valid to use any thread (as long as no concurrency is introduced).
ServiceTalk concurrency APIs are used to define which executor will be used for an asynchronous source for Part 2,
which is typically an application
ServiceTalk uses the
abstraction to specify the source of threads to be used for the delivery of signals from an asynchronous source. The
default signal offloading, if any, used by an asynchronous source is determined by the source. For example, the HTTP
sources, in addition to allowing for specification of an offloading executor, provide both direct control of the
and may also influenced by the
computed execution strategy.
Applications with asynchronous, blocking, or computationally expensive tasks can also offload those tasks to specific
publishOn(Executor) operators will cause offloading execution from the
default signal delivery thread to a thread from the provided
Executor. The below diagram illustrates the interaction
between an asynchronous source, its
Subscriber, its operators, and the
Subscriber method execution, the result publication and termination signals, the
Executor active at the
source is inherited by all operators unless there is a reason to switch to a different
Executor. The switch to another
executor, offloading, is done for a couple of reasons; unless configured to not offload ServiceTalk will offload from
EventLoop thread as necessary in order to allow user code to block.
subscribe() the execution will offload at the
subscribeOn() operator and transition execution from the
subscribing thread to an
Exeuctor thread. The subscribing thread will be able to continue while the subscribe
operation asynchronously continues on an
The diagram shows a typical case, when a result is available at the source it will begin publication on the receiving
Netty EventLoop thread. Assuming that the default ServiceTalk offloading has been disabled, then offloading will only
happen at the
publishOn() operator during
Subscriber signals and will transition execution from the EventLoop thread
Executor thread. Once the
Subscriber signal is offloaded the EventLoop thread will be available again for
executing other I/O tasks while the response is asynchronously processed on the