/* * 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; import org.apache.tomcat.util.security.PrivilegedGetTccl; import org.apache.tomcat.util.security.PrivilegedSetTccl; /** * Manages the state transitions for async requests. * *
 * 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.
 * READ_WRITE_OP - Performing an asynchronous read or write.
 * 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.
 *
 * |-----------------»------|
 * |                       \|/
 * |   |----------«-------ERROR-----------------------------------«-------------------------------|
 * |   |      complete() /|\/|\\                                                                  |
 * |   |                  |  |  \                                                                 |
 * |   |    |-----»-------|  |   \-----------»----------|                                         |
 * |   |    |                |                          |dispatch()                               |
 * |   |    |                |                         \|/                                        |
 * |   |    |                |          |--|timeout()   |                                         |
 * |   |    |     post()     |          | \|/           |     post()                              |
 * |   |    |    |---------- | --»DISPATCHED«---------- | --------------COMPLETING«-----|         |
 * |   |    |    |           |   /|\  |                 |                 | /|\         |         |
 * |   |    |    |    |---»- | ---|   |                 |                 |--|          |         |
 * |   |    ^    ^    |      |        |startAsync()     |               timeout()       |         |
 * |   |    |    |    |       \       |                 |                               |         |
 * |   |    |    |    |        \      |                 |                               |         |
 * |   |    |    |    |         \     |                 |                               |         |
 * |   |    |    |    |          \    |                 |                               |         |
 * |  \|/   |    |    |           \  \|/     post()     |                               |         |
 * | MUST_COMPLETE-«- | ----«------STARTING--»--------- | -------------|                ^         |
 * |  /|\    /|\      |  complete()   |                 |              |     complete() |         |
 * |   |      |       |               |                 |    post()    |     /----------|         |
 * |   |      |       ^               |dispatch()       |    |-----|   |    /                     |
 * |   |      |       |               |                 |    |     |   |   /                      |
 * |   |      |       |              \|/                |    |    \|/ \|/ /       post()          |
 * |   |      |       |         MUST_DISPATCH-----«-----|    |--«--STARTED«---------«---------|   |
 * |   |      |       |              /|\   |                      / |   |                     |   |
 * |   |      |       |               |    |post()               /  |   |                     ^   |
 * ^   |      ^       |               |    |dispatched()        /   |   |asyncOperation()     |   |
 * |   |      |       ^               |    |                   /    |   |                     |   |
 * |   |      |       |               |    |    |-------------/     |   |»-READ_WRITE_OP--»---|   |
 * |   |      |       |               |    |    |    dispatch()     |            |  |  |          |
 * |   |      |       |post()         |    |    |          timeout()|            |  |  |   error()|
 * |   |      |       |dispatched()   |   \|/  \|/                  |  dispatch()|  |  |-»--------|
 * |   |      |       |---«---------- | ---DISPATCHING«-----«------ | ------«----|  |
 * |   |      |                       |     |    ^                  |               |
 * |   |      |                       |     |----|                  |               |
 * |   |      |                       |    timeout()                |               |
 * |   |      |                       |                             |               |
 * |   |      |                       |       dispatch()           \|/              |
 * |   |      |                       |-----------«-----------TIMING_OUT            |
 * |   |      |                                                 |   |               |
 * |   |      |-------«----------------------------------«------|   |               |
 * |   |                          complete()                        |               |
 * |   |                                                            |               |
 * |«- | ----«-------------------«-------------------------------«--|               |
 *     |                           error()                                          |
 *     |                                                  complete()                |
 *     |----------------------------------------------------------------------------|
 * 
*/ public class AsyncStateMachine { /** * The string manager for this package. */ private static final StringManager sm = StringManager.getManager(AsyncStateMachine.class); private static enum AsyncState { DISPATCHED (false, false, false, false, false), STARTING (true, true, false, false, true), STARTED (true, true, false, false, false), MUST_COMPLETE(true, true, true, false, false), COMPLETING (true, false, true, false, false), TIMING_OUT (true, true, false, false, false), MUST_DISPATCH(true, true, false, true, true), DISPATCHING (true, false, false, true, false), READ_WRITE_OP(true, true, false, false, true), ERROR (true, true, false, false, false); private final boolean isAsync; private final boolean isStarted; private final boolean isCompleting; private final boolean isDispatching; private final boolean pauseNonContainerThread; private AsyncState(boolean isAsync, boolean isStarted, boolean isCompleting, boolean isDispatching, boolean pauseNonContainerThread) { this.isAsync = isAsync; this.isStarted = isStarted; this.isCompleting = isCompleting; this.isDispatching = isDispatching; this.pauseNonContainerThread = pauseNonContainerThread; } public boolean isAsync() { return isAsync; } public boolean isStarted() { return isStarted; } public boolean isDispatching() { return isDispatching; } public boolean isCompleting() { return isCompleting; } public boolean getPauseNonContainerThread() { return pauseNonContainerThread; } } private volatile AsyncState state = AsyncState.DISPATCHED; private volatile long lastAsyncStart = 0; // Need this to fire listener on complete private AsyncContextCallback asyncCtxt = null; private final AbstractProcessor processor; public AsyncStateMachine(AbstractProcessor 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 boolean isAsyncError() { return state == AsyncState.ERROR; } public boolean isCompleting() { return state.isCompleting(); } /** * Obtain the time that this connection last transitioned to async * processing. * * @return The time (as returned by {@link System#currentTimeMillis()}) that * this connection last transitioned to async */ public long getLastAsyncStart() { return lastAsyncStart; } public synchronized void asyncStart(AsyncContextCallback asyncCtxt) { if (state == AsyncState.DISPATCHED) { state = AsyncState.STARTING; this.asyncCtxt = asyncCtxt; lastAsyncStart = System.currentTimeMillis(); } else { throw new IllegalStateException( sm.getString("asyncStateMachine.invalidAsyncState", "asyncStart()", state)); } } public synchronized void asyncOperation() { if (state==AsyncState.STARTED) { state = AsyncState.READ_WRITE_OP; } else { throw new IllegalStateException( sm.getString("asyncStateMachine.invalidAsyncState", "asyncOperation()", 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() { // Unpause any non-container threads that may be waiting for this // container thread to complete this method. Note because of the syncs // those non-container threads won't start back up until until this // method exits. notifyAll(); if (state == AsyncState.STARTING || state == AsyncState.READ_WRITE_OP) { 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.STARTED) { // This can occur if an async listener does a dispatch to an async // servlet during onTimeout return SocketState.LONG; } else { throw new IllegalStateException( sm.getString("asyncStateMachine.invalidAsyncState", "asyncPostProcess()", state)); } } public synchronized boolean asyncComplete() { pauseNonContainerThread(); clearNonBlockingListeners(); 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 if (state == AsyncState.READ_WRITE_OP) { 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.DISPATCHING || state == AsyncState.DISPATCHED) { // NOOP - App called complete() or dispatch() 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() { pauseNonContainerThread(); boolean doDispatch = false; if (state == AsyncState.STARTING || state == AsyncState.TIMING_OUT || state == AsyncState.ERROR) { // In these three cases processing is on a container thread so no // need to transfer processing to a new container thread state = AsyncState.MUST_DISPATCH; } else if (state == AsyncState.STARTED) { state = AsyncState.DISPATCHING; // A dispatch is always required. // If on a non-container thread, need to get back onto a container // thread to complete the processing. // If on a container thread the current request/response are not the // request/response associated with the AsyncContext so need a new // container thread to process the different request/response. doDispatch = true; } else if (state == AsyncState.READ_WRITE_OP) { state = AsyncState.DISPATCHING; // If on a container thread then the socket will be added to the // poller poller when the thread exits the // AbstractConnectionHandler.process() method so don't do a dispatch // here which would add it to the poller a second time. if (!ContainerThreadMarker.isContainerThread()) { doDispatch = true; } } else { throw new IllegalStateException( sm.getString("asyncStateMachine.invalidAsyncState", "asyncDispatch()", state)); } return doDispatch; } public synchronized void asyncDispatched() { if (state == AsyncState.DISPATCHING || state == AsyncState.MUST_DISPATCH) { state = AsyncState.DISPATCHED; } else { throw new IllegalStateException( sm.getString("asyncStateMachine.invalidAsyncState", "asyncDispatched()", state)); } } public synchronized void asyncError() { if (state == AsyncState.STARTING || state == AsyncState.DISPATCHED || state == AsyncState.TIMING_OUT || state == AsyncState.MUST_COMPLETE || state == AsyncState.READ_WRITE_OP) { clearNonBlockingListeners(); state = AsyncState.ERROR; } else { throw new IllegalStateException( sm.getString("asyncStateMachine.invalidAsyncState", "asyncError()", state)); } } public synchronized void asyncRun(Runnable runnable) { if (state == AsyncState.STARTING || state == AsyncState.STARTED || state == AsyncState.READ_WRITE_OP) { // 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 pa = new PrivilegedGetTccl(); oldCL = AccessController.doPrivileged(pa); } else { oldCL = Thread.currentThread().getContextClassLoader(); } try { if (Constants.IS_SECURITY_ENABLED) { PrivilegedAction 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 pa = new PrivilegedSetTccl( oldCL); AccessController.doPrivileged(pa); } else { Thread.currentThread().setContextClassLoader(oldCL); } } } else { throw new IllegalStateException( sm.getString("asyncStateMachine.invalidAsyncState", "asyncRun()", state)); } } public synchronized void recycle() { // Use lastAsyncStart to determine if this instance has been used since // it was last recycled. If it hasn't there is no need to recycle again // which saves the relatively expensive call to notifyAll() if (lastAsyncStart == 0) { return; } // Ensure in case of error that any non-container threads that have been // paused are unpaused. notifyAll(); asyncCtxt = null; state = AsyncState.DISPATCHED; lastAsyncStart = 0; } private void clearNonBlockingListeners() { processor.getRequest().listener = null; processor.getRequest().getResponse().listener = null; } /* * startAsync() has been called but the container thread where this was * called has not completed processing. To avoid various race conditions - * including several related to error page handling - pause this * non-container thread until the container thread has finished processing. * The non-container thread will be paused until the container thread * completes asyncPostProcess(). */ private synchronized void pauseNonContainerThread() { while (!ContainerThreadMarker.isContainerThread() && state.getPauseNonContainerThread()) { try { wait(); } catch (InterruptedException e) { // TODO Log this? } } } }