ratpack.exec.Execution Maven / Gradle / Ivy
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ratpack.exec;
import com.google.common.reflect.TypeToken;
import io.netty.channel.EventLoop;
import ratpack.exec.internal.DefaultExecution;
import ratpack.exec.internal.ExecThreadBinding;
import ratpack.func.Action;
import ratpack.func.Block;
import ratpack.registry.MutableRegistry;
import java.time.Duration;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
/**
* A logical operation, such as servicing a request, that may be comprised of non contiguous units of work.
*
* In a synchronous environment, a logical operation is typically given exclusive access to a thread for its duration.
* The use of thread local variables for operation global state and try/catch as a global error handling strategy rely on this.
* The execution construct provides mechanisms to emulate constructs of synchronous programming that cannot be achieved the same way in asynchronous programming.
*
* Almost all work that occurs as part of a running Ratpack application happens during an execution.
* Ratpack APIs such as {@link Promise}, {@link Blocking} etc. can only be used within an execution.
* Request processing is always within an execution.
* When initiating other work (e.g. background processing), an execution can be created via {@link Execution#fork()}
*
* The term “execution segment” (sometimes just “segment”) is used to refer to a unit of work within an execution.
* An execution segment has exclusive access to a thread.
* All executions start with the segment given to the {@link ExecStarter#start(Action)} method.
* If the initial execution segment does not use any asynchronous APIs, the execution will be comprised of that single segment.
* When an asynchronous API is used, via {@link Promise#async(Upstream)}, the resumption of work when the result becomes available is within a new execution segment.
* During any execution segment, the {@link Execution#current()} method will return the current execution, giving global access to the execution object.
*
* Segments of an execution are never executed concurrently.
*
*
Execution state (i.e. simulating thread locals)
*
* Each execution is a {@link MutableRegistry}.
* Objects can be added to this registry and then later retrieved at any time during the execution.
* The registry storage can be leveraged via an {@link ExecInterceptor} to manage thread local storage for execution segments.
*
*
Error handling
*
* When starting an execution, a global error handler can be specified via {@link ExecStarter#onError(Action)}.
* The default error handler simply logs the error to a logger named {@code ratpack.exec.Execution}.
*
* The error handler for request processing executions forwards the exception to {@link ratpack.handling.Context#error(Throwable)}.
*
*
Cleanup
*
* The {@link #onComplete(AutoCloseable)} method can be used to register actions to invoke or resources to close when the execution completes.
*
* @see ExecInterceptor
* @see ExecInitializer
* @see ExecController
* @see Promise
*/
public interface Execution extends MutableRegistry {
/**
* Provides the currently executing execution.
*
* @return the currently executing execution
* @throws UnmanagedThreadException if called from outside of an execution
* @see #currentOpt()
*/
static Execution current() throws UnmanagedThreadException {
return DefaultExecution.require();
}
/**
* Provides the currently executing execution, if any.
*
* @return the currently executing execution
* @see #current()
* @since 1.5
*/
static Optional currentOpt() {
return Optional.ofNullable(DefaultExecution.get());
}
/**
* Used to create a new execution.
*
* This method obtains the thread bound {@link ExecController} and simply calls {@link ExecController#fork()}.
*
* @return an execution starter
* @throws UnmanagedThreadException if there is no thread bound execution controller (i.e. this was called on a thread that is not managed by the Ratpack application)
*/
static ExecStarter fork() throws UnmanagedThreadException {
return ExecController.require().fork();
}
/**
* Whether there is currently an active bound execution.
*
* When {@code true}, {@link #current()} will return an execution.
* When completing/closing an execution (e.g. {@link #onComplete(AutoCloseable)}),
* this will return {@code false} but {@link #current()} will still return an execution.
*
* @return whether there is currently an active bound execution
* @since 1.5
*/
static boolean isActive() {
DefaultExecution execution = DefaultExecution.get();
return execution != null && !execution.isComplete();
}
/**
* Whether the current thread is a thread that is managed by Ratpack.
*
* @return whether the current thread is a thread that is managed by Ratpack
*/
static boolean isManagedThread() {
return ExecThreadBinding.get() != null;
}
/**
* Whether the current thread is a Ratpack compute thread.
*
* @return whether the current thread is a Ratpack compute thread
*/
static boolean isComputeThread() {
return ExecThreadBinding.maybeGet().map(ExecThreadBinding::isCompute).orElse(false);
}
/**
* Whether the current thread is a Ratpack blocking thread.
*
* @return whether the current thread is a Ratpack blocking thread
*/
static boolean isBlockingThread() {
return ExecThreadBinding.maybeGet().map(threadBinding -> !threadBinding.isCompute()).orElse(false);
}
/**
* The execution controller that this execution is associated with.
*
* @return the execution controller that this execution is associated with
*/
ExecController getController();
/**
* The specific event loop that this execution is using for compute operations.
*
* When integrating with asynchronous API that allows an executor to be specified that should be used to
* schedule the receipt of the value, use this executor.
*
* @return the event loop used by this execution
*/
EventLoop getEventLoop();
/**
* A reference to this execution.
*
* @return a reference to this execution
* @since 1.6
*/
ExecutionRef getRef();
/**
* A ref to the execution that forked this execution.
*
* @throws IllegalStateException if this is a top level exception with no parent
* @return a ref to the execution that forked this execution
* @see #maybeParent()
* @since 1.6
*/
default ExecutionRef getParent() {
return getRef().getParent();
}
/**
* A ref to the execution that forked this execution, if it has a parent.
*
* @return a ref to the execution that forked this execution
* @since 1.6
*/
default Optional maybeParent() {
return getRef().maybeParent();
}
/**
* Registers a closeable that will be closed when the execution completes.
*
* Where possible, care should be taken to have the given closeable not throw exceptions.
* Any that are thrown will be logged and ignored.
*
* @param closeable the resource to close when the execution completes
*/
void onComplete(AutoCloseable closeable);
/**
* Indicates whether the execution has completed or not.
*
* Will return {@code true} if called during an {@link #onComplete(AutoCloseable)} or {@link ExecSpec#onComplete(Action)} callback.
*
* @return whether the execution has completed or not
* @since 1.5
*/
boolean isComplete();
/**
* {@inheritDoc}
*/
@Override
default Execution add(Class type, O object) {
MutableRegistry.super.add(type, object);
return this;
}
/**
* {@inheritDoc}
*/
@Override
default Execution add(TypeToken type, O object) {
MutableRegistry.super.add(type, object);
return this;
}
/**
* {@inheritDoc}
*/
@Override
default Execution add(Object object) {
MutableRegistry.super.add(object);
return this;
}
/**
* {@inheritDoc}
*/
@Override
default Execution addLazy(Class type, Supplier supplier) {
MutableRegistry.super.addLazy(type, supplier);
return this;
}
/**
* {@inheritDoc}
*/
@Override
Execution addLazy(TypeToken type, Supplier supplier);
/**
* Adds an interceptor that wraps the rest of the current execution segment and all future segments of this execution.
*
* The given action is executed immediately.
* 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 {@link ExecInterceptor} for example use of an interceptor.
*
* It is generally preferable to register the interceptor in the server registry, or execution registry when starting, than using this method.
* That way, the interceptor can interceptor all of the execution.
*
* @param execInterceptor the execution interceptor to add
* @param continuation the rest of the code to be executed
* @throws Exception any thrown by {@code continuation}
* @see ExecInterceptor
*/
void addInterceptor(ExecInterceptor execInterceptor, Block continuation) throws Exception;
/**
* Pauses this execution for the given duration.
*
* Unlike {@link Thread#sleep(long)}, this method does not block the thread.
* The thread will be relinquished for use by other executions.
*
* The given block will be invoked after the duration has passed.
* The duration must be non-negative.
*
* @param duration the duration this execution should sleep for
* @param onWake the code to resume with upon awaking
* @since 1.5
*/
static void sleep(Duration duration, Block onWake) {
sleep(duration).then(onWake);
}
/**
* Creates a sleep operation.
*
* Unlike {@link Thread#sleep(long)}, this method does not block the thread.
* The thread will be relinquished for use by other executions.
*
* The given block will be invoked after the duration has passed.
* The duration must be non-negative.
*
* @param duration the duration this execution should sleep for
* @since 1.5
*/
static Operation sleep(Duration duration) {
if (duration.isNegative()) {
throw new IllegalArgumentException("Sleep duration must be non negative (value: " + duration + ")");
} else {
if (duration.isZero()) {
return Operation.noop();
} else {
return Promise.async(down -> {
try {
current().getEventLoop().schedule(() -> down.success(null), duration.toNanos(), TimeUnit.NANOSECONDS);
} catch (Throwable e) {
down.error(e);
}
}).operation();
}
}
}
}