Files
sdk/sdk/lib/async/stream_controller.dart
T
Anis Alibegić 40e18905f2 Fixed various typos in a lot of files
Closes https://github.com/dart-lang/sdk/pull/49478

TEST=Manual

GitOrigin-RevId: f4c9c6869dfe73639295e86574a021523b3d374d
Change-Id: I134a97caed4eec59d70e9cbca16b7e9a472cf2c1
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/251902
Reviewed-by: Michael Thomsen <mit@google.com>
Commit-Queue: Alexander Thomas <athom@google.com>
Reviewed-by: Aske Simon Christensen <askesc@google.com>
Reviewed-by: Kevin Chisholm <kevinjchisholm@google.com>
Reviewed-by: Alexander Thomas <athom@google.com>
2022-07-25 12:21:59 +00:00

948 lines
35 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
part of dart.async;
// -------------------------------------------------------------------
// Controller for creating and adding events to a stream.
// -------------------------------------------------------------------
/// Type of a stream controller's `onListen`, `onPause` and `onResume` callbacks.
typedef void ControllerCallback();
/// Type of stream controller `onCancel` callbacks.
typedef FutureOr<void> ControllerCancelCallback();
/// A controller with the stream it controls.
///
/// This controller allows sending data, error and done events on
/// its [stream].
///
/// This class can be used to create a simple stream that others
/// can listen on, and to push events to that stream.
///
/// It's possible to check whether the stream is paused or not, and whether
/// it has subscribers or not, as well as getting a callback when either of
/// these change.
///
/// Example:
/// ```dart
/// final streamController = StreamController(
/// onPause: () => print('Paused'),
/// onResume: () => print('Resumed'),
/// onCancel: () => print('Cancelled'),
/// onListen: () => print('Listens'),
/// );
///
/// streamController.stream.listen(
/// (event) => print('Event: $event'),
/// onDone: () => print('Done'),
/// onError: (error) => print(error),
/// );
/// ```
/// To check whether there is a subscriber on the stream, use [hasListener].
/// ```dart continued
/// var hasListener = streamController.hasListener; // true
/// ```
/// To send data events to the stream, use [add] or [addStream].
/// ```dart continued
/// streamController.add(999);
/// final stream = Stream<int>.periodic(
/// const Duration(milliseconds: 200), (count) => count * count).take(4);
/// await streamController.addStream(stream);
/// ```
/// To send an error event to the stream, use [addError] or [addStream].
/// ```dart continued
/// streamController.addError(Exception('Issue 101'));
/// await streamController.addStream(Stream.error(Exception('Issue 404')));
/// ```
/// To check whether the stream is closed, use [isClosed].
/// ```dart continued
/// var isClosed = streamController.isClosed; // false
/// ```
/// To close the stream, use [close].
/// ```dart continued
/// await streamController.close();
/// isClosed = streamController.isClosed; // true
/// ```
abstract class StreamController<T> implements StreamSink<T> {
/// The stream that this controller is controlling.
Stream<T> get stream;
/// A controller with a [stream] that supports only one single subscriber.
///
/// If [sync] is true, the returned stream controller is a
/// [SynchronousStreamController], and must be used with the care
/// and attention necessary to not break the [Stream] contract. If in doubt,
/// use the non-sync version.
///
/// Using an asynchronous controller will never give the wrong
/// behavior, but using a synchronous controller incorrectly can cause
/// otherwise correct programs to break.
///
/// A synchronous controller is only intended for optimizing event
/// propagation when one asynchronous event immediately triggers another.
/// It should not be used unless the calls to [add] or [addError]
/// are guaranteed to occur in places where it won't break `Stream` invariants.
///
/// Use synchronous controllers only to forward (potentially transformed)
/// events from another stream or a future.
///
/// A Stream should be inert until a subscriber starts listening on it (using
/// the [onListen] callback to start producing events). Streams should not
/// leak resources (like websockets) when no user ever listens on the stream.
///
/// The controller buffers all incoming events until a subscriber is
/// registered, but this feature should only be used in rare circumstances.
///
/// The [onPause] function is called when the stream becomes
/// paused. [onResume] is called when the stream resumed.
///
/// The [onListen] callback is called when the stream
/// receives its listener and [onCancel] when the listener ends
/// its subscription. If [onCancel] needs to perform an asynchronous operation,
/// [onCancel] should return a future that completes when the cancel operation
/// is done.
///
/// If the stream is canceled before the controller needs data the
/// [onResume] call might not be executed.
factory StreamController(
{void onListen()?,
void onPause()?,
void onResume()?,
FutureOr<void> onCancel()?,
bool sync = false}) {
return sync
? _SyncStreamController<T>(onListen, onPause, onResume, onCancel)
: _AsyncStreamController<T>(onListen, onPause, onResume, onCancel);
}
/// A controller where [stream] can be listened to more than once.
///
/// The [Stream] returned by [stream] is a broadcast stream.
/// It can be listened to more than once.
///
/// A Stream should be inert until a subscriber starts listening on it (using
/// the [onListen] callback to start producing events). Streams should not
/// leak resources (like websockets) when no user ever listens on the stream.
///
/// Broadcast streams do not buffer events when there is no listener.
///
/// The controller distributes any events to all currently subscribed
/// listeners at the time when [add], [addError] or [close] is called.
/// It is not allowed to call `add`, `addError`, or `close` before a previous
/// call has returned. The controller does not have any internal queue of
/// events, and if there are no listeners at the time the event is added,
/// it will just be dropped, or, if it is an error, be reported as uncaught.
///
/// Each listener subscription is handled independently,
/// and if one pauses, only the pausing listener is affected.
/// A paused listener will buffer events internally until unpaused or canceled.
///
/// If [sync] is true, events may be fired directly by the stream's
/// subscriptions during an [add], [addError] or [close] call.
/// The returned stream controller is a [SynchronousStreamController],
/// and must be used with the care and attention necessary to not break
/// the [Stream] contract.
/// See [Completer.sync] for some explanations on when a synchronous
/// dispatching can be used.
/// If in doubt, keep the controller non-sync.
///
/// If [sync] is false, the event will always be fired at a later time,
/// after the code adding the event has completed.
/// In that case, no guarantees are given with regard to when
/// multiple listeners get the events, except that each listener will get
/// all events in the correct order. Each subscription handles the events
/// individually.
/// If two events are sent on an async controller with two listeners,
/// one of the listeners may get both events
/// before the other listener gets any.
/// A listener must be subscribed both when the event is initiated
/// (that is, when [add] is called)
/// and when the event is later delivered,
/// in order to receive the event.
///
/// The [onListen] callback is called when the first listener is subscribed,
/// and the [onCancel] is called when there are no longer any active listeners.
/// If a listener is added again later, after the [onCancel] was called,
/// the [onListen] will be called again.
factory StreamController.broadcast(
{void onListen()?, void onCancel()?, bool sync = false}) {
return sync
? _SyncBroadcastStreamController<T>(onListen, onCancel)
: _AsyncBroadcastStreamController<T>(onListen, onCancel);
}
/// The callback which is called when the stream is listened to.
///
/// May be set to `null`, in which case no callback will happen.
abstract void Function()? onListen;
/// The callback which is called when the stream is paused.
///
/// May be set to `null`, in which case no callback will happen.
///
/// Pause related callbacks are not supported on broadcast stream controllers.
abstract void Function()? onPause;
/// The callback which is called when the stream is resumed.
///
/// May be set to `null`, in which case no callback will happen.
///
/// Pause related callbacks are not supported on broadcast stream controllers.
abstract void Function()? onResume;
/// The callback which is called when the stream is canceled.
///
/// May be set to `null`, in which case no callback will happen.
abstract FutureOr<void> Function()? onCancel;
/// Returns a view of this object that only exposes the [StreamSink] interface.
StreamSink<T> get sink;
/// Whether the stream controller is closed for adding more events.
///
/// The controller becomes closed by calling the [close] method.
/// New events cannot be added, by calling [add] or [addError],
/// to a closed controller.
///
/// If the controller is closed,
/// the "done" event might not have been delivered yet,
/// but it has been scheduled, and it is too late to add more events.
bool get isClosed;
/// Whether the subscription would need to buffer events.
///
/// This is the case if the controller's stream has a listener and it is
/// paused, or if it has not received a listener yet. In that case, the
/// controller is considered paused as well.
///
/// A broadcast stream controller is never considered paused. It always
/// forwards its events to all uncanceled subscriptions, if any,
/// and let the subscriptions handle their own pausing and buffering.
bool get isPaused;
/// Whether there is a subscriber on the [Stream].
bool get hasListener;
/// Sends a data [event].
///
/// Listeners receive this event in a later microtask.
///
/// Note that a synchronous controller (created by passing true to the `sync`
/// parameter of the `StreamController` constructor) delivers events
/// immediately. Since this behavior violates the contract mentioned here,
/// synchronous controllers should only be used as described in the
/// documentation to ensure that the delivered events always *appear* as if
/// they were delivered in a separate microtask.
void add(T event);
/// Sends or enqueues an error event.
///
/// Listeners receive this event at a later microtask. This behavior can be
/// overridden by using `sync` controllers. Note, however, that sync
/// controllers have to satisfy the preconditions mentioned in the
/// documentation of the constructors.
void addError(Object error, [StackTrace? stackTrace]);
/// Closes the stream.
///
/// No further events can be added to a closed stream.
///
/// The returned future is the same future provided by [done].
/// It is completed when the stream listeners is done sending events,
/// This happens either when the done event has been sent,
/// or when the subscriber on a single-subscription stream is canceled.
///
/// A broadcast stream controller will send the done event
/// even if listeners are paused, so some broadcast events may not have been
/// received yet when the returned future completes.
///
/// If no one listens to a non-broadcast stream,
/// or the listener pauses and never resumes,
/// the done event will not be sent and this future will never complete.
Future close();
/// A future which is completed when the stream controller is done
/// sending events.
///
/// This happens either when the done event has been sent, or if the
/// subscriber on a single-subscription stream is canceled.
///
/// A broadcast stream controller will send the done event
/// even if listeners are paused, so some broadcast events may not have been
/// received yet when the returned future completes.
///
/// If there is no listener on a non-broadcast stream,
/// or the listener pauses and never resumes,
/// the done event will not be sent and this future will never complete.
Future get done;
/// Receives events from [source] and puts them into this controller's stream.
///
/// Returns a future which completes when the source stream is done.
///
/// Events must not be added directly to this controller using [add],
/// [addError], [close] or [addStream], until the returned future
/// is complete.
///
/// Data and error events are forwarded to this controller's stream. A done
/// event on the source will end the `addStream` operation and complete the
/// returned future.
///
/// If [cancelOnError] is `true`, only the first error on [source] is
/// forwarded to the controller's stream, and the `addStream` ends
/// after this. If [cancelOnError] is false, all errors are forwarded
/// and only a done event will end the `addStream`.
/// If [cancelOnError] is omitted or `null`, it defaults to `false`.
Future addStream(Stream<T> source, {bool? cancelOnError});
}
/// A stream controller that delivers its events synchronously.
///
/// A synchronous stream controller is intended for cases where
/// an already asynchronous event triggers an event on a stream.
///
/// Instead of adding the event to the stream in a later microtask,
/// causing extra latency, the event is instead fired immediately by the
/// synchronous stream controller, as if the stream event was
/// the current event or microtask.
///
/// The synchronous stream controller can be used to break the contract
/// on [Stream], and it must be used carefully to avoid doing so.
///
/// The only advantage to using a [SynchronousStreamController] over a
/// normal [StreamController] is the improved latency.
/// Only use the synchronous version if the improvement is significant,
/// and if its use is safe. Otherwise just use a normal stream controller,
/// which will always have the correct behavior for a [Stream], and won't
/// accidentally break other code.
///
/// Adding events to a synchronous controller should only happen as the
/// very last part of the handling of the original event.
/// At that point, adding an event to the stream is equivalent to
/// returning to the event loop and adding the event in the next microtask.
///
/// Each listener callback will be run as if it was a top-level event
/// or microtask. This means that if it throws, the error will be reported as
/// uncaught as soon as possible.
/// This is one reason to add the event as the last thing in the original event
/// handler any action done after adding the event will delay the report of
/// errors in the event listener callbacks.
///
/// If an event is added in a setting that isn't known to be another event,
/// it may cause the stream's listener to get that event before the listener
/// is ready to handle it. We promise that after calling [Stream.listen],
/// you won't get any events until the code doing the listen has completed.
/// Calling [add] in response to a function call of unknown origin may break
/// that promise.
///
/// An [onListen] callback from the controller is *not* an asynchronous event,
/// and adding events to the controller in the `onListen` callback is always
/// wrong. The events will be delivered before the listener has even received
/// the subscription yet.
///
/// The synchronous broadcast stream controller also has a restrictions that a
/// normal stream controller does not:
/// The [add], [addError], [close] and [addStream] methods *must not* be
/// called while an event is being delivered.
/// That is, if a callback on a subscription on the controller's stream causes
/// a call to any of the functions above, the call will fail.
/// A broadcast stream may have more than one listener, and if an
/// event is added synchronously while another is being also in the process
/// of being added, the latter event might reach some listeners before
/// the former. To prevent that, an event cannot be added while a previous
/// event is being fired.
/// This guarantees that an event is fully delivered when the
/// first [add], [addError] or [close] returns,
/// and further events will be delivered in the correct order.
///
/// This still only guarantees that the event is delivered to the subscription.
/// If the subscription is paused, the actual callback may still happen later,
/// and the event will instead be buffered by the subscription.
/// Barring pausing, and the following buffered events that haven't been
/// delivered yet, callbacks will be called synchronously when an event is added.
///
/// Adding an event to a synchronous non-broadcast stream controller while
/// another event is in progress may cause the second event to be delayed
/// and not be delivered synchronously, and until that event is delivered,
/// the controller will not act synchronously.
abstract class SynchronousStreamController<T> implements StreamController<T> {
/// Adds event to the controller's stream.
///
/// As [StreamController.add], but must not be called while an event is
/// being added by [add], [addError] or [close].
void add(T data);
/// Adds error to the controller's stream.
///
/// As [StreamController.addError], but must not be called while an event is
/// being added by [add], [addError] or [close].
void addError(Object error, [StackTrace? stackTrace]);
/// Closes the controller's stream.
///
/// As [StreamController.close], but must not be called while an event is
/// being added by [add], [addError] or [close].
Future close();
}
abstract class _StreamControllerLifecycle<T> {
StreamSubscription<T> _subscribe(void onData(T data)?, Function? onError,
void onDone()?, bool cancelOnError);
void _recordPause(StreamSubscription<T> subscription) {}
void _recordResume(StreamSubscription<T> subscription) {}
Future<void>? _recordCancel(StreamSubscription<T> subscription) => null;
}
// Base type for implementations of stream controllers.
abstract class _StreamControllerBase<T>
implements
StreamController<T>,
_StreamControllerLifecycle<T>,
_EventSink<T>,
_EventDispatch<T> {}
/// Default implementation of [StreamController].
///
/// Controls a stream that only supports a single controller.
abstract class _StreamController<T> implements _StreamControllerBase<T> {
// The states are bit-flags. More than one can be set at a time.
//
// The "subscription state" goes through the states:
// initial -> subscribed -> canceled.
// These are mutually exclusive.
// The "closed" state records whether the [close] method has been called
// on the controller. This can be done at any time. If done before
// subscription, the done event is queued. If done after cancel, the done
// event is ignored (just as any other event after a cancel).
/// The controller is in its initial state with no subscription.
static const int _STATE_INITIAL = 0;
/// The controller has a subscription, but hasn't been closed or canceled.
///
/// Keep in sync with
/// runtime/vm/stack_trace.cc:kStreamController_StateSubscribed.
static const int _STATE_SUBSCRIBED = 1;
/// The subscription is canceled.
static const int _STATE_CANCELED = 2;
/// Mask for the subscription state.
static const int _STATE_SUBSCRIPTION_MASK = 3;
// The following state relate to the controller, not the subscription.
// If closed, adding more events is not allowed.
// If executing an [addStream], new events are not allowed either, but will
// be added by the stream.
/// The controller is closed due to calling [close].
///
/// When the stream is closed, you can neither add new events nor add new
/// listeners.
static const int _STATE_CLOSED = 4;
/// The controller is in the middle of an [addStream] operation.
///
/// While adding events from a stream, no new events can be added directly
/// on the controller.
static const int _STATE_ADDSTREAM = 8;
/// Field containing different data depending on the current subscription
/// state.
///
/// If [_state] is [_STATE_INITIAL], the field may contain a [_PendingEvents]
/// for events added to the controller before a subscription.
///
/// While [_state] is [_STATE_SUBSCRIBED], the field contains the subscription.
///
/// When [_state] is [_STATE_CANCELED] the field is currently not used,
/// and will contain `null`.
@pragma("vm:entry-point")
Object? _varData;
/// Current state of the controller.
@pragma("vm:entry-point")
int _state = _STATE_INITIAL;
/// Future completed when the stream sends its last event.
///
/// This is also the future returned by [close].
// TODO(lrn): Could this be stored in the varData field too, if it's not
// accessed until the call to "close"? Then we need to special case if it's
// accessed earlier, or if close is called before subscribing.
_Future<void>? _doneFuture;
void Function()? onListen;
void Function()? onPause;
void Function()? onResume;
FutureOr<void> Function()? onCancel;
_StreamController(this.onListen, this.onPause, this.onResume, this.onCancel);
// Return a new stream every time. The streams are equal, but not identical.
Stream<T> get stream => _ControllerStream<T>(this);
/// Returns a view of this object that only exposes the [StreamSink] interface.
StreamSink<T> get sink => _StreamSinkWrapper<T>(this);
/// Whether a listener has existed and been canceled.
///
/// After this, adding more events will be ignored.
bool get _isCanceled => (_state & _STATE_CANCELED) != 0;
/// Whether there is an active listener.
bool get hasListener => (_state & _STATE_SUBSCRIBED) != 0;
/// Whether there has not been a listener yet.
bool get _isInitialState =>
(_state & _STATE_SUBSCRIPTION_MASK) == _STATE_INITIAL;
bool get isClosed => (_state & _STATE_CLOSED) != 0;
bool get isPaused =>
hasListener ? _subscription._isInputPaused : !_isCanceled;
bool get _isAddingStream => (_state & _STATE_ADDSTREAM) != 0;
/// New events may not be added after close, or during addStream.
bool get _mayAddEvent => (_state < _STATE_CLOSED);
// Returns the pending events.
// Pending events are events added before a subscription exists.
// They are added to the subscription when it is created.
// Pending events, if any, are kept in the _varData field until the
// stream is listened to.
// While adding a stream, pending events are moved into the
// state object to allow the state object to use the _varData field.
_PendingEvents<T>? get _pendingEvents {
assert(_isInitialState);
if (!_isAddingStream) {
return _varData as dynamic;
}
_StreamControllerAddStreamState<T> state = _varData as dynamic;
return state.varData;
}
// Returns the pending events, and creates the object if necessary.
_PendingEvents<T> _ensurePendingEvents() {
assert(_isInitialState);
if (!_isAddingStream) {
Object? events = _varData;
if (events == null) {
_varData = events = _PendingEvents<T>();
}
return events as dynamic;
}
_StreamControllerAddStreamState<T> state = _varData as dynamic;
Object? events = state.varData;
if (events == null) {
state.varData = events = _PendingEvents<T>();
}
return events as dynamic;
}
// Get the current subscription.
// If we are adding a stream, the subscription is moved into the state
// object to allow the state object to use the _varData field.
_ControllerSubscription<T> get _subscription {
assert(hasListener);
Object? varData = _varData;
if (_isAddingStream) {
_StreamControllerAddStreamState<Object?> streamState = varData as dynamic;
varData = streamState.varData;
}
return varData as dynamic;
}
/// Creates an error describing why an event cannot be added.
///
/// The reason, and therefore the error message, depends on the current state.
Error _badEventState() {
if (isClosed) {
return StateError("Cannot add event after closing");
}
assert(_isAddingStream);
return StateError("Cannot add event while adding a stream");
}
// StreamSink interface.
Future addStream(Stream<T> source, {bool? cancelOnError}) {
if (!_mayAddEvent) throw _badEventState();
if (_isCanceled) return _Future.immediate(null);
_StreamControllerAddStreamState<T> addState =
_StreamControllerAddStreamState<T>(
this, _varData, source, cancelOnError ?? false);
_varData = addState;
_state |= _STATE_ADDSTREAM;
return addState.addStreamFuture;
}
/// Returns a future that is completed when the stream is done
/// processing events.
///
/// This happens either when the done event has been sent, or if the
/// subscriber of a single-subscription stream is cancelled.
Future<void> get done => _ensureDoneFuture();
Future<void> _ensureDoneFuture() =>
_doneFuture ??= _isCanceled ? Future._nullFuture : _Future<void>();
/// Send or enqueue a data event.
void add(T value) {
if (!_mayAddEvent) throw _badEventState();
_add(value);
}
/// Send or enqueue an error event.
void addError(Object error, [StackTrace? stackTrace]) {
checkNotNullable(error, "error");
if (!_mayAddEvent) throw _badEventState();
AsyncError? replacement = Zone.current.errorCallback(error, stackTrace);
if (replacement != null) {
error = replacement.error;
stackTrace = replacement.stackTrace;
} else {
stackTrace ??= AsyncError.defaultStackTrace(error);
}
_addError(error, stackTrace);
}
/// Closes this controller and sends a done event on the stream.
///
/// The first time a controller is closed, a "done" event is added to its
/// stream.
///
/// You are allowed to close the controller more than once, but only the first
/// call has any effect.
///
/// After closing, no further events may be added using [add], [addError]
/// or [addStream].
///
/// The returned future is completed when the done event has been delivered.
Future close() {
if (isClosed) {
return _ensureDoneFuture();
}
if (!_mayAddEvent) throw _badEventState();
_closeUnchecked();
return _ensureDoneFuture();
}
void _closeUnchecked() {
_state |= _STATE_CLOSED;
if (hasListener) {
_sendDone();
} else if (_isInitialState) {
_ensurePendingEvents().add(const _DelayedDone());
}
}
// EventSink interface. Used by the [addStream] events.
// Add data event, used both by the [addStream] events and by [add].
void _add(T value) {
if (hasListener) {
_sendData(value);
} else if (_isInitialState) {
_ensurePendingEvents().add(_DelayedData<T>(value));
}
}
void _addError(Object error, StackTrace stackTrace) {
if (hasListener) {
_sendError(error, stackTrace);
} else if (_isInitialState) {
_ensurePendingEvents().add(_DelayedError(error, stackTrace));
}
}
void _close() {
// End of addStream stream.
assert(_isAddingStream);
_StreamControllerAddStreamState<T> addState = _varData as dynamic;
_varData = addState.varData;
_state &= ~_STATE_ADDSTREAM;
addState.complete();
}
// _StreamControllerLifeCycle interface
StreamSubscription<T> _subscribe(void onData(T data)?, Function? onError,
void onDone()?, bool cancelOnError) {
if (!_isInitialState) {
throw StateError("Stream has already been listened to.");
}
_ControllerSubscription<T> subscription = _ControllerSubscription<T>(
this, onData, onError, onDone, cancelOnError);
_PendingEvents<T>? pendingEvents = _pendingEvents;
_state |= _STATE_SUBSCRIBED;
if (_isAddingStream) {
_StreamControllerAddStreamState<T> addState = _varData as dynamic;
addState.varData = subscription;
addState.resume();
} else {
_varData = subscription;
}
subscription._setPendingEvents(pendingEvents);
subscription._guardCallback(() {
_runGuarded(onListen);
});
return subscription;
}
Future<void>? _recordCancel(StreamSubscription<T> subscription) {
// When we cancel, we first cancel any stream being added,
// Then we call `onCancel`, and finally the _doneFuture is completed.
// If either of addStream's cancel or `onCancel` returns a future,
// we wait for it before continuing.
// Any error during this process ends up in the returned future.
// If more errors happen, we act as if it happens inside nested try/finally
// blocks or whenComplete calls, and only the last error ends up in the
// returned future.
Future<void>? result;
if (_isAddingStream) {
_StreamControllerAddStreamState<T> addState = _varData as dynamic;
result = addState.cancel();
}
_varData = null;
_state =
(_state & ~(_STATE_SUBSCRIBED | _STATE_ADDSTREAM)) | _STATE_CANCELED;
var onCancel = this.onCancel;
if (onCancel != null) {
if (result == null) {
// Only introduce a future if one is needed.
// If _onCancel returns null, no future is needed.
try {
var cancelResult = onCancel();
if (cancelResult is Future<void>) {
result = cancelResult;
}
} catch (e, s) {
// Return the error in the returned future.
// Complete it asynchronously, so there is time for a listener
// to handle the error.
result = _Future().._asyncCompleteError(e, s);
}
} else {
// Simpler case when we already know that we will return a future.
result = result.whenComplete(onCancel);
}
}
void complete() {
var doneFuture = _doneFuture;
if (doneFuture != null && doneFuture._mayComplete) {
doneFuture._asyncComplete(null);
}
}
if (result != null) {
result = result.whenComplete(complete);
} else {
complete();
}
return result;
}
void _recordPause(StreamSubscription<T> subscription) {
if (_isAddingStream) {
_StreamControllerAddStreamState<T> addState = _varData as dynamic;
addState.pause();
}
_runGuarded(onPause);
}
void _recordResume(StreamSubscription<T> subscription) {
if (_isAddingStream) {
_StreamControllerAddStreamState<T> addState = _varData as dynamic;
addState.resume();
}
_runGuarded(onResume);
}
}
abstract class _SyncStreamControllerDispatch<T>
implements _StreamController<T>, SynchronousStreamController<T> {
void _sendData(T data) {
_subscription._add(data);
}
void _sendError(Object error, StackTrace stackTrace) {
_subscription._addError(error, stackTrace);
}
void _sendDone() {
_subscription._close();
}
}
abstract class _AsyncStreamControllerDispatch<T>
implements _StreamController<T> {
void _sendData(T data) {
_subscription._addPending(_DelayedData<T>(data));
}
void _sendError(Object error, StackTrace stackTrace) {
_subscription._addPending(_DelayedError(error, stackTrace));
}
void _sendDone() {
_subscription._addPending(const _DelayedDone());
}
}
// TODO(lrn): Use common superclass for callback-controllers when VM supports
// constructors in mixin superclasses.
@pragma("vm:entry-point")
class _AsyncStreamController<T> = _StreamController<T>
with _AsyncStreamControllerDispatch<T>;
class _SyncStreamController<T> = _StreamController<T>
with _SyncStreamControllerDispatch<T>;
void _runGuarded(void Function()? notificationHandler) {
if (notificationHandler == null) return;
try {
notificationHandler();
} catch (e, s) {
Zone.current.handleUncaughtError(e, s);
}
}
class _ControllerStream<T> extends _StreamImpl<T> {
_StreamControllerLifecycle<T> _controller;
_ControllerStream(this._controller);
StreamSubscription<T> _createSubscription(void onData(T data)?,
Function? onError, void onDone()?, bool cancelOnError) =>
_controller._subscribe(onData, onError, onDone, cancelOnError);
// Override == and hashCode so that new streams returned by the same
// controller are considered equal. The controller returns a new stream
// each time it's queried, but doesn't have to cache the result.
int get hashCode => _controller.hashCode ^ 0x35323532;
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is _ControllerStream &&
identical(other._controller, this._controller);
}
}
class _ControllerSubscription<T> extends _BufferingStreamSubscription<T> {
final _StreamControllerLifecycle<T> _controller;
_ControllerSubscription(this._controller, void onData(T data)?,
Function? onError, void onDone()?, bool cancelOnError)
: super(onData, onError, onDone, cancelOnError);
Future<void>? _onCancel() {
return _controller._recordCancel(this);
}
void _onPause() {
_controller._recordPause(this);
}
void _onResume() {
_controller._recordResume(this);
}
}
/// A class that exposes only the [StreamSink] interface of an object.
class _StreamSinkWrapper<T> implements StreamSink<T> {
final StreamController _target;
_StreamSinkWrapper(this._target);
void add(T data) {
_target.add(data);
}
void addError(Object error, [StackTrace? stackTrace]) {
_target.addError(error, stackTrace);
}
Future close() => _target.close();
Future addStream(Stream<T> source) => _target.addStream(source);
Future get done => _target.done;
}
/// Object containing the state used to handle [StreamController.addStream].
class _AddStreamState<T> {
// [_Future] returned by call to addStream.
final _Future addStreamFuture;
// Subscription on stream argument to addStream.
final StreamSubscription addSubscription;
_AddStreamState(
_EventSink<T> controller, Stream<T> source, bool cancelOnError)
: addStreamFuture = _Future(),
addSubscription = source.listen(controller._add,
onError: cancelOnError
? makeErrorHandler(controller)
: controller._addError,
onDone: controller._close,
cancelOnError: cancelOnError);
static makeErrorHandler(_EventSink controller) => (Object e, StackTrace s) {
controller._addError(e, s);
controller._close();
};
void pause() {
addSubscription.pause();
}
void resume() {
addSubscription.resume();
}
/// Stop adding the stream.
///
/// Complete the future returned by `StreamController.addStream` when
/// the cancel is complete.
///
/// Return a future if the cancel takes time, otherwise return `null`.
Future<void> cancel() {
var cancel = addSubscription.cancel();
if (cancel == null) {
addStreamFuture._asyncComplete(null);
return Future._nullFuture;
}
return cancel.whenComplete(() {
addStreamFuture._asyncComplete(null);
});
}
void complete() {
addStreamFuture._asyncComplete(null);
}
}
class _StreamControllerAddStreamState<T> extends _AddStreamState<T> {
// The subscription or pending data of a _StreamController.
// Stored here because we reuse the `_varData` field in the _StreamController
// to store this state object.
var varData;
_StreamControllerAddStreamState(_StreamController<T> controller, this.varData,
Stream<T> source, bool cancelOnError)
: super(controller, source, cancelOnError) {
if (controller.isPaused) {
addSubscription.pause();
}
}
}