Java Code Examples for reactor.core.publisher.Hooks#onErrorDropped()

The following examples show how to use reactor.core.publisher.Hooks#onErrorDropped() . You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example 1
Source File: RejectedExecutionTest.java    From reactor-core with Apache License 2.0 6 votes vote down vote up
@Before
public void setUp() {
	scheduler = new BoundedScheduler(Schedulers.newSingle("bounded-single"));
	Hooks.onNextDropped(o -> onNextDropped.add(o));
	Hooks.onErrorDropped(e -> onErrorDropped.add(e));
	Hooks.onOperatorError((e, o) -> {
		onOperatorError.add(e);
		if (o instanceof Long)
			onOperatorErrorData.add((Long) o);
		else if (o != null) {
			System.out.println(o);
		}
		return e;
	});
	Schedulers.onHandleError((thread, t) -> onSchedulerHandleError.add(t));
}
 
Example 2
Source File: FluxDiscardOnCancelTest.java    From r2dbc-mysql with Apache License 2.0 5 votes vote down vote up
@Test
void errorDropped() {
    int size = 10;
    int takeSize = 2;
    int halfSize = size >>> 1;
    List<Throwable> es = new ArrayList<>();
    String message = "Some random text just for test";
    Iterator<Integer> items = createItems(size);

    assertThat(halfSize).isGreaterThan(takeSize << 1);
    Hooks.onErrorDropped(es::add);

    Flux.fromIterable(() -> items)
        .doOnNext(it -> {
            if (it == halfSize) {
                throw new IllegalStateException(message);
            }
        })
        .as(OperatorUtils::discardOnCancel)
        .as(it -> StepVerifier.create(it, 0))
        .thenRequest(takeSize)
        .expectNext(0, 1)
        .thenCancel()
        .verify();

    assertThat(es).hasSize(1)
        .element(0)
        .isExactlyInstanceOf(IllegalStateException.class)
        .extracting(Throwable::getMessage)
        .isEqualTo(message);
}
 
Example 3
Source File: RSocketRequesterTest.java    From rsocket-java with Apache License 2.0 5 votes vote down vote up
@BeforeEach
public void setUp() throws Throwable {
  Hooks.onNextDropped(ReferenceCountUtil::safeRelease);
  Hooks.onErrorDropped((t) -> {});
  rule = new ClientSocketRule();
  rule.apply(
          new Statement() {
            @Override
            public void evaluate() {}
          },
          null)
      .evaluate();
}
 
Example 4
Source File: RSocketResponderTest.java    From rsocket-java with Apache License 2.0 5 votes vote down vote up
@Test
public void checkNoLeaksOnRacingBetweenDownstreamCancelAndOnNextFromRequestResponseTest1() {
  Scheduler parallel = Schedulers.parallel();
  Hooks.onErrorDropped((e) -> {});
  ByteBufAllocator allocator = rule.alloc();
  for (int i = 0; i < 10000; i++) {
    Operators.MonoSubscriber<Payload, Payload>[] sources = new Operators.MonoSubscriber[1];

    rule.setAcceptingSocket(
        new RSocket() {
          @Override
          public Mono<Payload> requestResponse(Payload payload) {
            payload.release();
            return new Mono<Payload>() {
              @Override
              public void subscribe(CoreSubscriber<? super Payload> actual) {
                sources[0] = new Operators.MonoSubscriber<>(actual);
                actual.onSubscribe(sources[0]);
              }
            };
          }
        },
        Integer.MAX_VALUE);

    rule.sendRequest(1, REQUEST_RESPONSE);

    ByteBuf cancelFrame = CancelFrameCodec.encode(allocator, 1);
    RaceTestUtils.race(
        () -> rule.connection.addToReceivedBuffer(cancelFrame),
        () -> {
          sources[0].complete(ByteBufPayload.create("d1", "m1"));
        },
        parallel);

    Assertions.assertThat(rule.connection.getSent()).allMatch(ReferenceCounted::release);

    rule.assertHasNoLeaks();
  }
}
 
