Skip to content

Commit 6e85dd8

Browse files
committed
Polish async request processing
This change fixes a cyclical package dependency. The change also improves the implementation of WebAsyncManager.hasConcurrentResult() following the resolution of Apache issue id=53632 and the release of Apache Tomcat 7.0.30 that contains the fix.
1 parent 988f376 commit 6e85dd8

File tree

14 files changed

+119
-113
lines changed

14 files changed

+119
-113
lines changed

spring-orm/src/main/java/org/springframework/orm/hibernate3/support/OpenSessionInViewInterceptor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@
2424
import org.springframework.orm.hibernate3.SessionHolder;
2525
import org.springframework.transaction.support.TransactionSynchronizationManager;
2626
import org.springframework.ui.ModelMap;
27+
import org.springframework.web.context.request.AsyncWebRequestInterceptor;
2728
import org.springframework.web.context.request.WebRequest;
28-
import org.springframework.web.context.request.async.AsyncWebRequestInterceptor;
2929
import org.springframework.web.context.request.async.AsyncWebUtils;
3030
import org.springframework.web.context.request.async.WebAsyncManager;
3131
import org.springframework.web.context.request.async.WebAsyncManager.WebAsyncThreadInitializer;

spring-orm/src/main/java/org/springframework/orm/hibernate4/support/OpenSessionInViewInterceptor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@
2828
import org.springframework.orm.hibernate4.SessionHolder;
2929
import org.springframework.transaction.support.TransactionSynchronizationManager;
3030
import org.springframework.ui.ModelMap;
31+
import org.springframework.web.context.request.AsyncWebRequestInterceptor;
3132
import org.springframework.web.context.request.WebRequest;
32-
import org.springframework.web.context.request.async.AsyncWebRequestInterceptor;
3333
import org.springframework.web.context.request.async.AsyncWebUtils;
3434
import org.springframework.web.context.request.async.WebAsyncManager;
3535
import org.springframework.web.context.request.async.WebAsyncManager.WebAsyncThreadInitializer;

spring-orm/src/main/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewInterceptor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@
2626
import org.springframework.orm.jpa.EntityManagerHolder;
2727
import org.springframework.transaction.support.TransactionSynchronizationManager;
2828
import org.springframework.ui.ModelMap;
29+
import org.springframework.web.context.request.AsyncWebRequestInterceptor;
2930
import org.springframework.web.context.request.WebRequest;
30-
import org.springframework.web.context.request.async.AsyncWebRequestInterceptor;
3131
import org.springframework.web.context.request.async.AsyncWebUtils;
3232
import org.springframework.web.context.request.async.WebAsyncManager;
3333
import org.springframework.web.context.request.async.WebAsyncManager.WebAsyncThreadInitializer;

spring-orm/src/test/java/org/springframework/orm/hibernate3/support/OpenSessionInViewTests.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717
package org.springframework.orm.hibernate3.support;
1818

1919
import static org.easymock.EasyMock.anyObject;
20+
import static org.easymock.EasyMock.createMock;
2021
import static org.easymock.EasyMock.createStrictMock;
2122
import static org.easymock.EasyMock.expect;
22-
import static org.easymock.EasyMock.expectLastCall;
2323
import static org.easymock.EasyMock.replay;
2424
import static org.easymock.EasyMock.reset;
2525
import static org.easymock.EasyMock.verify;
@@ -196,6 +196,10 @@ public String call() throws Exception {
196196

197197
// Async dispatch thread
198198

199+
reset(asyncWebRequest);
200+
expect(asyncWebRequest.isDispatched()).andReturn(true);
201+
replay(asyncWebRequest);
202+
199203
interceptor.preHandle(this.webRequest);
200204
assertTrue("Session not bound to async thread", TransactionSynchronizationManager.hasResource(sf));
201205

@@ -488,11 +492,11 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo
488492
}
489493
};
490494

