mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-09-17 08:24:13 +00:00
Merge branch '7.0.x'
This commit is contained in:
@@ -64,7 +64,8 @@ public class CheckstyleConventions {
|
||||
NoHttpExtension noHttp = project.getExtensions().getByType(NoHttpExtension.class);
|
||||
noHttp.setAllowlistFile(project.file("src/nohttp/allowlist.lines"));
|
||||
noHttp.getSource().exclude("**/test-output/**", "**/.settings/**", "**/.classpath",
|
||||
"**/.project", "**/.gradle/**", "**/node_modules/**", "**/spring-jcl/**", "buildSrc/build/**");
|
||||
"**/.project", "**/.gradle/**", "**/node_modules/**", "**/spring-jcl/**", "buildSrc/build/**",
|
||||
".claude/**");
|
||||
List<String> buildFolders = List.of("bin", "build", "out");
|
||||
project.allprojects(subproject -> {
|
||||
Path rootPath = project.getRootDir().toPath();
|
||||
|
||||
@@ -44,6 +44,7 @@ import org.jspecify.annotations.Nullable;
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.reactivestreams.Subscriber;
|
||||
import org.reactivestreams.Subscription;
|
||||
import reactor.core.Exceptions;
|
||||
import reactor.core.publisher.BaseSubscriber;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.FluxSink;
|
||||
@@ -1070,7 +1071,14 @@ public abstract class DataBufferUtils {
|
||||
Assert.state(iterator.hasNext(), "No ByteBuffer available");
|
||||
ByteBuffer byteBuffer = iterator.next();
|
||||
Attachment attachment = new Attachment(dataBuffer, iterator);
|
||||
this.channel.read(byteBuffer, this.position.get(), attachment, this);
|
||||
try {
|
||||
this.channel.read(byteBuffer, this.position.get(), attachment, this);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
Exceptions.throwIfFatal(ex);
|
||||
// If the exception escapes, route it to the failure handler
|
||||
failed(ex, attachment);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+61
@@ -64,6 +64,7 @@ import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.willAnswer;
|
||||
import static org.mockito.BDDMockito.willThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
@@ -201,6 +202,66 @@ class DataBufferUtilsTests extends AbstractDataBufferAllocatingTests {
|
||||
.verify(Duration.ofSeconds(3));
|
||||
}
|
||||
|
||||
@Test // gh-37143
|
||||
@SuppressWarnings("deprecation") // PooledByteBufAllocator no longer supports tinyCacheSize.
|
||||
void readAsynchronousFileChannelReadThrowsSynchronously() {
|
||||
// Use a dedicated allocator rather than a shared one from
|
||||
// ParameterizedDataBufferAllocatingTest, since this test deliberately
|
||||
// triggers a buffer leak and must not pollute the allocation counters
|
||||
// that other tests rely on.
|
||||
PooledByteBufAllocator allocator = new PooledByteBufAllocator(true, 1, 1, 4096, 4, 0, 0, 0, true);
|
||||
super.bufferFactory = new NettyDataBufferFactory(allocator);
|
||||
|
||||
// On some JDK/Netty combinations (e.g. Windows + JDK 25 + Netty 4.2), the
|
||||
// AsynchronousFileChannel#read(ByteBuffer, long, A, CompletionHandler) call
|
||||
// itself throws instead of invoking CompletionHandler#failed asynchronously.
|
||||
AsynchronousFileChannel channel = mock();
|
||||
willThrow(new UnsupportedOperationException("simulated synchronous failure"))
|
||||
.given(channel).read(any(), anyLong(), any(), any());
|
||||
|
||||
Flux<DataBuffer> result =
|
||||
DataBufferUtils.readAsynchronousFileChannel(() -> channel, super.bufferFactory, 3);
|
||||
|
||||
StepVerifier.create(result)
|
||||
.expectError(UnsupportedOperationException.class)
|
||||
.verify(Duration.ofSeconds(3));
|
||||
}
|
||||
|
||||
@Test // gh-37143
|
||||
void readAsynchronousFileChannelReadThrowsSynchronouslyFromCompletionThread() throws Exception {
|
||||
super.bufferFactory = new DefaultDataBufferFactory();
|
||||
|
||||
// Real AsynchronousFileChannel implementations invoke the CompletionHandler on a
|
||||
// separate thread, not the calling thread. If a subsequent read (triggered
|
||||
// recursively from within ReadCompletionHandler#completed on that other thread)
|
||||
// throws synchronously, the exception never reaches the original Flux.create
|
||||
// request() call stack, and thus never reaches the FluxSink.
|
||||
var executor = Executors.newSingleThreadExecutor();
|
||||
try {
|
||||
AsynchronousFileChannel channel = mock();
|
||||
willAnswer(invocation -> {
|
||||
ByteBuffer byteBuffer = invocation.getArgument(0);
|
||||
byteBuffer.put("foo".getBytes(StandardCharsets.UTF_8));
|
||||
Object attachment = invocation.getArgument(2);
|
||||
CompletionHandler<Integer, Object> completionHandler = invocation.getArgument(3);
|
||||
executor.submit(() -> completionHandler.completed(3, attachment));
|
||||
return null;
|
||||
}).willThrow(new UnsupportedOperationException("simulated synchronous failure"))
|
||||
.given(channel).read(any(), anyLong(), any(), any());
|
||||
|
||||
Flux<DataBuffer> result =
|
||||
DataBufferUtils.readAsynchronousFileChannel(() -> channel, super.bufferFactory, 3);
|
||||
|
||||
StepVerifier.create(result)
|
||||
.consumeNextWith(stringConsumer("foo"))
|
||||
.expectError(UnsupportedOperationException.class)
|
||||
.verify(Duration.ofSeconds(3));
|
||||
}
|
||||
finally {
|
||||
executor.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedDataBufferAllocatingTest
|
||||
void readAsynchronousFileChannelCancel(DataBufferFactory bufferFactory) throws Exception {
|
||||
super.bufferFactory = bufferFactory;
|
||||
|
||||
Reference in New Issue
Block a user