The Execute Blocking component is a small but potentially useful new component I’ve written and committed to the apiman gateway, allowing custom policy plugin authors to safely execute blocking code.
Blocking is bad, but…
Apiman’s gateway embraces non-blocking and asychronous design principles in order to best exploit the beneficial performance characteristics of platforms such as Vert.x.
A constraint of this approach means that code must not block the thread it is executing upon for excessive periods of time, lest an entire reactor (and often physical core) becomes completely unavailable; blocking on IO or holding locks for example. For those who are interested I explored this issue in more depth in a previous post, but safe to say, this golden rule can be difficult to abide by in the real world where libraries and protocols outside of our control may not behave as we need.
The Execute Blocking Component (IExecuteBlockingComponent
) comes to our rescue, providing a mechanism to execute blocking code from a threadpool rather than directly on the reactor.
Needless to say, special attention should be paid to its limitations and characteristics. Only use this functionality when strictly necessary, and with the smallest practical scope, as it substantially reduces the performance benefits of the underlying non-blocking architecture.
Execute Blocking
Here’s a short example of the component in action:
void doApply(ApiRequest request, IPolicyContext context,
IPolicyChain<ApiRequest> chain) {
IExecuteBlockingComponent ebComponent =
context.getComponent("IExecuteBlockingComponent.class");
ebComponent.executeBlocking(future -> { (1)
String result = BlockingService(50000); (2)
future.completed(result); (3)
},
result -> { (4)
if (result.successful()) {
request.getHeaders().put("Foo", request.getResult()) (5)
chain.doApply(request);
} else {
chain.doFailure(new PolFailure(result.getError())); (6)
}
});
}
1 | Execute your blocking code within the first lambda, and indicate to the future when your code has terminated, either completed or fail . Exceptions thrown within the scope of the lambda will be caught and implicitly passed to fail . |
2 | Imaginary blocking service that blocks for 50000ms. |
3 | Indicating the future has completed successfully and passing the result to completed . |
4 | The result is passed to this second lambda asynchronously. |
5 | Set our successful result as a header Foo . |
6 | Creating a policy failure with the error. |
Only use executeBlocking when absolutely necessary and constrain it as tightly as possible. |
Apiman on blocking platforms
You may be wondering what happens if you use executeBlocking
on an apiman platform which has backed by a implementation where it’s safe to block the executing thread (such a servlet on WildFly). Simply, a passthrough implementation is used on those platforms which executes immediately on the same thread - so it’s safe to use on any platform.
Even if you tend to run your plugins on a blocking platform you should still respect the "no blocking the main thread" rule and use executeBlocking
, as other platforms and users will expect this guarantee - it’s especially crucial if you wish to share your work with the community.
Final Words
This is the first commit of this code onto master, so changes are possible. This feature is directly designed to enable the use of Vert.x, so their interface has been adopted fairly closely, so many thanks to them again for their brilliant work which we’re fortunate to be able harness in apiman.