Skip to content

Commit 3d1bf28

Browse files
committed
Refine how async request state lock is obtained
Rather than waiting indefinitely, keep checking if the state changed from ASYNC (e.g. to ERROR). Closes gh-33421
1 parent b7c7823 commit 3d1bf28

File tree

1 file changed

+52
-45
lines changed

1 file changed

+52
-45
lines changed

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

Lines changed: 52 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.ArrayList;
2222
import java.util.List;
2323
import java.util.Locale;
24+
import java.util.concurrent.TimeUnit;
2425
import java.util.concurrent.locks.ReentrantLock;
2526
import java.util.function.Consumer;
2627

@@ -211,6 +212,38 @@ public void onComplete(AsyncEvent event) throws IOException {
211212
}
212213

213214

215+
/**
216+
* Return 0 when there is no need to obtain a lock (no async handling in
217+
* progress), 1 if lock was acquired, and -1 if lock is not acquired because
218+
* request is no longer usable.
219+
*/
220+
private int tryObtainLock() {
221+
222+
if (this.state == State.NEW) {
223+
return 0;
224+
}
225+
226+
// Do not wait indefinitely, stop if we moved on from ASYNC state (e.g. to ERROR),
227+
// helps to avoid ABBA deadlock with onError callback
228+
229+
while (this.state == State.ASYNC) {
230+
try {
231+
if (this.stateLock.tryLock(500, TimeUnit.MILLISECONDS)) {
232+
if (this.state == State.ASYNC) {
233+
return 1;
234+
}
235+
this.stateLock.unlock();
236+
break;
237+
}
238+
}
239+
catch (InterruptedException ex) {
240+
// ignore
241+
}
242+
}
243+
244+
return -1;
245+
}
246+
214247
/**
215248
* Package private access for testing only.
216249
*/
@@ -244,7 +277,7 @@ public void setAsyncWebRequest(StandardServletAsyncWebRequest asyncWebRequest) {
244277

245278
@Override
246279
public ServletOutputStream getOutputStream() throws IOException {
247-
int level = obtainLockAndCheckState();
280+
int level = obtainLockOrRaiseException();
248281
try {
249282
if (this.outputStream == null) {
250283
Assert.notNull(this.asyncWebRequest, "Not initialized");
@@ -263,7 +296,7 @@ public ServletOutputStream getOutputStream() throws IOException {
263296

264297
@Override
265298
public PrintWriter getWriter() throws IOException {
266-
int level = obtainLockAndCheckState();
299+
int level = obtainLockOrRaiseException();
267300
try {
268301
if (this.writer == null) {
269302
Assert.notNull(this.asyncWebRequest, "Not initialized");
@@ -281,7 +314,7 @@ public PrintWriter getWriter() throws IOException {
281314

282315
@Override
283316
public void flushBuffer() throws IOException {
284-
int level = obtainLockAndCheckState();
317+
int level = obtainLockOrRaiseException();
285318
try {
286319
getResponse().flushBuffer();
287320
}
@@ -293,25 +326,15 @@ public void flushBuffer() throws IOException {
293326
}
294327
}
295328

296-
/**
297-
* Return 0 if checks passed and lock is not needed, 1 if checks passed
298-
* and lock is held, or raise AsyncRequestNotUsableException.
299-
*/
300-
private int obtainLockAndCheckState() throws AsyncRequestNotUsableException {
329+
private int obtainLockOrRaiseException() throws AsyncRequestNotUsableException {
301330
Assert.notNull(this.asyncWebRequest, "Not initialized");
302-
if (this.asyncWebRequest.state == State.NEW) {
303-
return 0;
304-
}
305-
306-
this.asyncWebRequest.stateLock.lock();
307-
if (this.asyncWebRequest.state == State.ASYNC) {
308-
return 1;
331+
int result = this.asyncWebRequest.tryObtainLock();
332+
if (result == -1) {
333+
throw new AsyncRequestNotUsableException("Response not usable after " +
334+
(this.asyncWebRequest.state == State.COMPLETED ?
335+
"async request completion" : "response errors") + ".");
309336
}
310-
311-
this.asyncWebRequest.stateLock.unlock();
312-
throw new AsyncRequestNotUsableException("Response not usable after " +
313-
(this.asyncWebRequest.state == State.COMPLETED ?
314-
"async request completion" : "response errors") + ".");
337+
return result;
315338
}
316339

317340
void handleIOException(IOException ex, String msg) throws AsyncRequestNotUsableException {
@@ -357,7 +380,7 @@ public void setWriteListener(WriteListener writeListener) {
357380

358381
@Override
359382
public void write(int b) throws IOException {
360-
int level = this.response.obtainLockAndCheckState();
383+
int level = this.response.obtainLockOrRaiseException();
361384
try {
362385
this.delegate.write(b);
363386
}
@@ -370,7 +393,7 @@ public void write(int b) throws IOException {
370393
}
371394

372395
public void write(byte[] buf, int offset, int len) throws IOException {
373-
int level = this.response.obtainLockAndCheckState();
396+
int level = this.response.obtainLockOrRaiseException();
374397
try {
375398
this.delegate.write(buf, offset, len);
376399
}
@@ -384,7 +407,7 @@ public void write(byte[] buf, int offset, int len) throws IOException {
384407

385408
@Override
386409
public void flush() throws IOException {
387-
int level = this.response.obtainLockAndCheckState();
410+
int level = this.response.obtainLockOrRaiseException();
388411
try {
389412
this.delegate.flush();
390413
}
@@ -398,7 +421,7 @@ public void flush() throws IOException {
398421

399422
@Override
400423
public void close() throws IOException {
401-
int level = this.response.obtainLockAndCheckState();
424+
int level = this.response.obtainLockOrRaiseException();
402425
try {
403426
this.delegate.close();
404427
}
@@ -432,7 +455,7 @@ private LifecyclePrintWriter(PrintWriter delegate, StandardServletAsyncWebReques
432455

433456
@Override
434457
public void flush() {
435-
int level = tryObtainLockAndCheckState();
458+
int level = this.asyncWebRequest.tryObtainLock();
436459
if (level > -1) {
437460
try {
438461
this.delegate.flush();
@@ -445,7 +468,7 @@ public void flush() {
445468

446469
@Override
447470
public void close() {
448-
int level = tryObtainLockAndCheckState();
471+
int level = this.asyncWebRequest.tryObtainLock();
449472
if (level > -1) {
450473
try {
451474
this.delegate.close();
@@ -463,7 +486,7 @@ public boolean checkError() {
463486

464487
@Override
465488
public void write(int c) {
466-
int level = tryObtainLockAndCheckState();
489+
int level = this.asyncWebRequest.tryObtainLock();
467490
if (level > -1) {
468491
try {
469492
this.delegate.write(c);
@@ -476,7 +499,7 @@ public void write(int c) {
476499

477500
@Override
478501
public void write(char[] buf, int off, int len) {
479-
int level = tryObtainLockAndCheckState();
502+
int level = this.asyncWebRequest.tryObtainLock();
480503
if (level > -1) {
481504
try {
482505
this.delegate.write(buf, off, len);
@@ -494,7 +517,7 @@ public void write(char[] buf) {
494517

495518
@Override
496519
public void write(String s, int off, int len) {
497-
int level = tryObtainLockAndCheckState();
520+
int level = this.asyncWebRequest.tryObtainLock();
498521
if (level > -1) {
499522
try {
500523
this.delegate.write(s, off, len);
@@ -510,22 +533,6 @@ public void write(String s) {
510533
this.delegate.write(s);
511534
}
512535

513-
/**
514-
* Return 0 if checks passed and lock is not needed, 1 if checks passed
515-
* and lock is held, and -1 if checks did not pass.
516-
*/
517-
private int tryObtainLockAndCheckState() {
518-
if (this.asyncWebRequest.state == State.NEW) {
519-
return 0;
520-
}
521-
this.asyncWebRequest.stateLock.lock();
522-
if (this.asyncWebRequest.state == State.ASYNC) {
523-
return 1;
524-
}
525-
this.asyncWebRequest.stateLock.unlock();
526-
return -1;
527-
}
528-
529536
private void releaseLock(int level) {
530537
if (level > 0) {
531538
this.asyncWebRequest.stateLock.unlock();

0 commit comments

Comments
 (0)