Blocking safe by default
ServiceTalk uses Netty under the hood which uses the Event loop execution model. A drawback of this construct is that any potentially long-running work (blocking calls, intensive computations, etc) that is done on the event loop negatively impacts the processing of all subsequent tasks. This is the reason why writing blocking code is usually discouraged when using Netty.
Writing non-blocking code is hard and requires continuous, significant effort to make sure that no code path interacting with the event loop is invoking any blocking code. In practice, even the most careful application owners are occasionally surprised that they have some blocking code in their applications.
We want ServiceTalk to be an approachable library for all developers. Providing a simple message that "blocking is safe by default" lowers the bar to entry and eases the operational burden of continuously making sure that no blocking code exists in any path. There are no exceptions to this rule for ServiceTalk users, although the bar is higher for ServiceTalk developers.
Although writing completely non-blocking applications is hard in practice, still there are reasons and use cases to reduce thread hops and do all application processing on the event loop. ServiceTalk makes it possible to write such applications but requires explicit opt-ins to avoid inadvertently blocking the event loop.
Simply put, ServiceTalk makes sure that no user code is called on the event loop. In order to do that, it uses an Executor to offload invocation of user code out of the event loop to a different thread.
Although the outcome is simple that no user code should run on the event loop, implementations of this offloading are often either naive hence sub-optimal or fairly complex. ServiceTalk internally does the heavy lifting to make sure that it does not offload more than what is required. In other words, it reduces offloading when it can deduce that no user code can interact with the event loop on a certain path.
An Execution Strategy has two primary purposes:
Define which interaction paths require offloading.
Optionally specify an Executor to use for offloading. In absence of a specified
Executor, ServiceTalk will use a default
At a general transport layer (no application level protocol), only two offload paths are available:
Sending data to the transport.
Receiving data from the transport.
However, different protocols, eg: HTTP may provide more sophisticated offloading paths that can be controlled by a strategy.
ServiceTalk does the heavy lifting of determining the optimal offloading strategy. This optimal strategy is determined based on different inputs as outlined below:
Programming Paradigms.: A certain programming model may reduce the paths user code can interact with the event loop and hence make some offloading redundant.
Presence of user code: ServiceTalk is flexible and allows users to add code at different places. Presence of any user code is deemed as unsafe and hence requires offloading. If users are sure the added code does not block, they can declare so through the constructs provided at the protocol level.
Override execution strategy: ServiceTalk provides a way for users to override the Execution Strategy used for a client/server. This disables all intelligence around finding the optimized strategy and just uses the provided strategy.
ServiceTalk acknowledges that writing completely asynchronous code is hard and is not an "all or nothing" decision. Practically, applications will have a mixed profile such that some paths are completely asynchronous, and some paths have blocking code. This is the reason it provides ways to selectively opt-in for disabling offloading for the same client/server. These selective opt-ins are specific to protocols, for example as elaborated under HTTP. In addition, there is a way to override the strategy for a client/server, but it comes with additional responsibility from the user assuming "you know what you are doing".
Internals of ServiceTalk still discourages blocking code and hence should be avoided while contributing to ServiceTalk. Unless there is a valid reason to do it, ServiceTalk internals are always non-blocking.