Skip to content

Commit d2b25c0

Browse files
committed
Merge branch '6.1.x'
2 parents 6518a56 + 5c1ab7e commit d2b25c0

File tree

4 files changed

+85
-47
lines changed

4 files changed

+85
-47
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

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

214215

216+
/**
217+
* Return 0 when there is no need to obtain a lock (no async handling in
218+
* progress), 1 if lock was acquired, and -1 if lock is not acquired because
219+
* request is no longer usable.
220+
*/
221+
private int tryObtainLock() {
222+
223+
if (this.state == State.NEW) {
224+
return 0;
225+
}
226+
227+
// Do not wait indefinitely, stop if we moved on from ASYNC state (e.g. to ERROR),
228+
// helps to avoid ABBA deadlock with onError callback
229+
230+
while (this.state == State.ASYNC) {
231+
try {
232+
if (this.stateLock.tryLock(500, TimeUnit.MILLISECONDS)) {
233+
if (this.state == State.ASYNC) {
234+
return 1;
235+
}
236+
this.stateLock.unlock();
237+
break;
238+
}
239+
}
240+
catch (InterruptedException ex) {
241+
// ignore
242+
}
243+
}
244+
245+
return -1;
246+
}
247+
215248
/**
216249
* Package private access for testing only.
217250
*/
@@ -246,7 +279,7 @@ public void setAsyncWebRequest(StandardServletAsyncWebRequest asyncWebRequest) {
246279
@Override
247280
@SuppressWarnings("NullAway")
248281
public ServletOutputStream getOutputStream() throws IOException {
249-
int level = obtainLockAndCheckState();
282+
int level = obtainLockOrRaiseException();
250283
try {
251284
if (this.outputStream == null) {
252285
Assert.notNull(this.asyncWebRequest, "Not initialized");
@@ -266,7 +299,7 @@ public ServletOutputStream getOutputStream() throws IOException {
266299
@Override
267300
@SuppressWarnings("NullAway")
268301
public PrintWriter getWriter() throws IOException {
269-
int level = obtainLockAndCheckState();
302+
int level = obtainLockOrRaiseException();
270303
try {
271304
if (this.writer == null) {
272305
Assert.notNull(this.asyncWebRequest, "Not initialized");
@@ -284,7 +317,7 @@ public PrintWriter getWriter() throws IOException {
284317

285318
@Override
286319
public void flushBuffer() throws IOException {
287-
int level = obtainLockAndCheckState();
320+
int level = obtainLockOrRaiseException();
288321
try {
289322
getResponse().flushBuffer();
290323
}
@@ -296,25 +329,15 @@ public void flushBuffer() throws IOException {
296329
}
297330
}
298331

299-
/**
300-
* Return 0 if checks passed and lock is not needed, 1 if checks passed
301-
* and lock is held, or raise AsyncRequestNotUsableException.
302-
*/
303-
private int obtainLockAndCheckState() throws AsyncRequestNotUsableException {
332+
private int obtainLockOrRaiseException() throws AsyncRequestNotUsableException {
304333
Assert.notNull(this.asyncWebRequest, "Not initialized");
305-
if (this.asyncWebRequest.state == State.NEW) {
306-
return 0;
307-
}
308-
309-
this.asyncWebRequest.stateLock.lock();
310-
if (this.asyncWebRequest.state == State.ASYNC) {
311-
return 1;
334+
int result = this.asyncWebRequest.tryObtainLock();
335+
if (result == -1) {
336+
throw new AsyncRequestNotUsableException("Response not usable after " +
337+
(this.asyncWebRequest.state == State.COMPLETED ?
338+
"async request completion" : "response errors") + ".");
312339
}
313-
314-
this.asyncWebRequest.stateLock.unlock();
315-
throw new AsyncRequestNotUsableException("Response not usable after " +
316-
(this.asyncWebRequest.state == State.COMPLETED ?
317-
"async request completion" : "response errors") + ".");
340+
return result;
318341
}
319342

320343
void handleIOException(IOException ex, String msg) throws AsyncRequestNotUsableException {
@@ -360,7 +383,7 @@ public void setWriteListener(WriteListener writeListener) {
360383

361384
@Override
362385
public void write(int b) throws IOException {
363-
int level = this.response.obtainLockAndCheckState();
386+
int level = this.response.obtainLockOrRaiseException();
364387
try {
365388
this.delegate.write(b);
366389
}
@@ -373,7 +396,7 @@ public void write(int b) throws IOException {
373396
}
374397

375398
public void write(byte[] buf, int offset, int len) throws IOException {
376-
int level = this.response.obtainLockAndCheckState();
399+
int level = this.response.obtainLockOrRaiseException();
377400
try {
378401
this.delegate.write(buf, offset, len);
379402
}
@@ -387,7 +410,7 @@ public void write(byte[] buf, int offset, int len) throws IOException {
387410

388411
@Override
389412
public void flush() throws IOException {
390-
int level = this.response.obtainLockAndCheckState();
413+
int level = this.response.obtainLockOrRaiseException();
391414
try {
392415
this.delegate.flush();
393416
}
@@ -401,7 +424,7 @@ public void flush() throws IOException {
401424

402425
@Override
403426
public void close() throws IOException {
404-
int level = this.response.obtainLockAndCheckState();
427+
int level = this.response.obtainLockOrRaiseException();
405428
try {
406429
this.delegate.close();
407430
}
@@ -435,7 +458,7 @@ private LifecyclePrintWriter(PrintWriter delegate, StandardServletAsyncWebReques
435458

436459
@Override
437460
public void flush() {
438-
int level = tryObtainLockAndCheckState();
461+
int level = this.asyncWebRequest.tryObtainLock();
439462
if (level > -1) {
440463
try {
441464
this.delegate.flush();
@@ -448,7 +471,7 @@ public void flush() {
448471

449472
@Override
450473
public void close() {
451-
int level = tryObtainLockAndCheckState();
474+
int level = this.asyncWebRequest.tryObtainLock();
452475
if (level > -1) {
453476
try {
454477
this.delegate.close();
@@ -466,7 +489,7 @@ public boolean checkError() {
466489

467490
@Override
468491
public void write(int c) {
469-
int level = tryObtainLockAndCheckState();
492+
int level = this.asyncWebRequest.tryObtainLock();
470493
if (level > -1) {
471494
try {
472495
this.delegate.write(c);
@@ -479,7 +502,7 @@ public void write(int c) {
479502

480503
@Override
481504
public void write(char[] buf, int off, int len) {
482-
int level = tryObtainLockAndCheckState();
505+
int level = this.asyncWebRequest.tryObtainLock();
483506
if (level > -1) {
484507
try {
485508
this.delegate.write(buf, off, len);
@@ -497,7 +520,7 @@ public void write(char[] buf) {
497520

498521
@Override
499522
public void write(String s, int off, int len) {
500-
int level = tryObtainLockAndCheckState();
523+
int level = this.asyncWebRequest.tryObtainLock();
501524
if (level > -1) {
502525
try {
503526
this.delegate.write(s, off, len);
@@ -513,22 +536,6 @@ public void write(String s) {
513536
this.delegate.write(s);
514537
}
515538

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

spring-webflux/src/main/java/org/springframework/web/reactive/result/view/DefaultRenderingBuilder.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2024 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.
@@ -84,7 +84,12 @@ private Model initModel() {
8484

8585
@Override
8686
public DefaultRenderingBuilder status(HttpStatusCode status) {
87-
this.status = status;
87+
if (this.view instanceof RedirectView) {
88+
((RedirectView) this.view).setStatusCode(status);
89+
}
90+
else {
91+
this.status = status;
92+
}
8893
return this;
8994
}
9095

spring-webflux/src/test/java/org/springframework/web/reactive/result/view/DefaultRenderingBuilderTests.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.junit.jupiter.api.Test;
2424

2525
import org.springframework.http.HttpHeaders;
26+
import org.springframework.http.HttpStatus;
2627

2728
import static org.assertj.core.api.Assertions.assertThat;
2829

@@ -126,6 +127,16 @@ void redirectWithPropagateQuery() {
126127
assertThat(((RedirectView) view).isPropagateQuery()).isTrue();
127128
}
128129

130+
@Test // gh-33498
131+
void redirectWithCustomStatus() {
132+
HttpStatus status = HttpStatus.MOVED_PERMANENTLY;
133+
Rendering rendering = Rendering.redirectTo("foo").status(status).build();
134+
135+
Object view = rendering.view();
136+
assertThat(view.getClass()).isEqualTo(RedirectView.class);
137+
assertThat(((RedirectView) view).getStatusCode()).isEqualTo(status);
138+
}
139+
129140

130141
private static class Foo {}
131142

spring-webflux/src/test/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandlerTests.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.web.reactive.result.view;
1818

19+
import java.net.URI;
1920
import java.nio.ByteBuffer;
2021
import java.time.Duration;
2122
import java.util.Arrays;
@@ -202,6 +203,20 @@ void handleReturnValueTypes() {
202203
assertThat(exchange.getResponse().getHeaders().getFirst("h")).isEqualTo("h1");
203204
}
204205

206+
@Test // gh-33498
207+
void handleRedirect() {
208+
HttpStatus status = HttpStatus.MOVED_PERMANENTLY;
209+
Rendering returnValue = Rendering.redirectTo("foo").status(status).build();
210+
MethodParameter returnType = on(Handler.class).resolveReturnType(Rendering.class);
211+
HandlerResult result = new HandlerResult(new Object(), returnValue, returnType, this.bindingContext);
212+
213+
MockServerWebExchange exchange = MockServerWebExchange.from(get("/path"));
214+
resultHandler(new UrlBasedViewResolver()).handleResult(exchange, result).block(Duration.ofSeconds(5));
215+
216+
assertThat(exchange.getResponse().getStatusCode()).isEqualTo(status);
217+
assertThat(exchange.getResponse().getHeaders().getLocation()).isEqualTo(URI.create("foo"));
218+
}
219+
205220
@Test
206221
void handleWithMultipleResolvers() {
207222
testHandle("/account",

0 commit comments

Comments
 (0)