apple / apple/servicetalk

StepVerifier: *LastStep issues with cancellation and terminal events

Open
#1,492 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

API bug
Dominant language
Java
Stars
1k
Forks
230
Avg merge
23h 23m
Merged PRs (30d)
16

Description

thenCancel() is on the *LastStep interface, but it triggers cancellation right away ignoring all previous steps.

Example:

    @Test
    public void requesterPayloadBodyDoesNotTimeoutWhenIgnored() {
        Duration timeout = ofMillis(100L);
        TestPublisher<Buffer> payloadBody = new TestPublisher<>();
        FilterableStreamingHttpConnection connection = connection(succeeded(newResponse(payloadBody)));
        StreamingHttpRequester requester = new TimeoutHttpRequesterFilter(timeout, false).create(connection);
        AtomicBoolean responseSucceeded = new AtomicBoolean();
        StepVerifiers.create(requester.request(STRATEGY, mock(StreamingHttpRequest.class))
                .whenOnSuccess(__ -> responseSucceeded.set(true))
                .whenCancel(() -> System.out.println("CANCELLED"))
                .flatMapPublisher(StreamingHttpResponse::payloadBody))
                .thenRequest(MAX_VALUE)
                .expectNoSignals(timeout.plusMillis(10L))
                .thenCancel()
                .verify();
        assertThat("Response did not succeeded", responseSucceeded.get(), is(true));
        assertThat("No subscribe for payload body", payloadBody.isSubscribed(), is(true));
    }

This test is expected to cancel after "expectNoSignals", but it cancels immediately.

The workaround is to intercept the Cancellable manually, schedule cancellation using executor, and use another terminal state. Example:

AtomicReference<Cancellable> cancellable = new AtomicReference<>();
stepVerifier(requester.request(...)
        .expectCancellableConsumed(cancellable::set)
        .then(() -> {
            try {
                secondRequestReceivedLatch.await();
                cancellable.get().cancel(); // If I use thenCancel() the current then(Runnable) does not run
            } catch (InterruptedException e) {
                // ignore
            }
        })
        .expectError()    // subscrived.onError(…) was never invoked, test is green
        .verify();

This is confusing because expectError is just ignored. It's only there to transition to verify() method through the last step.

thenCancel() should trigger cancellation only after all previous steps complete. Or we should have another step method that will have described behavior.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at the StepVerifier *LastStep.thenCancel() entry point and reproduce the behavior with the requesterPayloadBodyDoesNotTimeoutWhenIgnored() test shown in the issue. Check that preceding steps, including expectNoSignals(timeout), complete before cancellation; done when the test cancels only afterward and the existing terminal-step behavior remains covered.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
testing
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.