Compliance with Reactive Streams specs#7191
Conversation
| // would call Subscription::cancel from within onError (Reactive Streams rule 2.3). Still cancel on an | ||
| // external abort. | ||
| if (!onErrorInvoked) { | ||
| synchronized (this) { |
There was a problem hiding this comment.
Could there be a race condition where onErrorInvoked turns to true (onError invoked) right after the check?
There was a problem hiding this comment.
No, Rule 2.3 only prohibits calling cancel() from within the onError call stack (same thread). That's the case this flag guards: onError → sets flag → calls completeExceptionally() → handler runs inline on the same thread → sees flag → skips cancel. In the scenario you're describing (different thread sets the flag after the check), the handler is running because of an external abort, not because of onError — so calling cancel() there is correct and not a rule 2.3 violation.
| @@ -317,6 +340,11 @@ private boolean shouldProcessQueueEntry(QueueEntry<T> entry) { | |||
| return false; | |||
| } | |||
|
|
|||
| if (onSubscribeInProgress) { | |||
| // Do not deliver signals until onSubscribe has returned (Reactive Streams rule 1.03). | |||
There was a problem hiding this comment.
Trying to understand this: how could processEventQueue be invoked if onSubscribeInProgress?
There was a problem hiding this comment.
The subscriber calls request(n) from within onSubscribe (rule 2.7 allows this, and most subscribers do it to signal initial demand). request(n) → processEventQueue(). Without this flag, if data was already queued, it would deliver onNext before onSubscribe returns — violating rule 1.03. A concurrent send() from another thread can also trigger processEventQueue() during onSubscribe.
Motivation and Context
Reactive Streams TCK tests are TestNG-based, so Surefire only runs them when the
surefire-testngprovider is configured. Onlycore/sdk-coreandhttp-clients/netty-nio-clienthad it. As a result, ~20 TCK tests across four other modules were silently skipped — they compiled and looked like they ran, but Surefire never executed them (no report, no "Tests run" line).Modifications
1. Moved TCK/TestNG surefire configuration to the parent
pom.xmlPreviously, each module with TCK tests needed to independently declare:
reactive-streams-tckas a test dependencysurefire-testngas a surefire provider dependencyThis was error-prone — four modules had the TCK dependency but not the provider, silently skipping their tests.
Now the parent POM declares
reactive-streams-tckas a global test dependency (which brings TestNG transitively) and configures bothsurefire-junit-platformandsurefire-testngproviders. Any module with a*TckTest.javafile will have it automatically discovered and executed. Thejunit=falseTestNG property prevents double-execution of JUnit tests.Removed the per-module
reactive-streams-tckdependency and surefire plugin overrides from:utils,core/sdk-core,core/http-auth-aws,http-clients/netty-nio-client,services/s3,services-custom/s3-transfer-manager. Also removed the unusedreactive-streams-tckdependency fromhttp-client-spiandhttp-clients/aws-crt-client(these had the dependency declared but no actual TCK tests or surefire-testng provider).2.
utils—SimplePublisher(Reactive Streams rules 3.13 and 1.03)Rule 3.13:
SimplePublishernever released its reference to the subscriber after a terminal event, so the TCK'srequired_spec313(publisher must drop subscriber references after cancellation) failed. Now thesubscriberfield is set tonullafteronComplete,onError, andcancel. A separatesubscribedAtomicBooleanwas introduced to keep enforcing the single-subscription guard (previously done by null-checkingsubscriber), andpanicAndDieguards against the now-nullable field.This one fix resolved 4 TCK failures, because
OutputStreamPublisherandInputStreamConsumingPublisher(utils) andSigV4DataFramePublisher(http-auth-aws) all delegate toSimplePublisher.Rule 1.03: When
request(n)was called from within the subscriber'sonSubscribe,processEventQueue()would immediately deliveronNextbeforeonSubscribereturned — violating the rule that signals must not be invoked concurrently. Added avolatile boolean onSubscribeInProgressflag that suppresses signal delivery untilonSubscribecompletes. TheprocessEventQueue()call after the flag is cleared picks up any pending work.3.
core/http-auth-aws—ChunkedEncodedPublisherTckTest26 failures were a test defect, not a production bug: the test never called
.contentLength(...), which the builder requires. Production always sets it via thex-amz-decoded-content-lengthheader (AwsChunkedV4PayloadSigner/AwsChunkedV4aPayloadSigner), so theValidate.notNullguard is correct. Fixed the test to pass acontentLengthmatching the data it produces.4.
services-custom/s3-transfer-manager—AsyncBufferingSubscriberTwo TCK Subscriber-rule violations:
spec213):onNext(null)must throwNullPointerException. Added a null check on the incoming element.spec203): a Subscriber must not callSubscription.cancel()from withinonError. ThereturnFuture.whenCompletehandler cancelled the subscription whenever the future failed — including whenonErroritself failed it (synchronously, from withinonError). Added anonErrorInvokedflag so the handler skips the subscription cancel when the failure originated fromonError(upstream has already terminated, so the cancel was redundant anyway). External-abort cancellation is unchanged.The existing unit test
consumerFunctionThrows_shouldCancelSubscriptionAndCompleteFutureExceptionallyassertedcancel()was called twice — the second call being the rule-2.3 violation. Updated it to expect a single cancel (from theonNextcatch block); the subscription is still cancelled and the future still completes exceptionally.Testing
Newly-enabled TCK tests pass across all affected modules.