Example 5
Source File: RSocketResponderTest.java    From rsocket-java with Apache License 2.0 5 votes vote down vote up
@BeforeEach
public void setUp() throws Throwable {
  Hooks.onNextDropped(ReferenceCountUtil::safeRelease);
  Hooks.onErrorDropped(t -> {});
  rule = new ServerSocketRule();
  rule.apply(
          new Statement() {
            @Override
            public void evaluate() {}
          },
          null)
      .evaluate();
}
 
Example 6
Source File: ReconnectMonoTests.java    From rsocket-java with Apache License 2.0 5 votes vote down vote up
@Test
public void shouldExpireValueOnRacingDisposeAndComplete() {
  Hooks.onErrorDropped(t -> {});
  for (int i = 0; i < 10000; i++) {
    final TestPublisher<String> cold =
        TestPublisher.createNoncompliant(TestPublisher.Violation.REQUEST_OVERFLOW);

    final ReconnectMono<String> reconnectMono =
        cold.mono().as(source -> new ReconnectMono<>(source, onExpire(), onValue()));

    final MonoProcessor<String> processor = reconnectMono.subscribeWith(MonoProcessor.create());

    Assertions.assertThat(expired).isEmpty();
    Assertions.assertThat(received).isEmpty();

    cold.next("value" + i);

    RaceTestUtils.race(cold::complete, reconnectMono::dispose);

    Assertions.assertThat(processor.isTerminated()).isTrue();

    if (processor.isError()) {
      Assertions.assertThat(processor.getError())
          .isInstanceOf(CancellationException.class)
          .hasMessage("ReconnectMono has already been disposed");
    } else {
      Assertions.assertThat(received)
          .hasSize(1)
          .containsOnly(Tuples.of("value" + i, reconnectMono));
      Assertions.assertThat(processor.peek()).isEqualTo("value" + i);
    }

    Assertions.assertThat(expired).hasSize(1).containsOnly("value" + i);

    expired.clear();
    received.clear();
  }
}
 
Example 7
Source File: ReconnectMonoTests.java    From rsocket-java with Apache License 2.0 5 votes vote down vote up
@Test
public void shouldExpireValueOnRacingDisposeAndNoValueComplete() {
  Hooks.onErrorDropped(t -> {});
  for (int i = 0; i < 10000; i++) {
    final TestPublisher<String> cold =
        TestPublisher.createNoncompliant(TestPublisher.Violation.REQUEST_OVERFLOW);

    final ReconnectMono<String> reconnectMono =
        cold.mono().as(source -> new ReconnectMono<>(source, onExpire(), onValue()));

    final MonoProcessor<String> processor = reconnectMono.subscribeWith(MonoProcessor.create());

    Assertions.assertThat(expired).isEmpty();
    Assertions.assertThat(received).isEmpty();

    RaceTestUtils.race(cold::complete, reconnectMono::dispose);

    Assertions.assertThat(processor.isTerminated()).isTrue();

    Throwable error = processor.getError();

    if (error instanceof CancellationException) {
      Assertions.assertThat(error)
          .isInstanceOf(CancellationException.class)
          .hasMessage("ReconnectMono has already been disposed");
    } else {
      Assertions.assertThat(error)
          .isInstanceOf(IllegalStateException.class)
          .hasMessage("Source completed empty");
    }

    Assertions.assertThat(expired).isEmpty();

    expired.clear();
    received.clear();
  }
}
 
Example 8
Source File: DefaultRSocketClientTests.java    From rsocket-java with Apache License 2.0 5 votes vote down vote up
@BeforeEach
public void setUp() throws Throwable {
  Hooks.onNextDropped(ReferenceCountUtil::safeRelease);
  Hooks.onErrorDropped((t) -> {});
  rule = new ClientSocketRule();
  rule.apply(
          new Statement() {
            @Override
            public void evaluate() {}
          },
          null)
      .evaluate();
}
 
