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
super(request, new LifecycleHttpServletResponse(response)); super(request, new LifecycleHttpServletResponse(response));
this.state = (previousRequest != null ? previousRequest.state : State.ACTIVE); this.state = (previousRequest != null ? previousRequest.state : State.NEW);
//noinspection DataFlowIssue //noinspection DataFlowIssue
((LifecycleHttpServletResponse) getResponse()).setAsyncWebRequest(this); ((LifecycleHttpServletResponse) getResponse()).setAsyncWebRequest(this);
@ -142,11 +142,17 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
"or by adding \"<async-supported>true</async-supported>\" to servlet and " + "or by adding \"<async-supported>true</async-supported>\" to servlet and " +
"filter declarations in web.xml."); "filter declarations in web.xml.");
Assert.state(!isAsyncComplete(), "Async processing has already completed");
if (isAsyncStarted()) { if (isAsyncStarted()) {
return; 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 = getRequest().startAsync(getRequest(), getResponse());
this.asyncContext.addListener(this); this.asyncContext.addListener(this);
if (this.timeout != null) { if (this.timeout != null) {
@ -190,7 +196,7 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
} }
private void transitionToErrorState() { private void transitionToErrorState() {
if (this.state == State.ACTIVE) { if (!isAsyncComplete()) {
this.state = State.ERROR; this.state = State.ERROR;
} }
} }
@ -323,15 +329,29 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
} }
private void obtainLockAndCheckState() throws AsyncRequestNotUsableException { private void obtainLockAndCheckState() throws AsyncRequestNotUsableException {
if (!this.asyncWebRequest.stateLock.tryLock() || this.asyncWebRequest.state != State.ACTIVE) { if (state() != State.NEW) {
throw new AsyncRequestNotUsableException("Response not usable after " + stateLock().lock();
(this.asyncWebRequest.state == State.COMPLETED ? if (state() != State.ASYNC) {
"async request completion" : "onError notification") + "."); stateLock().unlock();
throw new AsyncRequestNotUsableException("Response not usable after " +
(state() == State.COMPLETED ?
"async request completion" : "onError notification") + ".");
}
} }
} }
private void releaseLock() { 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 { 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. * Represents a state for {@link StandardServletAsyncWebRequest} to be in.
* <p><pre> * <p><pre>
* ACTIVE ----+ * NEW
* |
* v
* ASYNC----> +
* | | * | |
* v | * v |
* ERROR | * ERROR |
@ -357,7 +380,17 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
*/ */
private enum State { 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 @@
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -96,9 +96,8 @@ public class StandardServletAsyncWebRequestTests {
@Test @Test
public void startAsyncAfterCompleted() throws Exception { public void startAsyncAfterCompleted() throws Exception {
this.asyncRequest.onComplete(new AsyncEvent(new MockAsyncContext(this.request, this.response))); this.asyncRequest.onComplete(new AsyncEvent(new MockAsyncContext(this.request, this.response)));
assertThatIllegalStateException().isThrownBy( assertThatIllegalStateException().isThrownBy(this.asyncRequest::startAsync)
this.asyncRequest::startAsync) .withMessage("Cannot start async: [COMPLETED]");
.withMessage("Async processing has already completed");
} }
@Test @Test

Loading…
Cancel
Save