Flux.delayElements() breaks StepVerifier.verify()
btheu opened this issue · comments
Upgrading from reactor-core 3.4.30 to 3.5.0 (or even latest 3.5.7) breaks a junit test case using Flux.delayElements().
Expected Behavior
Flux.delayElements() should flow delayed items in a test context using TestPublisher and StepVerifier.
Actual Behavior
reactor-core 3.4.30: Ok
reactor-core 3.5.0: Failed
reactor-core 3.5.7: Failed
java.lang.AssertionError: expectation "expectNext(1)" failed (expected: onNext(1); actual: onError(java.lang.IllegalStateException: Can't deliver value due to lack of requests))
Steps to Reproduce
@Test
void issue() {
TestPublisher<Integer> publisher = TestPublisher.<Integer>create();
var source = publisher.flux().delayElements(Duration.ofMillis(1_000));
StepVerifier.create(source)
.then(() -> publisher.next(1, 2, 3, 4, 5))
.expectNext(1)
.expectNext(2)
.expectNext(3)
.expectNext(4)
.expectNext(5)
.thenAwait()
.thenCancel()
.verify();
}
Your Environment
- Reactor version(s) used: 3.5.0
- Other relevant libraries versions (eg.
netty
, ...): reactor-test aligned with reactor-core - JVM version (
java -version
):
java version "17.0.1" 2021-10-19 LTS
Java(TM) SE Runtime Environment (build 17.0.1+12-LTS-39)
Java HotSpot(TM) 64-Bit Server VM (build 17.0.1+12-LTS-39, mixed mode, sharing) - OS and version (eg
uname -a
): Windows 10 Enterprise
This behaviour is a result of #2967 which addressed #2599.
Consider the following:
@Test
void issue() {
TestPublisher<Integer> publisher = TestPublisher.<Integer>create();
Flux<Integer> source = publisher.flux().delayElements(Duration.ofMillis(50));
int limit = 33;
StepVerifier.create(source)
.then(() -> {
for (int i = 0; i < limit; i++) {
publisher.next(i);
}
})
.expectNext(IntStream.range(0, limit).boxed().toArray(Integer[]::new))
.thenAwait()
.thenCancel()
.verify();
}
If you run it on 3.4.x it will also fail. If you change limit to 32, it will pass. That comes from the fact that the prefetch size is 32, which means the initial request is satisfied when you emit 32 items. If more are emitted, overflow happens. The logic of then
would need to coordinate with the delayElements
argument and become async, which would become unpredictable.
The behaviour in 3.5 is the result of a behaviour change of the delayElements
implementation, where there is no prefetching, but that doesn't change the principle driving the error.
One mitigation would be to use the buffering, cold variant:
TestPublisher.<Integer>createCold();
Other, buffering operators in your logic would allow to prevent overflow, but it will depend on the actual use case.