Example 9
Source File: DefaultStepVerifierBuilder.java    From reactor-core with Apache License 2.0 5 votes vote down vote up
private void plugHooks() {
	Hooks.onErrorDropped(droppedErrors::offer);
	Hooks.onNextDropped(droppedElements::offer);
	Hooks.onOperatorError((t, d) -> {
		operatorErrors.offer(Tuples.of(Optional.ofNullable(t), Optional.ofNullable(d)));
		return t;
	});
}
 
Example 10
Source File: FluxDiscardOnCancelTest.java    From r2dbc-mysql with Apache License 2.0 5 votes vote down vote up
@Test
void errorDropped() {
    int size = 10;
    int takeSize = 2;
    int halfSize = size >>> 1;
    List<Throwable> es = new ArrayList<>();
    String message = "Some random text just for test";
    Iterator<Integer> items = createItems(size);

    assertThat(halfSize).isGreaterThan(takeSize << 1);
    Hooks.onErrorDropped(es::add);

    Flux.fromIterable(() -> items)
        .doOnNext(it -> {
            if (it == halfSize) {
                throw new IllegalStateException(message);
            }
        })
        .as(OperatorUtils::discardOnCancel)
        .as(it -> StepVerifier.create(it, 0))
        .thenRequest(takeSize)
        .expectNext(0, 1)
        .thenCancel()
        .verify();

    assertThat(es).hasSize(1)
        .element(0)
        .isExactlyInstanceOf(IllegalStateException.class)
        .extracting(Throwable::getMessage)
        .isEqualTo(message);
}
 
Example 11
Source File: ReconnectMonoTests.java    From rsocket-java with Apache License 2.0 4 votes vote down vote up
@Test
public void shouldNotifyAllTheSubscribersUnderRacingBetweenSubscribeAndComplete() {
  Hooks.onErrorDropped(t -> {});
  for (int i = 0; i < 10000; i++) {
    final TestPublisher<String> cold =
        TestPublisher.createNoncompliant(TestPublisher.Violation.REQUEST_OVERFLOW);

    final ReconnectMono<String> reconnectMono =
        cold.mono().as(source -> new ReconnectMono<>(source, onExpire(), onValue()));

    final MonoProcessor<String> processor = reconnectMono.subscribeWith(MonoProcessor.create());
    final MonoProcessor<String> racerProcessor = MonoProcessor.create();

    Assertions.assertThat(expired).isEmpty();
    Assertions.assertThat(received).isEmpty();

    cold.next("value" + i);

    RaceTestUtils.race(cold::complete, () -> reconnectMono.subscribe(racerProcessor));

    Assertions.assertThat(processor.isTerminated()).isTrue();

    Assertions.assertThat(processor.peek()).isEqualTo("value" + i);
    Assertions.assertThat(racerProcessor.peek()).isEqualTo("value" + i);

    Assertions.assertThat(reconnectMono.resolvingInner.subscribers)
        .isEqualTo(ResolvingOperator.READY);

    Assertions.assertThat(
            reconnectMono.resolvingInner.add(
                new ResolvingOperator.MonoDeferredResolutionOperator<>(
                    reconnectMono.resolvingInner, processor)))
        .isEqualTo(ResolvingOperator.READY_STATE);

    Assertions.assertThat(expired).isEmpty();
    Assertions.assertThat(received)
        .hasSize(1)
        .containsOnly(Tuples.of("value" + i, reconnectMono));

    received.clear();
  }
}
 
