@ -1,5 +1,5 @@
@@ -1,5 +1,5 @@
/ *
* Copyright 2002 - 2016 the original author or authors .
* Copyright 2002 - 2019 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 .
@ -16,25 +16,29 @@
@@ -16,25 +16,29 @@
package org.springframework.http.server.reactive ;
import java.nio.charset.StandardCharsets ;
import java.util.ArrayList ;
import java.util.Arrays ;
import java.util.List ;
import java.util.concurrent.Executors ;
import java.util.concurrent.TimeUnit ;
import io.netty.buffer.ByteBufAllocator ;
import org.junit.Before ;
import org.junit.Test ;
import org.reactivestreams.Publisher ;
import org.reactivestreams.Subscriber ;
import org.reactivestreams.Subscription ;
import reactor.core.publisher.BaseSubscriber ;
import reactor.core.publisher.Flux ;
import reactor.core.publisher.Mono ;
import reactor.core.publisher.Signal ;
import static org.junit.Assert.assertEquals ;
import static org.junit.Assert.assertNotNull ;
import static org.junit.Assert.assertSame ;
import static org.junit.Assert.assertTrue ;
import org.springframework.core.io.buffer.DataBuffer ;
import org.springframework.core.io.buffer.LeakAwareDataBufferFactory ;
import org.springframework.core.io.buffer.NettyDataBufferFactory ;
import static org.junit.Assert.* ;
/ * *
* @author Rossen Stoyanchev
@ -50,9 +54,6 @@ public class ChannelSendOperatorTests {
@@ -50,9 +54,6 @@ public class ChannelSendOperatorTests {
this . writer = new OneByOneAsyncWriter ( ) ;
}
private < T > Mono < Void > sendOperator ( Publisher < String > source ) {
return new ChannelSendOperator < > ( source , writer : : send ) ;
}
@Test
public void errorBeforeFirstItem ( ) throws Exception {
@ -130,6 +131,66 @@ public class ChannelSendOperatorTests {
@@ -130,6 +131,66 @@ public class ChannelSendOperatorTests {
assertSame ( error , this . writer . error ) ;
}
@Test // gh-22720
public void cancelWhileItemCached ( ) {
NettyDataBufferFactory delegate = new NettyDataBufferFactory ( ByteBufAllocator . DEFAULT ) ;
LeakAwareDataBufferFactory bufferFactory = new LeakAwareDataBufferFactory ( delegate ) ;
ChannelSendOperator < DataBuffer > operator = new ChannelSendOperator < > (
Mono . fromCallable ( ( ) - > {
DataBuffer dataBuffer = bufferFactory . allocateBuffer ( ) ;
dataBuffer . write ( "foo" , StandardCharsets . UTF_8 ) ;
return dataBuffer ;
} ) ,
publisher - > {
ZeroDemandSubscriber subscriber = new ZeroDemandSubscriber ( ) ;
publisher . subscribe ( subscriber ) ;
return Mono . never ( ) ;
} ) ;
BaseSubscriber < Void > subscriber = new BaseSubscriber < Void > ( ) { } ;
operator . subscribe ( subscriber ) ;
subscriber . cancel ( ) ;
bufferFactory . checkForLeaks ( ) ;
}
@Test // gh-22720
public void errorWhileItemCached ( ) {
NettyDataBufferFactory delegate = new NettyDataBufferFactory ( ByteBufAllocator . DEFAULT ) ;
LeakAwareDataBufferFactory bufferFactory = new LeakAwareDataBufferFactory ( delegate ) ;
ZeroDemandSubscriber writeSubscriber = new ZeroDemandSubscriber ( ) ;
ChannelSendOperator < DataBuffer > operator = new ChannelSendOperator < > (
Flux . create ( sink - > {
DataBuffer dataBuffer = bufferFactory . allocateBuffer ( ) ;
dataBuffer . write ( "foo" , StandardCharsets . UTF_8 ) ;
sink . next ( dataBuffer ) ;
sink . error ( new IllegalStateException ( "err" ) ) ;
} ) ,
publisher - > {
publisher . subscribe ( writeSubscriber ) ;
return Mono . never ( ) ;
} ) ;
operator . subscribe ( new BaseSubscriber < Void > ( ) { } ) ;
try {
writeSubscriber . signalDemand ( 1 ) ; // Let cached signals ("foo" and error) be published..
}
catch ( Throwable ex ) {
assertNotNull ( ex . getCause ( ) ) ;
assertEquals ( "err" , ex . getCause ( ) . getMessage ( ) ) ;
}
bufferFactory . checkForLeaks ( ) ;
}
private < T > Mono < Void > sendOperator ( Publisher < String > source ) {
return new ChannelSendOperator < > ( source , writer : : send ) ;
}
private static class OneByOneAsyncWriter {
@ -182,4 +243,18 @@ public class ChannelSendOperatorTests {
@@ -182,4 +243,18 @@ public class ChannelSendOperatorTests {
}
}
private static class ZeroDemandSubscriber extends BaseSubscriber < DataBuffer > {
@Override
protected void hookOnSubscribe ( Subscription subscription ) {
// Just subscribe without requesting
}
public void signalDemand ( long demand ) {
upstream ( ) . request ( demand ) ;
}
}
}