diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequest.java b/spring-web/src/main/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequest.java index c2799b2f516..2f267bc1eae 100644 --- a/spring-web/src/main/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequest.java +++ b/spring-web/src/main/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequest.java @@ -87,7 +87,7 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements super(request, new LifecycleHttpServletResponse(response)); - this.state = (previousRequest != null ? previousRequest.state : State.ACTIVE); + this.state = (previousRequest != null ? previousRequest.state : State.NEW); //noinspection DataFlowIssue ((LifecycleHttpServletResponse) getResponse()).setAsyncWebRequest(this); @@ -142,11 +142,17 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements "or by adding \"true\" to servlet and " + "filter declarations in web.xml."); - Assert.state(!isAsyncComplete(), "Async processing has already completed"); - if (isAsyncStarted()) { return; } + + if (this.state == State.NEW) { + this.state = State.ASYNC; + } + else { + Assert.state(this.state == State.ASYNC, "Cannot start async: [" + this.state + "]"); + } + this.asyncContext = getRequest().startAsync(getRequest(), getResponse()); this.asyncContext.addListener(this); if (this.timeout != null) { @@ -190,7 +196,7 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements } private void transitionToErrorState() { - if (this.state == State.ACTIVE) { + if (!isAsyncComplete()) { this.state = State.ERROR; } } @@ -323,15 +329,29 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements } private void obtainLockAndCheckState() throws AsyncRequestNotUsableException { - if (!this.asyncWebRequest.stateLock.tryLock() || this.asyncWebRequest.state != State.ACTIVE) { - throw new AsyncRequestNotUsableException("Response not usable after " + - (this.asyncWebRequest.state == State.COMPLETED ? - "async request completion" : "onError notification") + "."); + if (state() != State.NEW) { + stateLock().lock(); + if (state() != State.ASYNC) { + stateLock().unlock(); + throw new AsyncRequestNotUsableException("Response not usable after " + + (state() == State.COMPLETED ? + "async request completion" : "onError notification") + "."); + } } } private void releaseLock() { - this.asyncWebRequest.stateLock.unlock(); + if (state() != State.NEW) { + stateLock().unlock(); + } + } + + private State state() { + return this.asyncWebRequest.state; + } + + private ReentrantLock stateLock() { + return this.asyncWebRequest.stateLock; } private void handleIOException(IOException ex, String msg) throws AsyncRequestNotUsableException { @@ -345,7 +365,10 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements /** * Represents a state for {@link StandardServletAsyncWebRequest} to be in. *

-	 *       ACTIVE ----+
+	 *        NEW
+	 *         |
+	 *         v
+	 *       ASYNC----> +
 	 *         |        |
 	 *         v        |
 	 *       ERROR      |
@@ -357,7 +380,17 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
 	 */
 	private enum State {
 
-		ACTIVE, ERROR, COMPLETED
+		/** New request (thas may not do async handling). */
+		NEW,
+
+		/** Async handling has started. */
+		ASYNC,
+
+		/** onError notification received, or ServletOutputStream failed. */
+		ERROR,
+
+		/** onComplete notification received. */
+		COMPLETED
 
 	}
 
diff --git a/spring-web/src/test/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequestTests.java b/spring-web/src/test/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequestTests.java
index 6a71cf98582..9bc204d157e 100644
--- a/spring-web/src/test/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequestTests.java
+++ b/spring-web/src/test/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequestTests.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2024 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -96,9 +96,8 @@ public class StandardServletAsyncWebRequestTests {
 	@Test
 	public void startAsyncAfterCompleted() throws Exception {
 		this.asyncRequest.onComplete(new AsyncEvent(new MockAsyncContext(this.request, this.response)));
-		assertThatIllegalStateException().isThrownBy(
-				this.asyncRequest::startAsync)
-			.withMessage("Async processing has already completed");
+		assertThatIllegalStateException().isThrownBy(this.asyncRequest::startAsync)
+				.withMessage("Cannot start async: [COMPLETED]");
 	}
 
 	@Test