Example 12
Source File: ReconnectMonoTests.java    From rsocket-java with Apache License 2.0 4 votes vote down vote up
@Test
public void shouldNotExpireNewlyResolvedValueIfSubscribeIsRacingWithInvalidate() {
  Hooks.onErrorDropped(t -> {});
  for (int i = 0; i < 10000; i++) {
    final int index = i;
    final TestPublisher<String> cold =
        TestPublisher.createNoncompliant(TestPublisher.Violation.REQUEST_OVERFLOW);

    final ReconnectMono<String> reconnectMono =
        cold.mono().as(source -> new ReconnectMono<>(source, onExpire(), onValue()));

    final MonoProcessor<String> processor = reconnectMono.subscribeWith(MonoProcessor.create());
    final MonoProcessor<String> racerProcessor = MonoProcessor.create();

    Assertions.assertThat(expired).isEmpty();
    Assertions.assertThat(received).isEmpty();

    reconnectMono.resolvingInner.mainSubscriber.onNext("value_to_expire" + i);
    reconnectMono.resolvingInner.mainSubscriber.onComplete();

    RaceTestUtils.race(
        reconnectMono::invalidate,
        () -> {
          reconnectMono.subscribe(racerProcessor);
          if (!racerProcessor.isTerminated()) {
            reconnectMono.resolvingInner.mainSubscriber.onNext("value_to_not_expire" + index);
            reconnectMono.resolvingInner.mainSubscriber.onComplete();
          }
        },
        Schedulers.parallel());

    Assertions.assertThat(processor.isTerminated()).isTrue();

    Assertions.assertThat(processor.peek()).isEqualTo("value_to_expire" + i);
    StepVerifier.create(racerProcessor)
        .expectNextMatches(
            (v) -> {
              if (reconnectMono.resolvingInner.subscribers == ResolvingOperator.READY) {
                return v.equals("value_to_not_expire" + index);
              } else {
                return v.equals("value_to_expire" + index);
              }
            })
        .expectComplete()
        .verify(Duration.ofMillis(100));

    Assertions.assertThat(expired).hasSize(1).containsOnly("value_to_expire" + i);
    if (reconnectMono.resolvingInner.subscribers == ResolvingOperator.READY) {
      Assertions.assertThat(received)
          .hasSize(2)
          .containsExactly(
              Tuples.of("value_to_expire" + i, reconnectMono),
              Tuples.of("value_to_not_expire" + i, reconnectMono));
    } else {
      Assertions.assertThat(received)
          .hasSize(1)
          .containsOnly(Tuples.of("value_to_expire" + i, reconnectMono));
    }

    expired.clear();
    received.clear();
  }
}
 
Example 13
Source File: ReconnectMonoTests.java    From rsocket-java with Apache License 2.0 4 votes vote down vote up
@Test
public void shouldNotExpireNewlyResolvedValueIfSubscribeIsRacingWithInvalidates() {
  Hooks.onErrorDropped(t -> {});
  for (int i = 0; i < 10000; i++) {
    final int index = i;
    final TestPublisher<String> cold =
        TestPublisher.createNoncompliant(TestPublisher.Violation.REQUEST_OVERFLOW);

    final ReconnectMono<String> reconnectMono =
        cold.mono().as(source -> new ReconnectMono<>(source, onExpire(), onValue()));

    final MonoProcessor<String> processor = reconnectMono.subscribeWith(MonoProcessor.create());
    final MonoProcessor<String> racerProcessor = MonoProcessor.create();

    Assertions.assertThat(expired).isEmpty();
    Assertions.assertThat(received).isEmpty();

    reconnectMono.resolvingInner.mainSubscriber.onNext("value_to_expire" + i);
    reconnectMono.resolvingInner.mainSubscriber.onComplete();

    RaceTestUtils.race(
        () ->
            RaceTestUtils.race(
                reconnectMono::invalidate, reconnectMono::invalidate, Schedulers.parallel()),
        () -> {
          reconnectMono.subscribe(racerProcessor);
          if (!racerProcessor.isTerminated()) {
            reconnectMono.resolvingInner.mainSubscriber.onNext(
                "value_to_possibly_expire" + index);
            reconnectMono.resolvingInner.mainSubscriber.onComplete();
          }
        },
        Schedulers.parallel());

    Assertions.assertThat(processor.isTerminated()).isTrue();

    Assertions.assertThat(processor.peek()).isEqualTo("value_to_expire" + i);
    StepVerifier.create(racerProcessor)
        .expectNextMatches(
            (v) ->
                v.equals("value_to_possibly_expire" + index)
                    || v.equals("value_to_expire" + index))
        .expectComplete()
        .verify(Duration.ofMillis(100));

    if (expired.size() == 2) {
      Assertions.assertThat(expired)
          .hasSize(2)
          .containsExactly("value_to_expire" + i, "value_to_possibly_expire" + i);
    } else {
      Assertions.assertThat(expired).hasSize(1).containsOnly("value_to_expire" + i);
    }
    if (received.size() == 2) {
      Assertions.assertThat(received)
          .hasSize(2)
          .containsExactly(
              Tuples.of("value_to_expire" + i, reconnectMono),
              Tuples.of("value_to_possibly_expire" + i, reconnectMono));
    } else {
      Assertions.assertThat(received)
          .hasSize(1)
          .containsOnly(Tuples.of("value_to_expire" + i, reconnectMono));
    }

    expired.clear();
    received.clear();
  }
}
 
