From cc5a6d4d18b929760a8f1b3b86563ca3c3bf8496 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Thu, 29 Jul 2021 15:09:58 +1000 Subject: [PATCH] Fix #412 AsyncContext execute Fix #412 AsyncContext is an Executor that mutually excludes from other container invocations for the same request. --- .../java/jakarta/servlet/AsyncContext.java | 68 ++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/api/src/main/java/jakarta/servlet/AsyncContext.java b/api/src/main/java/jakarta/servlet/AsyncContext.java index 2f9620040..1e4cc6b0a 100644 --- a/api/src/main/java/jakarta/servlet/AsyncContext.java +++ b/api/src/main/java/jakarta/servlet/AsyncContext.java @@ -17,6 +17,9 @@ package jakarta.servlet; +import java.util.concurrent.CancellationException; +import java.util.concurrent.Executor; + /** * Class representing the execution context for an asynchronous operation that was initiated on a ServletRequest. * @@ -36,9 +39,17 @@ * {@link #dispatch} methods, call {@link #complete}. * * + *

+ * An AsyncContext is an {@link Executor} that provides mutual exclusion between container managed threads for the + * same request. Specifically container managed threads are used for calls to + * {@link Servlet#service(ServletRequest, ServletResponse)}, + * {@link Filter#doFilter(ServletRequest, ServletResponse, FilterChain)}, + * {@link ReadListener#onDataAvailable()}, {@link WriteListener#onWritePossible()} + * and to call the {@link Runnable}s passed to {@link AsyncContext#execute(Runnable)} and {@link AsyncContext#run(Runnable)}. + * * @since Servlet 3.0 */ -public interface AsyncContext { +public interface AsyncContext extends Executor { /** * The name of the request attribute under which the original request URI is made available to the target of a @@ -188,6 +199,11 @@ public interface AsyncContext { * within the same asynchronous cycle will result in an IllegalStateException. If startAsync is subsequently called on * the dispatched request, then any of the dispatch or {@link #complete} methods may be called. * + *

+ * This method should be called from a container managed thread associated with the same request. + * A container may log a warning the first time this method is called from a non container managed thread and + * future versions of the specification may prohibit such calls. + * * @throws IllegalStateException if one of the dispatch methods has been called and the startAsync method has not been * called during the resulting dispatch, or if {@link #complete} was called * @@ -218,6 +234,11 @@ public interface AsyncContext { *

* See {@link #dispatch()} for additional details, including error handling. * + *

+ * This method should be called from a container managed thread associated with the same request. + * A container may log a warning the first time this method is called from a non container managed thread and + * future versions of the specification may prohibit such calls. + * * @param path the path of the dispatch target, scoped to the ServletContext from which this AsyncContext was * initialized * @@ -252,6 +273,11 @@ public interface AsyncContext { *

* See {@link #dispatch()} for additional details, including error handling. * + *

+ * This method should be called from a container managed thread associated with the same request. + * A container may log a warning the first time this method is called from a non container managed thread and + * future versions of the specification may prohibit such calls. + * * @param context the ServletContext of the dispatch target * @param path the path of the dispatch target, scoped to the given ServletContext * @@ -277,6 +303,11 @@ public interface AsyncContext { * startAsync has returned to the container, then the call will not take effect (and any invocations of * {@link AsyncListener#onComplete(AsyncEvent)} will be delayed) until after the container-initiated dispatch has * returned to the container. + * + *

+ * This method should be called from a container managed thread associated with the same request. + * A container may log a warning the first time this method is called from a non container managed thread and + * future versions of the specification may prohibit such calls. */ public void complete(); @@ -284,10 +315,45 @@ public interface AsyncContext { * Causes the container to dispatch a thread, possibly from a managed thread pool, to run the specified * Runnable. The container may propagate appropriate contextual information to the Runnable. * + * @see #execute(Runnable) + * @see #run(Runnable) * @param run the asynchronous handler */ public void start(Runnable run); + /** + * Causes the container to dispatch a container managed thread, possibly from a managed thread pool, + * to run the passed Runnable. The execution will be mutually excluded from all other container + * managed threads for the same request. + *

+ * If {@link AsyncContext#complete()} is called, or the request lifecycle completes via a dispatch + * prior to the runnable being run, then it will never be run. + *

+ * The container may propagate appropriate contextual information to the Runnable. + * + * @see #start(Runnable) + * @see #run(Runnable) + * @param run the asynchronous handler + */ + @Override + public default void execute(Runnable run) { + start(() -> run(run)); + } + + /** + * Executes the given command in the calling thread, mutually excluded from all other container + * managed threads for the same request. + *

+ * The container may propagate appropriate contextual information to the Runnable. + * + * @see #start(Runnable) + * @see #execute(Runnable) + * @param run the asynchronous handler + * @throws CancellationException If {@link AsyncContext#complete()} is called, or the request + * lifecycle completes via a dispatch prior to the runnable being run + */ + public void run(Runnable run) throws CancellationException; + /** * Registers the given {@link AsyncListener} with the most recent asynchronous cycle that was started by a call to one * of the {@link ServletRequest#startAsync} methods.