Skip to content

fix: preserve ValueStream replay for listenOnlyOnce transformers#815

Open
rbellens wants to merge 2 commits into
ReactiveX:masterfrom
rbellens:fix/do-transformer-value-stream-replay
Open

fix: preserve ValueStream replay for listenOnlyOnce transformers#815
rbellens wants to merge 2 commits into
ReactiveX:masterfrom
rbellens:fix/do-transformer-value-stream-replay

Conversation

@rbellens

Copy link
Copy Markdown

Summary

Test plan

DoStreamTransformer uses forwardStream with listenOnlyOnce, which
previously used a plain broadcast StreamController. When the source
is a ValueStream (e.g. BehaviorSubject), late listeners such as
sequential Stream.first calls would hang because the latest value was
not replayed.

Forward ValueStream sources through a BehaviorSubject output so side
effects still share a single upstream subscription while new listeners
receive the latest value.

Fixes ReactiveX#657
Relates to ReactiveX#605, ReactiveX#587
@rbellens
rbellens requested a review from hoc081098 as a code owner June 10, 2026 12:23
…ubscribe

Replace the listenOnlyOnce single-subscription path with sideEffectsOnce
broadcast forwarding so replay sources behave correctly (ReactiveX#605, ReactiveX#657)
while doOn* callbacks still fire once per upstream event.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses a long-standing edge case in RxDart’s doOn* operators when used on broadcast replay sources (e.g. BehaviorSubject), where late/serial listeners (like repeated Stream.first) could hang due to upstream subscription/cancellation handling.

Changes:

  • Refactors forwardStream() to use a named sideEffectsOnce option and introduces a dedicated broadcast forwarding path to ensure doOn* side effects run only once per upstream event.
  • Fixes re-listen behavior by resetting the internal cancelled flag on _forward’s onListen.
  • Adds regression tests for issues #657 and #605 around sequential first and cancel/relisten behavior with BehaviorSubject.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

File Description
packages/rxdart/lib/src/utils/forwarding_stream.dart Adds a new broadcast forwarding strategy for doOn* with sideEffectsOnce, and fixes _forward re-listen after cancel.
packages/rxdart/lib/src/transformers/do.dart Updates DoStreamTransformer to use forwardStream(..., sideEffectsOnce: true).
packages/rxdart/test/transformers/do_test.dart Adds regression tests covering sequential first and cancel/relisten behavior on BehaviorSubject.

Comment on lines +3 to +4
import 'package:rxdart/src/streams/value_stream.dart';
import 'package:rxdart/src/subjects/behavior_subject.dart';
Comment on lines +73 to +95
(v) {
if (controllers.first == controller) {
sink.onData(v);
} else {
// This listener has its own upstream subscription (source semantics),
// but side effects already ran on the primary listener's path.
sink.sink.add(v);
}
},
onError: (Object e, StackTrace s) {
if (controllers.first == controller) {
sink.onError(e, s);
} else {
sink.sink.addError(e, s);
}
},
onDone: () {
if (controllers.first == controller) {
sink.onDone();
} else {
sink.sink.close();
}
},
Comment on lines +501 to +502
await subscription.cancel();
});
Comment on lines +517 to +518
await subscription.cancel();
});
Comment on lines +525 to +527
await stream.listen(null).cancel();
expect(await stream.first, 'b');
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DoStreamTransformer not behaving as expected

2 participants