Browse Source

Avoid locking if not handling asynchronously

Avoid the overhead of locking in the ServletOutputStream wrapper
if it is not an asynchronous request.

See gh-32342
pull/32754/head
rstoyanchev 2 years ago
parent
commit
30c75ffe8e
  1. 55
      spring-web/src/main/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequest.java
  2. 7
      spring-web/src/test/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequestTests.java

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

@ -87,7 +87,7 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements @@ -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 @@ -142,11 +142,17 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
"or by adding \"<async-supported>true</async-supported>\" 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 @@ -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 @@ -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 @@ -345,7 +365,10 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
/**
* Represents a state for {@link StandardServletAsyncWebRequest} to be in.
* <p><pre>
* ACTIVE ----+
* NEW
* |
* v
* ASYNC----> +
* | |
* v |
* ERROR |
@ -357,7 +380,17 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements @@ -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
}

7
spring-web/src/test/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequestTests.java

@ -1,5 +1,5 @@ @@ -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 { @@ -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

Loading…
Cancel
Save