Example 14
Source File: ReconnectMonoTests.java    From rsocket-java with Apache License 2.0 4 votes vote down vote up
@Test
public void shouldNotExpireNewlyResolvedValueIfBlockIsRacingWithInvalidate() {
  Hooks.onErrorDropped(t -> {});
  for (int i = 0; i < 10000; i++) {
    final int index = i;
    final TestPublisher<String> cold =
        TestPublisher.createNoncompliant(TestPublisher.Violation.REQUEST_OVERFLOW);

    final ReconnectMono<String> reconnectMono =
        cold.mono().as(source -> new ReconnectMono<>(source, onExpire(), onValue()));

    final MonoProcessor<String> processor = reconnectMono.subscribeWith(MonoProcessor.create());

    Assertions.assertThat(expired).isEmpty();
    Assertions.assertThat(received).isEmpty();

    reconnectMono.resolvingInner.mainSubscriber.onNext("value_to_expire" + i);
    reconnectMono.resolvingInner.mainSubscriber.onComplete();

    RaceTestUtils.race(
        () ->
            Assertions.assertThat(reconnectMono.block())
                .matches(
                    (v) ->
                        v.equals("value_to_not_expire" + index)
                            || v.equals("value_to_expire" + index)),
        () ->
            RaceTestUtils.race(
                reconnectMono::invalidate,
                () -> {
                  for (; ; ) {
                    if (reconnectMono.resolvingInner.subscribers != ResolvingOperator.READY) {
                      reconnectMono.resolvingInner.mainSubscriber.onNext(
                          "value_to_not_expire" + index);
                      reconnectMono.resolvingInner.mainSubscriber.onComplete();
                      break;
                    }
                  }
                },
                Schedulers.parallel()),
        Schedulers.parallel());

    Assertions.assertThat(processor.isTerminated()).isTrue();

    Assertions.assertThat(processor.peek()).isEqualTo("value_to_expire" + i);

    Assertions.assertThat(expired).hasSize(1).containsOnly("value_to_expire" + i);
    if (reconnectMono.resolvingInner.subscribers == ResolvingOperator.READY) {
      Assertions.assertThat(received)
          .hasSize(2)
          .containsExactly(
              Tuples.of("value_to_expire" + i, reconnectMono),
              Tuples.of("value_to_not_expire" + i, reconnectMono));
    } else {
      Assertions.assertThat(received)
          .hasSize(1)
          .containsOnly(Tuples.of("value_to_expire" + i, reconnectMono));
    }

    expired.clear();
    received.clear();
  }
}
 
