1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.coyote;
18
19 import java.security.AccessController;
20 import java.security.PrivilegedAction;
21 import java.util.concurrent.atomic.AtomicLong;
22
23 import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
24 import org.apache.tomcat.util.res.StringManager;
25 import org.apache.tomcat.util.security.PrivilegedGetTccl;
26 import org.apache.tomcat.util.security.PrivilegedSetTccl;
27
28 /**
29 * Manages the state transitions for async requests.
30 *
31 * <pre>
32 * The internal states that are used are:
33 * DISPATCHED - Standard request. Not in Async mode.
34 * STARTING - ServletRequest.startAsync() has been called from
35 * Servlet.service() but service() has not exited.
36 * STARTED - ServletRequest.startAsync() has been called from
37 * Servlet.service() and service() has exited.
38 * READ_WRITE_OP - Performing an asynchronous read or write.
39 * MUST_COMPLETE - ServletRequest.startAsync() followed by complete() have
40 * been called during a single Servlet.service() method. The
41 * complete() will be processed as soon as Servlet.service()
42 * exits.
43 * COMPLETE_PENDING - ServletRequest.startAsync() has been called from
44 * Servlet.service() but, before service() exited, complete()
45 * was called from another thread. The complete() will
46 * be processed as soon as Servlet.service() exits.
47 * COMPLETING - The call to complete() was made once the request was in
48 * the STARTED state.
49 * TIMING_OUT - The async request has timed out and is waiting for a call
50 * to complete() or dispatch(). If that isn't made, the error
51 * state will be entered.
52 * MUST_DISPATCH - ServletRequest.startAsync() followed by dispatch() have
53 * been called during a single Servlet.service() method. The
54 * dispatch() will be processed as soon as Servlet.service()
55 * exits.
56 * DISPATCH_PENDING - ServletRequest.startAsync() has been called from
57 * Servlet.service() but, before service() exited, dispatch()
58 * was called from another thread. The dispatch() will
59 * be processed as soon as Servlet.service() exits.
60 * DISPATCHING - The dispatch is being processed.
61 * MUST_ERROR - ServletRequest.startAsync() has been called from
62 * Servlet.service() but, before service() exited, an I/O
63 * error occured on another thread. The container will
64 * perform the necessary error handling when
65 * Servlet.service() exits.
66 * ERROR - Something went wrong.
67 *
68 *
69 * The valid state transitions are:
70 *
71 * post() dispatched()
72 * |-------»------------------»---------| |-------«-----------------------«-----|
73 * | | | |
74 * | | | post() |
75 * | post() \|/ \|/ dispatched() |
76 * | |-----»----------------»DISPATCHED«-------------«-------------| |
77 * | | | /|\ | | |
78 * | | startAsync()| |--|timeout() | |
79 * ^ | | | |
80 * | | complete() | dispatch() ^ |
81 * | | |--«---------------«-- | ---«--MUST_ERROR--»-----| | |
82 * | | | | /|\ | | |
83 * | ^ | | | | | |
84 * | | | | /-----|error() | | |
85 * | | | | / | ^ |
86 * | | \|/ ST-complete() \|/ / ST-dispatch() \|/ | |
87 * | MUST_COMPLETE«--------«--------STARTING--------»---------»MUST_DISPATCH |
88 * | / | \ |
89 * | / | \ |
90 * | OT-complete() / | \ OT-dispatch() |
91 * | COMPLETE_PENDING«------«------/ | \-------»---------»DISPATCH_PENDING |
92 * | | | | |
93 * | post()| timeout() post()| post() post()| timeout() |
94 * | | |--| | |--| | |--| |
95 * | \|/ \|/ | complete() \|/\|/ | dispatch() \|/ \|/ | |
96 * |--«-----COMPLETING«--------«----------STARTED--------»---------»DISPATCHING----|
97 * /|\ /|\ /|\ | /|\ \ /|\ /|\ /|\
98 * | | | | \ \asyncOperation() | | |
99 * | | | timeout()| \ \ | | |
100 * | | | | \ \ | | |
101 * | | | | \ \ | | |
102 * | | | | \ \ | | |
103 * | | | | \ \ | | |
104 * | | | | post()\ \ dispatch()| | |
105 * | | | complete() | \ \|/ | | |
106 * | | |---«------------«-- | --«---READ_WRITE----»----| | |
107 * | | | | |
108 * | | complete() \|/ dispatch() | |
109 * | |------------«-------TIMING_OUT--------»----------------| |
110 * | |
111 * | complete() dispatch() |
112 * |---------------«-----------ERROR--------------»-----------------|
113 *
114 *
115 * Notes: * For clarity, the transitions to ERROR which are valid from every state apart from
116 * STARTING are not shown.
117 * * All transitions may happen on either the Servlet.service() thread (ST) or on any
118 * other thread (OT) unless explicitly marked.
119 * </pre>
120 */
121 class AsyncStateMachine {
122
123 /**
124 * The string manager for this package.
125 */
126 private static final StringManager sm = StringManager.getManager(AsyncStateMachine.class);
127
128 private enum AsyncState {
129 DISPATCHED (false, false, false, false),
130 STARTING (true, true, false, false),
131 STARTED (true, true, false, false),
132 MUST_COMPLETE (true, true, true, false),
133 COMPLETE_PENDING(true, true, false, false),
134 COMPLETING (true, false, true, false),
135 TIMING_OUT (true, true, false, false),
136 MUST_DISPATCH (true, true, false, true),
137 DISPATCH_PENDING(true, true, false, false),
138 DISPATCHING (true, false, false, true),
139 READ_WRITE_OP (true, true, false, false),
140 MUST_ERROR (true, true, false, false),
141 ERROR (true, true, false, false);
142
143 private final boolean isAsync;
144 private final boolean isStarted;
145 private final boolean isCompleting;
146 private final boolean isDispatching;
147
148 private AsyncState(boolean isAsync, boolean isStarted, boolean isCompleting,
149 boolean isDispatching) {
150 this.isAsync = isAsync;
151 this.isStarted = isStarted;
152 this.isCompleting = isCompleting;
153 this.isDispatching = isDispatching;
154 }
155
156 boolean isAsync() {
157 return isAsync;
158 }
159
160 boolean isStarted() {
161 return isStarted;
162 }
163
164 boolean isDispatching() {
165 return isDispatching;
166 }
167
168 boolean isCompleting() {
169 return isCompleting;
170 }
171 }
172
173
174 private volatile AsyncState state = AsyncState.DISPATCHED;
175 private volatile long lastAsyncStart = 0;
176 /*
177 * Tracks the current generation of async processing for this state machine.
178 * The generation is incremented every time async processing is started. The
179 * primary purpose of this is to enable Tomcat to detect and prevent
180 * attempts to process an event for a previous generation with the current
181 * generation as processing such an event usually ends badly:
182 * e.g. CVE-2018-8037.
183 */
184 private final AtomicLong generation = new AtomicLong(0);
185 // Need this to fire listener on complete
186 private AsyncContextCallback asyncCtxt = null;
187 private final AbstractProcessor processor;
188
189
190 AsyncStateMachine(AbstractProcessor processor) {
191 this.processor = processor;
192 }
193
194
195 boolean isAsync() {
196 return state.isAsync();
197 }
198
199 boolean isAsyncDispatching() {
200 return state.isDispatching();
201 }
202
203 boolean isAsyncStarted() {
204 return state.isStarted();
205 }
206
207 boolean isAsyncTimingOut() {
208 return state == AsyncState.TIMING_OUT;
209 }
210
211 boolean isAsyncError() {
212 return state == AsyncState.ERROR;
213 }
214
215 boolean isCompleting() {
216 return state.isCompleting();
217 }
218
219 /**
220 * Obtain the time that this connection last transitioned to async
221 * processing.
222 *
223 * @return The time (as returned by {@link System#currentTimeMillis()}) that
224 * this connection last transitioned to async
225 */
226 long getLastAsyncStart() {
227 return lastAsyncStart;
228 }
229
230 long getCurrentGeneration() {
231 return generation.get();
232 }
233
234 synchronized void asyncStart(AsyncContextCallback asyncCtxt) {
235 if (state == AsyncState.DISPATCHED) {
236 generation.incrementAndGet();
237 state = AsyncState.STARTING;
238 this.asyncCtxt = asyncCtxt;
239 lastAsyncStart = System.currentTimeMillis();
240 } else {
241 throw new IllegalStateException(
242 sm.getString("asyncStateMachine.invalidAsyncState",
243 "asyncStart()", state));
244 }
245 }
246
247 synchronized void asyncOperation() {
248 if (state==AsyncState.STARTED) {
249 state = AsyncState.READ_WRITE_OP;
250 } else {
251 throw new IllegalStateException(
252 sm.getString("asyncStateMachine.invalidAsyncState",
253 "asyncOperation()", state));
254 }
255 }
256
257 /*
258 * Async has been processed. Whether or not to enter a long poll depends on
259 * current state. For example, as per SRV.2.3.3.3 can now process calls to
260 * complete() or dispatch().
261 */
262 synchronized SocketState asyncPostProcess() {
263 if (state == AsyncState.COMPLETE_PENDING) {
264 clearNonBlockingListeners();
265 state = AsyncState.COMPLETING;
266 return SocketState.ASYNC_END;
267 } else if (state == AsyncState.DISPATCH_PENDING) {
268 clearNonBlockingListeners();
269 state = AsyncState.DISPATCHING;
270 return SocketState.ASYNC_END;
271 } else if (state == AsyncState.STARTING || state == AsyncState.READ_WRITE_OP) {
272 state = AsyncState.STARTED;
273 return SocketState.LONG;
274 } else if (state == AsyncState.MUST_COMPLETE || state == AsyncState.COMPLETING) {
275 asyncCtxt.fireOnComplete();
276 state = AsyncState.DISPATCHED;
277 return SocketState.ASYNC_END;
278 } else if (state == AsyncState.MUST_DISPATCH) {
279 state = AsyncState.DISPATCHING;
280 return SocketState.ASYNC_END;
281 } else if (state == AsyncState.DISPATCHING) {
282 state = AsyncState.DISPATCHED;
283 return SocketState.ASYNC_END;
284 } else if (state == AsyncState.STARTED) {
285 // This can occur if an async listener does a dispatch to an async
286 // servlet during onTimeout
287 return SocketState.LONG;
288 } else {
289 throw new IllegalStateException(
290 sm.getString("asyncStateMachine.invalidAsyncState",
291 "asyncPostProcess()", state));
292 }
293 }
294
295
296 synchronized boolean asyncComplete() {
297 if (!ContainerThreadMarker.isContainerThread() && state == AsyncState.STARTING) {
298 state = AsyncState.COMPLETE_PENDING;
299 return false;
300 }
301
302 clearNonBlockingListeners();
303 boolean triggerDispatch = false;
304 if (state == AsyncState.STARTING || state == AsyncState.MUST_ERROR) {
305 // Processing is on a container thread so no need to transfer
306 // processing to a new container thread
307 state = AsyncState.MUST_COMPLETE;
308 } else if (state == AsyncState.STARTED) {
309 state = AsyncState.COMPLETING;
310 // A dispatch to a container thread is always required.
311 // If on a non-container thread, need to get back onto a container
312 // thread to complete the processing.
313 // If on a container thread the current request/response are not the
314 // request/response associated with the AsyncContext so need a new
315 // container thread to process the different request/response.
316 triggerDispatch = true;
317 } else if (state == AsyncState.READ_WRITE_OP || state == AsyncState.TIMING_OUT ||
318 state == AsyncState.ERROR) {
319 // Read/write operations can happen on or off a container thread but
320 // while in this state the call to listener that triggers the
321 // read/write will be in progress on a container thread.
322 // Processing of timeouts and errors can happen on or off a
323 // container thread (on is much more likely) but while in this state
324 // the call that triggers the timeout will be in progress on a
325 // container thread.
326 // The socket will be added to the poller when the container thread
327 // exits the AbstractConnectionHandler.process() method so don't do
328 // a dispatch here which would add it to the poller a second time.
329 state = AsyncState.COMPLETING;
330 } else {
331 throw new IllegalStateException(
332 sm.getString("asyncStateMachine.invalidAsyncState",
333 "asyncComplete()", state));
334 }
335 return triggerDispatch;
336 }
337
338
339 synchronized boolean asyncTimeout() {
340 if (state == AsyncState.STARTED) {
341 state = AsyncState.TIMING_OUT;
342 return true;
343 } else if (state == AsyncState.COMPLETING ||
344 state == AsyncState.DISPATCHING ||
345 state == AsyncState.DISPATCHED) {
346 // NOOP - App called complete() or dispatch() between the the
347 // timeout firing and execution reaching this point
348 return false;
349 } else {
350 throw new IllegalStateException(
351 sm.getString("asyncStateMachine.invalidAsyncState",
352 "asyncTimeout()", state));
353 }
354 }
355
356
357 synchronized boolean asyncDispatch() {
358 if (!ContainerThreadMarker.isContainerThread() && state == AsyncState.STARTING) {
359 state = AsyncState.DISPATCH_PENDING;
360 return false;
361 }
362
363 clearNonBlockingListeners();
364 boolean triggerDispatch = false;
365 if (state == AsyncState.STARTING || state == AsyncState.MUST_ERROR) {
366 // Processing is on a container thread so no need to transfer
367 // processing to a new container thread
368 state = AsyncState.MUST_DISPATCH;
369 } else if (state == AsyncState.STARTED) {
370 state = AsyncState.DISPATCHING;
371 // A dispatch to a container thread is always required.
372 // If on a non-container thread, need to get back onto a container
373 // thread to complete the processing.
374 // If on a container thread the current request/response are not the
375 // request/response associated with the AsyncContext so need a new
376 // container thread to process the different request/response.
377 triggerDispatch = true;
378 } else if (state == AsyncState.READ_WRITE_OP || state == AsyncState.TIMING_OUT ||
379 state == AsyncState.ERROR) {
380 // Read/write operations can happen on or off a container thread but
381 // while in this state the call to listener that triggers the
382 // read/write will be in progress on a container thread.
383 // Processing of timeouts and errors can happen on or off a
384 // container thread (on is much more likely) but while in this state
385 // the call that triggers the timeout will be in progress on a
386 // container thread.
387 // The socket will be added to the poller when the container thread
388 // exits the AbstractConnectionHandler.process() method so don't do
389 // a dispatch here which would add it to the poller a second time.
390 state = AsyncState.DISPATCHING;
391 } else {
392 throw new IllegalStateException(
393 sm.getString("asyncStateMachine.invalidAsyncState",
394 "asyncDispatch()", state));
395 }
396 return triggerDispatch;
397 }
398
399
400 synchronized void asyncDispatched() {
401 if (state == AsyncState.DISPATCHING ||
402 state == AsyncState.MUST_DISPATCH) {
403 state = AsyncState.DISPATCHED;
404 } else {
405 throw new IllegalStateException(
406 sm.getString("asyncStateMachine.invalidAsyncState",
407 "asyncDispatched()", state));
408 }
409 }
410
411
412 synchronized boolean asyncError() {
413 clearNonBlockingListeners();
414 if (state == AsyncState.STARTING) {
415 state = AsyncState.MUST_ERROR;
416 } else {
417 state = AsyncState.ERROR;
418 }
419 return !ContainerThreadMarker.isContainerThread();
420 }
421
422
423 synchronized void asyncRun(Runnable runnable) {
424 if (state == AsyncState.STARTING || state == AsyncState.STARTED ||
425 state == AsyncState.READ_WRITE_OP) {
426 // Execute the runnable using a container thread from the
427 // Connector's thread pool. Use a wrapper to prevent a memory leak
428 ClassLoader oldCL;
429 if (Constants.IS_SECURITY_ENABLED) {
430 PrivilegedAction<ClassLoader> pa = new PrivilegedGetTccl();
431 oldCL = AccessController.doPrivileged(pa);
432 } else {
433 oldCL = Thread.currentThread().getContextClassLoader();
434 }
435 try {
436 if (Constants.IS_SECURITY_ENABLED) {
437 PrivilegedAction<Void> pa = new PrivilegedSetTccl(
438 this.getClass().getClassLoader());
439 AccessController.doPrivileged(pa);
440 } else {
441 Thread.currentThread().setContextClassLoader(
442 this.getClass().getClassLoader());
443 }
444
445 processor.execute(runnable);
446 } finally {
447 if (Constants.IS_SECURITY_ENABLED) {
448 PrivilegedAction<Void> pa = new PrivilegedSetTccl(
449 oldCL);
450 AccessController.doPrivileged(pa);
451 } else {
452 Thread.currentThread().setContextClassLoader(oldCL);
453 }
454 }
455 } else {
456 throw new IllegalStateException(
457 sm.getString("asyncStateMachine.invalidAsyncState",
458 "asyncRun()", state));
459 }
460
461 }
462
463
464 synchronized boolean isAvailable() {
465 if (asyncCtxt == null) {
466 // Async processing has probably been completed in another thread.
467 // Trigger a timeout to make sure the Processor is cleaned up.
468 return false;
469 }
470 return asyncCtxt.isAvailable();
471 }
472
473
474 synchronized void recycle() {
475 // Use lastAsyncStart to determine if this instance has been used since
476 // it was last recycled. If it hasn't there is no need to recycle again
477 // which saves the relatively expensive call to notifyAll()
478 if (lastAsyncStart == 0) {
479 return;
480 }
481 // Ensure in case of error that any non-container threads that have been
482 // paused are unpaused.
483 notifyAll();
484 asyncCtxt = null;
485 state = AsyncState.DISPATCHED;
486 lastAsyncStart = 0;
487 }
488
489
490 private void clearNonBlockingListeners() {
491 processor.getRequest().listener = null;
492 processor.getRequest().getResponse().listener = null;
493 }
494 }
495