| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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 org.apache.coyote; |
| |
| import java.security.AccessController; |
| import java.security.PrivilegedAction; |
| |
| import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState; |
| import org.apache.tomcat.util.res.StringManager; |
| |
| /** |
| * Manages the state transitions for async requests. |
| * TODO: State transition diagram |
| * |
| * The internal states that are used are: |
| * DISPATCHED - Standard request. Not in Async mode. |
| * STARTING - ServletRequest.startAsync() has been called but the |
| * request in which that call was made has not finished |
| * processing. |
| * STARTED - ServletRequest.startAsync() has been called and the |
| * request in which that call was made has finished |
| * processing. |
| * MUST_COMPLETE - complete() has been called before the request in which |
| * ServletRequest.startAsync() has finished. As soon as that |
| * request finishes, the complete() will be processed. |
| * COMPLETING - The call to complete() was made once the request was in |
| * the STARTED state. May or may not be triggered by a |
| * container thread - depends if start(Runnable) was used |
| * TIMING_OUT - The async request has timed out and is waiting for a call |
| * to complete(). If that isn't made, the error state will |
| * entered. |
| * MUST_DISPATCH - dispatch() has been called before the request in which |
| * ServletRequest.startAsync() has finished. As soon as that |
| * request finishes, the dispatch() will be processed. |
| * DISPATCHING - The dispatch is being processed. |
| * ERROR - Something went wrong. |
| */ |
| public class AsyncStateMachine { |
| |
| /** |
| * The string manager for this package. |
| */ |
| private static final StringManager sm = |
| StringManager.getManager(Constants.Package); |
| |
| private static enum AsyncState { |
| DISPATCHED(false, false, false), |
| STARTING(true, true, false), |
| STARTED(true, true, false), |
| MUST_COMPLETE(true, false, false), |
| COMPLETING(true, false, false), |
| TIMING_OUT(true, false, false), |
| MUST_DISPATCH(true, false, true), |
| DISPATCHING(true, false, true), |
| ERROR(true,false,false); |
| |
| private boolean isAsync; |
| private boolean isStarted; |
| private boolean isDispatching; |
| |
| private AsyncState(boolean isAsync, boolean isStarted, |
| boolean isDispatching) { |
| this.isAsync = isAsync; |
| this.isStarted = isStarted; |
| this.isDispatching = isDispatching; |
| } |
| |
| public boolean isAsync() { |
| return this.isAsync; |
| } |
| |
| public boolean isStarted() { |
| return this.isStarted; |
| } |
| |
| public boolean isDispatching() { |
| return this.isDispatching; |
| } |
| } |
| |
| |
| private volatile AsyncState state = AsyncState.DISPATCHED; |
| // Need this to fire listener on complete |
| private AsyncContextCallback asyncCtxt = null; |
| private Processor processor; |
| |
| |
| public AsyncStateMachine(Processor processor) { |
| this.processor = processor; |
| } |
| |
| |
| public boolean isAsync() { |
| return state.isAsync(); |
| } |
| |
| public boolean isAsyncDispatching() { |
| return state.isDispatching(); |
| } |
| |
| public boolean isAsyncStarted() { |
| return state.isStarted(); |
| } |
| |
| public boolean isAsyncTimingOut() { |
| return state == AsyncState.TIMING_OUT; |
| } |
| |
| |
| public synchronized void asyncStart(AsyncContextCallback asyncCtxt) { |
| if (state == AsyncState.DISPATCHED) { |
| state = AsyncState.STARTING; |
| this.asyncCtxt = asyncCtxt; |
| } else { |
| throw new IllegalStateException( |
| sm.getString("asyncStateMachine.invalidAsyncState", |
| "asyncStart()", state)); |
| } |
| } |
| |
| /* |
| * Async has been processed. Whether or not to enter a long poll depends on |
| * current state. For example, as per SRV.2.3.3.3 can now process calls to |
| * complete() or dispatch(). |
| */ |
| public synchronized SocketState asyncPostProcess() { |
| |
| if (state == AsyncState.STARTING) { |
| state = AsyncState.STARTED; |
| return SocketState.LONG; |
| } else if (state == AsyncState.MUST_COMPLETE) { |
| asyncCtxt.fireOnComplete(); |
| state = AsyncState.DISPATCHED; |
| return SocketState.ASYNC_END; |
| } else if (state == AsyncState.COMPLETING) { |
| asyncCtxt.fireOnComplete(); |
| state = AsyncState.DISPATCHED; |
| return SocketState.ASYNC_END; |
| } else if (state == AsyncState.MUST_DISPATCH) { |
| state = AsyncState.DISPATCHING; |
| return SocketState.ASYNC_END; |
| } else if (state == AsyncState.DISPATCHING) { |
| state = AsyncState.DISPATCHED; |
| return SocketState.ASYNC_END; |
| } else if (state == AsyncState.ERROR) { |
| asyncCtxt.fireOnComplete(); |
| state = AsyncState.DISPATCHED; |
| return SocketState.ASYNC_END; |
| //} else if (state == AsyncState.DISPATCHED) { |
| // // No state change |
| // return SocketState.OPEN; |
| } else { |
| throw new IllegalStateException( |
| sm.getString("asyncStateMachine.invalidAsyncState", |
| "asyncPostProcess()", state)); |
| } |
| } |
| |
| |
| public synchronized boolean asyncComplete() { |
| boolean doComplete = false; |
| |
| if (state == AsyncState.STARTING) { |
| state = AsyncState.MUST_COMPLETE; |
| } else if (state == AsyncState.STARTED) { |
| state = AsyncState.COMPLETING; |
| doComplete = true; |
| } else if (state == AsyncState.TIMING_OUT || |
| state == AsyncState.ERROR) { |
| state = AsyncState.MUST_COMPLETE; |
| } else { |
| throw new IllegalStateException( |
| sm.getString("asyncStateMachine.invalidAsyncState", |
| "asyncComplete()", state)); |
| |
| } |
| return doComplete; |
| } |
| |
| |
| public synchronized boolean asyncTimeout() { |
| if (state == AsyncState.STARTED) { |
| state = AsyncState.TIMING_OUT; |
| return true; |
| } else if (state == AsyncState.COMPLETING || |
| state == AsyncState.DISPATCHED) { |
| // NOOP - App called complete between the the timeout firing and |
| // execution reaching this point |
| return false; |
| } else { |
| throw new IllegalStateException( |
| sm.getString("asyncStateMachine.invalidAsyncState", |
| "asyncTimeout()", state)); |
| } |
| } |
| |
| |
| public synchronized boolean asyncDispatch() { |
| boolean doDispatch = false; |
| if (state == AsyncState.STARTING) { |
| state = AsyncState.MUST_DISPATCH; |
| } else if (state == AsyncState.STARTED || |
| state == AsyncState.TIMING_OUT) { |
| state = AsyncState.DISPATCHING; |
| doDispatch = true; |
| } else { |
| throw new IllegalStateException( |
| sm.getString("asyncStateMachine.invalidAsyncState", |
| "asyncDispatch()", state)); |
| } |
| return doDispatch; |
| } |
| |
| |
| public synchronized void asyncDispatched() { |
| if (state == AsyncState.DISPATCHING) { |
| state = AsyncState.DISPATCHED; |
| } else { |
| throw new IllegalStateException( |
| sm.getString("asyncStateMachine.invalidAsyncState", |
| "asyncDispatched()", state)); |
| } |
| } |
| |
| |
| public synchronized boolean asyncError() { |
| boolean doDispatch = false; |
| if (state == AsyncState.DISPATCHED || |
| state == AsyncState.TIMING_OUT) { |
| state = AsyncState.ERROR; |
| } else { |
| throw new IllegalStateException( |
| sm.getString("asyncStateMachine.invalidAsyncState", |
| "asyncError()", state)); |
| } |
| return doDispatch; |
| } |
| |
| public synchronized void asyncRun(Runnable runnable) { |
| if (state == AsyncState.STARTING || state == AsyncState.STARTED) { |
| // Execute the runnable using a container thread from the |
| // Connector's thread pool. Use a wrapper to prevent a memory leak |
| ClassLoader oldCL; |
| if (Constants.IS_SECURITY_ENABLED) { |
| PrivilegedAction<ClassLoader> pa = new PrivilegedGetTccl(); |
| oldCL = AccessController.doPrivileged(pa); |
| } else { |
| oldCL = Thread.currentThread().getContextClassLoader(); |
| } |
| try { |
| if (Constants.IS_SECURITY_ENABLED) { |
| PrivilegedAction<Void> pa = new PrivilegedSetTccl( |
| this.getClass().getClassLoader()); |
| AccessController.doPrivileged(pa); |
| } else { |
| Thread.currentThread().setContextClassLoader( |
| this.getClass().getClassLoader()); |
| } |
| |
| processor.getExecutor().execute(runnable); |
| } finally { |
| if (Constants.IS_SECURITY_ENABLED) { |
| PrivilegedAction<Void> pa = new PrivilegedSetTccl( |
| oldCL); |
| AccessController.doPrivileged(pa); |
| } else { |
| Thread.currentThread().setContextClassLoader(oldCL); |
| } |
| } |
| } else { |
| throw new IllegalStateException( |
| sm.getString("asyncStateMachine.invalidAsyncState", |
| "asyncRun()", state)); |
| } |
| |
| } |
| |
| |
| public void recycle() { |
| asyncCtxt = null; |
| state = AsyncState.DISPATCHED; |
| } |
| |
| |
| private static class PrivilegedSetTccl implements PrivilegedAction<Void> { |
| |
| private ClassLoader cl; |
| |
| PrivilegedSetTccl(ClassLoader cl) { |
| this.cl = cl; |
| } |
| |
| @Override |
| public Void run() { |
| Thread.currentThread().setContextClassLoader(cl); |
| return null; |
| } |
| } |
| |
| private static class PrivilegedGetTccl |
| implements PrivilegedAction<ClassLoader> { |
| |
| @Override |
| public ClassLoader run() { |
| return Thread.currentThread().getContextClassLoader(); |
| } |
| } |
| } |