public interface Execution extends MutableRegistry, ExecControl
Ratpack is non blocking.
This requires that IO and other blocking operations are performed asynchronously.
In completely synchronous execution, the thread and the call stack serve as the representation of a stream of execution,
with the execution being bound to a single thread exclusively for its entire duration.
The Execution
concept in Ratpack brings some of the characteristics of the traditional single-thread exclusive model
to the asynchronous, non-exclusive, environment.
A well understood example of a logical stream of execution in the web application environment is the handling of a request. This can be thought of as a single logical operation; the request comes in and processing happens until the response is sent back. Many web application frameworks exclusively assign a thread to such a stream of execution, from a large thread pool. If a blocking operation is performed during the execution, the thread sits waiting until it can continue (e.g. the IO completes, or the contended resource becomes available). Thereby the segments of the execution are serialized and the call stack provides execution context. Ratpack supports the non-blocking model, where threads do not wait. Instead of threads waiting for IO or some future event, they are returned to the “pool” to be used by other executions (and therefore the pool can be smaller). When the IO completes or the contended resource becomes available, execution continues with a new call stack and possibly on a different thread.
The execution object underpins an entire logical operation, even when that operation may be performed by multiple threads.
The “execution” in Ratpack simulates aspects of the traditional execution context by allowing registration of an error handler
that is carried
across execution segments (and therefore threads), and completion notifications
.
Importantly, it also serializes execution segments by way of the ExecControl.promise(ratpack.func.Action)
method.
These methods are fundamentally asynchronous in that they facilitate performing operations where the result will arrive later without waiting for the result,
but are synchronous in the operations the perform are serialized and not executed concurrently or in parallel.
Crucially, this means that state that is local to an execution does not need to be thread safe.
The execution object actually underpins the Context
objects that are used when handling requests.
It is rarely used directly when request handling, except when concurrency or parallelism is required to process data via the Context.fork(ratpack.func.Action)
method.
Moreover, it provides its own error handling and completion mechanisms.
Modifier and Type | Method and Description |
---|---|
void |
addInterceptor(ExecInterceptor execInterceptor,
Action<? super Execution> continuation)
Adds an interceptor that wraps the rest of the current execution segment and all future segments of this execution.
|
ExecController |
getController()
The execution controller that this execution is associated with.
|
void |
onComplete(Runnable runnable)
Registers code to be executed when the execution completes.
|
void |
setErrorHandler(Action<? super Throwable> errorHandler)
Registers an action to occur if an exception is raised during an execution segment that is uncaught, or when asynchronous operations with no defined error handler fail.
|
register, register, registerLazy, remove
all, each, first, get, get, getAll, getAll, maybeGet, maybeGet
blocking, fork, promise, stream
void setErrorHandler(Action<? super Throwable> errorHandler)
import ratpack.launch.LaunchConfigBuilder; import ratpack.exec.Execution; import ratpack.exec.ExecController; import ratpack.func.Action; import java.util.concurrent.Callable; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; public class Example { public static void main(String[] args) throws InterruptedException { final BlockingQueue<String> queue = new LinkedBlockingQueue<>(); ExecController controller = LaunchConfigBuilder.noBaseDir().build().getExecController(); controller.start(new Action<Execution>() { public void execute(Execution execution) { execution.setErrorHandler(new Action<Throwable>() { public void execute(Throwable throwable) { try { queue.put("caught be execution error handler: " + throwable.getMessage()); } catch (Exception e) { // Important to not let the error handler throw an exception that would cause the error handler to be invoked again, // and throw another exception… resulting in a stack overflow. e.printStackTrace(); } } }); execution .blocking(new Callable<String>() { public String call() { return "foo"; } }) .then(new Action<String>() { public void execute(String value) { throw new RuntimeException(value); } }); } }); assert queue.take().equals("caught be execution error handler: foo"); } }
Before the error handler is invoked, all queued execution segments are discarded.
import ratpack.launch.LaunchConfigBuilder; import ratpack.exec.Execution; import ratpack.exec.ExecController; import ratpack.func.Action; import java.util.concurrent.Callable; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; public class Example { public static void main(String[] args) throws InterruptedException { final BlockingQueue<String> queue = new LinkedBlockingQueue<>(); ExecController controller = LaunchConfigBuilder.noBaseDir().build().getExecController(); controller.start(new Action<Execution>() { public void execute(Execution execution) { execution.setErrorHandler(new Action<Throwable>() { public void execute(Throwable throwable) { try { queue.put("caught be execution error handler: " + throwable.getMessage()); } catch (Exception e) { // Important to not let the error handler throw an exception that would cause the error handler to be invoked again, // and throw another exception… resulting in a stack overflow. e.printStackTrace(); } } }); // This blocking call will never be executed as the execution segment that queued it // will throw an exception that is uncaught execution .blocking(new Callable<String>() { public String call() { return "foo"; } }) .then(new Action<String>() { public void execute(String value) { throw new RuntimeException("will never be executed"); } }); throw new RuntimeException("after blocking call"); } }); assert queue.take().equals("caught be execution error handler: after blocking call"); } }
Note: it is generally not advisable to call this method for a request handling execution,
as Ratpack installs an error handler for such executions that delegates to Context.error(Exception)
.
It is generally used for background and forked executions.
errorHandler
- the action that should be invoked when an exception is uncaught during an execution segment.void addInterceptor(ExecInterceptor execInterceptor, Action<? super Execution> continuation) throws Exception
The given action is executed immediately (i.e. as opposed to being queued to be executed as the next execution segment). Any code executed after a call to this method in the same execution segment WILL NOT be intercepted. Therefore, it is advisable to not execute any code after calling this method in a given execution segment.
See ExecInterceptor
for example use of an interceptor.
execInterceptor
- the execution interceptor to addcontinuation
- the rest of the code to be executedException
- any thrown by continuation
ExecInterceptor
ExecController getController()
void onComplete(Runnable runnable)
An execution completes when an execution segment completes (that did not queue an async operation via (ExecControl.promise(ratpack.func.Action)
) and there are no further segments.
Multiple callbacks can be registered with this method. They will be executed in registration order.
Note: for request handling, it is generally more useful to use Context.onClose(ratpack.func.Action)
that this method for executing code
at the end of the execution as it exposes the request outcome.
runnable
- code to execute when this execution completes