491-
AsyncWebRequest asyncWebRequest = createStrictMock(AsyncWebRequest.class);
495+
AsyncWebRequest asyncWebRequest = createMock(AsyncWebRequest.class);
492496
asyncWebRequest.addCompletionHandler((Runnable) anyObject());
493497
asyncWebRequest.startAsync();
494-
expect(asyncWebRequest.isAsyncStarted()).andReturn(true);
495-
expectLastCall().anyTimes();
498+
expect(asyncWebRequest.isAsyncStarted()).andReturn(true).anyTimes();
499+
expect(asyncWebRequest.isDispatched()).andReturn(false).anyTimes();
496500
replay(asyncWebRequest);
497501

498502
WebAsyncManager asyncManager = AsyncWebUtils.getAsyncManager(this.request);
@@ -520,6 +524,7 @@ public String call() throws Exception {
520524

521525
expect(session.close()).andReturn(null);
522526
expect(asyncWebRequest.isAsyncStarted()).andReturn(false).anyTimes();
527+
expect(asyncWebRequest.isDispatched()).andReturn(true).anyTimes();
523528

524529
replay(sf);
525530
replay(session);

spring-orm/src/test/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewTests.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@
2424
import static org.easymock.EasyMock.replay;
2525
import static org.easymock.EasyMock.reset;
2626
import static org.easymock.EasyMock.verify;
27-
import static org.junit.Assert.assertEquals;
28-
import static org.junit.Assert.assertFalse;
2927

3028
import java.io.IOException;
3129
import java.util.concurrent.Callable;
@@ -161,22 +159,23 @@ public void testOpenEntityManagerInViewInterceptorAsyncScenario() throws Excepti
161159

162160
WebAsyncManager asyncManager = AsyncWebUtils.getAsyncManager(webRequest);
163161
asyncManager.setAsyncWebRequest(asyncWebRequest);
164-
165162
asyncManager.startCallableProcessing(new Callable<String>() {
166163
public String call() throws Exception {
167164
return "anything";
168165
}
169166
});
170167

171168
verify(asyncWebRequest);
172-
reset(asyncWebRequest);
173-
replay(asyncWebRequest);
174169

175170
interceptor.afterConcurrentHandlingStarted(webRequest);
176171
assertFalse(TransactionSynchronizationManager.hasResource(factory));
177172

178173
// Async dispatch thread
179174

175+
reset(asyncWebRequest);
176+
expect(asyncWebRequest.isDispatched()).andReturn(true);
177+
replay(asyncWebRequest);
178+
180179
reset(manager, factory);
181180
replay(manager, factory);
182181

@@ -345,11 +344,11 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo
345344

346345
FilterChain filterChain3 = new PassThroughFilterChain(filter2, filterChain2);
347346

348-
AsyncWebRequest asyncWebRequest = createStrictMock(AsyncWebRequest.class);
347+
AsyncWebRequest asyncWebRequest = createMock(AsyncWebRequest.class);
349348
asyncWebRequest.addCompletionHandler((Runnable) anyObject());
350349
asyncWebRequest.startAsync();
351-
expect(asyncWebRequest.isAsyncStarted()).andReturn(true);
352-
expectLastCall().anyTimes();
350+
expect(asyncWebRequest.isAsyncStarted()).andReturn(true).anyTimes();
351+
expect(asyncWebRequest.isDispatched()).andReturn(false).anyTimes();
353352
replay(asyncWebRequest);
354353

355354
WebAsyncManager asyncManager = AsyncWebUtils.getAsyncManager(request);
@@ -373,6 +372,7 @@ public String call() throws Exception {
373372

374373
reset(asyncWebRequest);
375374
expect(asyncWebRequest.isAsyncStarted()).andReturn(false).anyTimes();
375+
expect(asyncWebRequest.isDispatched()).andReturn(true).anyTimes();
376376
replay(asyncWebRequest);
377377

378378
assertFalse(TransactionSynchronizationManager.hasResource(factory));
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright 2002-2012 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.web.context.request;
17+
18+
/**
19+
* Extends {@code WebRequestInterceptor} with a callback method invoked during
20+
* asynchronous request handling.
21+
*
22+
* <p>When a handler starts asynchronous request handling, the DispatcherServlet
23+
* exits without invoking {@code postHandle} and {@code afterCompletion}, as it
24+
* normally does, since the results of request handling (e.g. ModelAndView) are
25+
* not available in the current thread and handling is not yet complete.
26+
* In such scenarios, the {@link #afterConcurrentHandlingStarted(WebRequest)}
27+
* method is invoked instead allowing implementations to perform tasks such as
28+
* cleaning up thread bound attributes.
29+
*
30+
* <p>When asynchronous handling completes, the request is dispatched to the
31+
* container for further processing. At this stage the DispatcherServlet invokes
32+
* {@code preHandle}, {@code postHandle} and {@code afterCompletion} as usual.
33+
*
34+
* @author Rossen Stoyanchev
35+
* @since 3.2
36+
*
37+
* @see org.springframework.web.context.request.async.WebAsyncManager
38+
*/
39+
public interface AsyncWebRequestInterceptor extends WebRequestInterceptor{
40+
41+
/**
42+
* Called instead of {@code postHandle} and {@code afterCompletion}, when the
43+
* the handler started handling the request concurrently.
44+
*
45+
* @param request the current request
46+
*/
47+
void afterConcurrentHandlingStarted(WebRequest request);
48+
49+
}

spring-web/src/main/java/org/springframework/web/context/request/Log4jNestedDiagnosticContextInterceptor.java

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2008 the original author or authors.
2+
* Copyright 2002-2012 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,7 +19,6 @@
1919
import org.apache.log4j.Logger;
2020
import org.apache.log4j.NDC;
2121
import org.springframework.ui.ModelMap;
22-
import org.springframework.web.context.request.async.AsyncWebUtils;
2322

2423
/**
2524
* Request logging interceptor that adds a request context message to the
@@ -31,7 +30,7 @@
3130
* @see org.apache.log4j.NDC#push(String)
3231
* @see org.apache.log4j.NDC#pop()
3332
*/
34-
public class Log4jNestedDiagnosticContextInterceptor implements WebRequestInterceptor {
33+
public class Log4jNestedDiagnosticContextInterceptor implements AsyncWebRequestInterceptor {
3534

3635
/** Logger available to subclasses */
3736
protected final Logger log4jLogger = Logger.getLogger(getClass());
@@ -60,11 +59,6 @@ protected boolean isIncludeClientInfo() {
6059
* Adds a message the Log4J NDC before the request is processed.
6160
*/
6261
public void preHandle(WebRequest request) throws Exception {
63-
64-
if (AsyncWebUtils.getAsyncManager(request).hasConcurrentResult()) {
65-
return;
66-
}
67-
6862
NDC.push(getNestedDiagnosticContextMessage(request));
6963
}
7064

@@ -93,4 +87,15 @@ public void afterCompletion(WebRequest request, Exception ex) throws Exception {
9387
}
9488
}
9589

90+
/**
91+
* Removes the log message from the Log4J NDC when the processing thread is
92+
* exited after the start of asynchronous request handling.
93+
*/
94+
public void afterConcurrentHandlingStarted(WebRequest request) {
95+
NDC.pop();
96+
if (NDC.getDepth() == 0) {
97+
NDC.remove();
98+
}
99+
}
100+
96101
}

spring-web/src/main/java/org/springframework/web/context/request/async/AsyncWebRequest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public interface AsyncWebRequest extends NativeWebRequest {
3737
void setTimeout(Long timeout);
3838

3939
/**
40-
* Set a handler to be invoked if concurrent processing times out.
40+
* Set the handler to use when concurrent handling has timed out.
4141
*/
4242
void setTimeoutHandler(Runnable runnable);
4343

spring-web/src/main/java/org/springframework/web/context/request/async/AsyncWebRequestInterceptor.java

Lines changed: 0 additions & 55 deletions
This file was deleted.

spring-web/src/main/java/org/springframework/web/context/request/async/NoSupportAsyncWebRequest.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@
2222
import org.springframework.web.context.request.ServletWebRequest;
2323

2424
/**
25-
* An implementation of {@link AsyncWebRequest} to use when no underlying support is available.
26-
* The methods {@link #startAsync()} and {@link #dispatch()} raise {@link UnsupportedOperationException}.
25+
* An {@code AsyncWebRequest} to use when there is no underlying async support.
2726
*
2827
* @author Rossen Stoyanchev
2928
* @since 3.2
@@ -54,11 +53,13 @@ public boolean isDispatched() {
5453
return false;
5554
}
5655

57-
public boolean isAsyncComplete() {
56+
// Not supported
57+
58+
public void startAsync() {
5859
throw new UnsupportedOperationException("No async support in a pre-Servlet 3.0 runtime");
5960
}
6061

61-
public void startAsync() {
62+
public boolean isAsyncComplete() {
6263
throw new UnsupportedOperationException("No async support in a pre-Servlet 3.0 runtime");
6364
}
6465

spring-web/src/main/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequest.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,19 @@ public StandardServletAsyncWebRequest(HttpServletRequest request, HttpServletRes
6767

6868
/**
6969
* {@inheritDoc}
70-
* <p>The timeout period begins when the main processing thread has exited.
70+
* <p>In Servlet 3 async processing, the timeout period begins after the
71+
* container processing thread has exited.
7172
*/
7273
public void setTimeout(Long timeout) {
7374
Assert.state(!isAsyncStarted(), "Cannot change the timeout with concurrent handling in progress");
7475
this.timeout = timeout;
7576
}
7677

78+
/**
79+
* {@inheritDoc}
80+
* <p>If not set, by default a timeout is handled by returning
81+
* SERVICE_UNAVAILABLE (503).
82+
*/
7783
public void setTimeoutHandler(Runnable timeoutHandler) {
7884
if (timeoutHandler != null) {
7985
this.timeoutHandler = timeoutHandler;

spring-web/src/main/java/org/springframework/web/context/request/async/WebAsyncManager.java

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
* @author Rossen Stoyanchev
5353
* @since 3.2
5454
*
55-
* @see org.springframework.web.context.request.async.AsyncWebRequestInterceptor
55+
* @see org.springframework.web.context.request.AsyncWebRequestInterceptor
5656
* @see org.springframework.web.servlet.AsyncHandlerInterceptor
5757
* @see org.springframework.web.filter.OncePerRequestFilter#shouldFilterAsyncDispatches
5858
* @see org.springframework.web.filter.OncePerRequestFilter#isAsyncDispatch
@@ -115,26 +115,23 @@ public void setTaskExecutor(AsyncTaskExecutor taskExecutor) {
115115
}
116116

117117
/**
118-
* Whether the target handler chose to handle the request asynchronously.
119-
* A return value of "true" indicates concurrent handling is under way and the
120-
* response will remain open. A return value of "false" will be returned again after concurrent
121-
* handling produces a result and the request is dispatched to resume processing.
118+
* Whether the selected handler for the current request chose to handle the
119+
* request asynchronously. A return value of "true" indicates concurrent
120+
* handling is under way and the response will remain open. A return value
121+
* of "false" means concurrent handling was either not started or possibly
122+
* that it has completed and the request was dispatched for further
123+
* processing of the concurrent result.
122124
*/
123125
public boolean isConcurrentHandlingStarted() {
124-
return ((this.asyncWebRequest != null) && (this.asyncWebRequest.isAsyncStarted()));
126+
return ((this.asyncWebRequest != null) && this.asyncWebRequest.isAsyncStarted());
125127
}
126128

127129
/**
128-
* Whether the request was dispatched to resume processing the result of
130+
* Whether the request has been dispatched to process the result of
129131
* concurrent handling.
130132
*/
131133
public boolean hasConcurrentResult() {
132-
133-
// TODO:
134-
// Add check for asyncWebRequest.isDispatched() once Apache id=53632 is fixed.
135-
// That ensure "true" is returned in the dispatched thread only.
136-
137-
return this.concurrentResult != RESULT_NONE;
134+
return ((this.concurrentResult != RESULT_NONE) && this.asyncWebRequest.isDispatched());
138135
}
139136

140137
/**

0 commit comments

Comments
 (0)