reactor / reactor-core

Non-Blocking Reactive Foundation for the JVM

Home Page:http://projectreactor.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Flux.delayElements() breaks StepVerifier.verify()

btheu opened this issue · comments

commented

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.