Example 15
Source File: ReconnectMonoTests.java    From rsocket-java with Apache License 2.0 4 votes vote down vote up
@Test
public void shouldExpireValueOnRacingDisposeAndNext() {
  Hooks.onErrorDropped(t -> {});
  Hooks.onNextDropped(System.out::println);
  for (int i = 0; i < 10000; i++) {
    final int index = i;
    final CoreSubscriber<? super String>[] monoSubscribers = new CoreSubscriber[1];
    Subscription mockSubscription = Mockito.mock(Subscription.class);
    final Mono<String> stringMono =
        new Mono<String>() {
          @Override
          public void subscribe(CoreSubscriber<? super String> actual) {
            actual.onSubscribe(mockSubscription);
            monoSubscribers[0] = actual;
          }
        };

    final ReconnectMono<String> reconnectMono =
        stringMono
            .doOnDiscard(Object.class, System.out::println)
            .as(source -> new ReconnectMono<>(source, onExpire(), onValue()));

    final MonoProcessor<String> processor = reconnectMono.subscribeWith(MonoProcessor.create());

    Assertions.assertThat(expired).isEmpty();
    Assertions.assertThat(received).isEmpty();

    RaceTestUtils.race(() -> monoSubscribers[0].onNext("value" + index), reconnectMono::dispose);

    monoSubscribers[0].onComplete();

    Assertions.assertThat(processor.isTerminated()).isTrue();
    Mockito.verify(mockSubscription).cancel();

    if (processor.isError()) {
      Assertions.assertThat(processor.getError())
          .isInstanceOf(CancellationException.class)
          .hasMessage("ReconnectMono has already been disposed");

      Assertions.assertThat(expired).containsOnly("value" + i);
    } else {
      Assertions.assertThat(processor.peek()).isEqualTo("value" + i);
    }

    expired.clear();
    received.clear();
  }
}
 
Example 16
Source File: ReconnectMonoTests.java    From rsocket-java with Apache License 2.0 4 votes vote down vote up
@Test
public void shouldExpireValueOnRacingDisposeAndError() {
  Hooks.onErrorDropped(t -> {});
  RuntimeException runtimeException = new RuntimeException("test");
  for (int i = 0; i < 10000; i++) {
    final TestPublisher<String> cold =
        TestPublisher.createNoncompliant(TestPublisher.Violation.REQUEST_OVERFLOW);

    final ReconnectMono<String> reconnectMono =
        cold.mono().as(source -> new ReconnectMono<>(source, onExpire(), onValue()));

    final MonoProcessor<String> processor = reconnectMono.subscribeWith(MonoProcessor.create());

    Assertions.assertThat(expired).isEmpty();
    Assertions.assertThat(received).isEmpty();

    cold.next("value" + i);

    RaceTestUtils.race(() -> cold.error(runtimeException), reconnectMono::dispose);

    Assertions.assertThat(processor.isTerminated()).isTrue();

    if (processor.isError()) {
      if (processor.getError() instanceof CancellationException) {
        Assertions.assertThat(processor.getError())
            .isInstanceOf(CancellationException.class)
            .hasMessage("ReconnectMono has already been disposed");
      } else {
        Assertions.assertThat(processor.getError())
            .isInstanceOf(RuntimeException.class)
            .hasMessage("test");
      }
    } else {
      Assertions.assertThat(received)
          .hasSize(1)
          .containsOnly(Tuples.of("value" + i, reconnectMono));
      Assertions.assertThat(processor.peek()).isEqualTo("value" + i);
    }

    Assertions.assertThat(expired).hasSize(1).containsOnly("value" + i);

    expired.clear();
    received.clear();
  }
}
 
Example 17
Source File: ReconnectMonoTests.java    From rsocket-java with Apache License 2.0 4 votes vote down vote up
@Test
public void shouldExpireValueOnRacingDisposeAndErrorWithNoBackoff() {
  Hooks.onErrorDropped(t -> {});
  RuntimeException runtimeException = new RuntimeException("test");
  for (int i = 0; i < 10000; i++) {
    final TestPublisher<String> cold =
        TestPublisher.createNoncompliant(TestPublisher.Violation.REQUEST_OVERFLOW);

    final ReconnectMono<String> reconnectMono =
        cold.mono()
            .retryWhen(Retry.max(1).filter(t -> t instanceof Exception))
            .as(source -> new ReconnectMono<>(source, onExpire(), onValue()));

    final MonoProcessor<String> processor = reconnectMono.subscribeWith(MonoProcessor.create());

    Assertions.assertThat(expired).isEmpty();
    Assertions.assertThat(received).isEmpty();

    cold.next("value" + i);

    RaceTestUtils.race(() -> cold.error(runtimeException), reconnectMono::dispose);

    Assertions.assertThat(processor.isTerminated()).isTrue();

    if (processor.isError()) {

      if (processor.getError() instanceof CancellationException) {
        Assertions.assertThat(processor.getError())
            .isInstanceOf(CancellationException.class)
            .hasMessage("ReconnectMono has already been disposed");
      } else {
        Assertions.assertThat(processor.getError())
            .matches(t -> Exceptions.isRetryExhausted(t))
            .hasCause(runtimeException);
      }

      Assertions.assertThat(expired).hasSize(1).containsOnly("value" + i);
    } else {
      Assertions.assertThat(received)
          .hasSize(1)
          .containsOnly(Tuples.of("value" + i, reconnectMono));
      Assertions.assertThat(processor.peek()).isEqualTo("value" + i);
    }

    expired.clear();
    received.clear();
  }
}
 
Example 18
Source File: ResolvingOperatorTests.java    From rsocket-java with Apache License 2.0 4 votes vote down vote up
@Test
public void shouldExpireValueOnRacingDisposeAndError() {
  Hooks.onErrorDropped(t -> {});
  RuntimeException runtimeException = new RuntimeException("test");
  for (int i = 0; i < 10000; i++) {
    MonoProcessor<String> processor = MonoProcessor.create();
    BiConsumer<String, Throwable> consumer =
        (v, t) -> {
          if (t != null) {
            processor.onError(t);
            return;
          }

          processor.onNext(v);
        };
    MonoProcessor<String> processor2 = MonoProcessor.create();
    BiConsumer<String, Throwable> consumer2 =
        (v, t) -> {
          if (t != null) {
            processor2.onError(t);
            return;
          }

          processor2.onNext(v);
        };

    ResolvingTest.<String>create()
        .assertNothingExpired()
        .assertNothingReceived()
        .assertPendingSubscribers(0)
        .assertPendingResolution()
        .thenAddObserver(consumer)
        .assertSubscribeCalled(1)
        .assertPendingSubscribers(1)
        .then(self -> RaceTestUtils.race(() -> self.terminate(runtimeException), self::dispose))
        .assertPendingSubscribers(0)
        .assertNothingExpired()
        .assertDisposeCalled(1)
        .then(
            self -> {
              Assertions.assertThat(self.subscribers).isEqualTo(ResolvingOperator.TERMINATED);

              Assertions.assertThat(self.add((v, t) -> {}))
                  .isEqualTo(ResolvingOperator.TERMINATED_STATE);
            })
        .thenAddObserver(consumer2);

    StepVerifier.create(processor)
        .expectErrorSatisfies(
            t -> {
              if (t instanceof CancellationException) {
                Assertions.assertThat(t)
                    .isInstanceOf(CancellationException.class)
                    .hasMessage("Disposed");
              } else {
                Assertions.assertThat(t).isInstanceOf(RuntimeException.class).hasMessage("test");
              }
            })
        .verify(Duration.ofMillis(10));

    StepVerifier.create(processor2)
        .expectErrorSatisfies(
            t -> {
              if (t instanceof CancellationException) {
                Assertions.assertThat(t)
                    .isInstanceOf(CancellationException.class)
                    .hasMessage("Disposed");
              } else {
                Assertions.assertThat(t).isInstanceOf(RuntimeException.class).hasMessage("test");
              }
            })
        .verify(Duration.ofMillis(10));

    // no way to guarantee equality because of racing
    //      Assertions.assertThat(processor.getError())
    //                .isEqualTo(processor2.getError());
  }
}