Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(465)

Unified Diff: sdk/lib/async/stream_controller.dart

Issue 16007003: Optimize internals of multiplex-streams. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Address comments Created 7 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « pkg/scheduled_test/lib/src/mock_clock.dart ('k') | sdk/lib/html/dart2js/html_dart2js.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: sdk/lib/async/stream_controller.dart
diff --git a/sdk/lib/async/stream_controller.dart b/sdk/lib/async/stream_controller.dart
index 3412e48f459536d843eafefe0d3a1b02d36147d9..b1d1f1a3368c565f9118276b3bfbba1a328925bb 100644
--- a/sdk/lib/async/stream_controller.dart
+++ b/sdk/lib/async/stream_controller.dart
@@ -73,16 +73,26 @@ abstract class StreamController<T> implements EventSink<T> {
=> new _StreamControllerImpl<T>(onListen, onPause, onResume, onCancel);
/**
- * A controller where [stream] creates new stream each time it is read.
+ * A controller where [stream] can be listened to more than once.
*
- * The controller distributes any events to all currently subscribed streams.
+ * The [Stream] returned by [stream] is a broadcast stream. It can be listened
+ * to more than once.
+ *
+ * The controller distributes any events to all currently subscribed
+ * listeners.
+ * It is not allowed to call [add], [addError], or [close] before a previous
+ * call has returned.
+ *
+ * Each listener is handled independently, and if they pause, only the pausing
+ * listener is affected. A paused listener will buffer events internally until
+ * unpaused or canceled.
*
* The [onListen] callback is called when the first listener is subscribed,
- * and the [onCancel] is called when there is no longer any active listeners.
+ * 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.multiplex({void onListen(), void onCancel()}) {
+ factory StreamController.broadcast({void onListen(), void onCancel()}) {
return new _MultiplexStreamController<T>(onListen, onCancel);
}
@@ -318,28 +328,102 @@ class _ControllerSubscription<T> extends _BufferingStreamSubscription<T> {
}
}
+class _MultiplexStream<T> extends _StreamImpl<T> {
+ _MultiplexStreamController _controller;
+
+ _MultiplexStream(this._controller);
+
+ bool get isBroadcast => true;
+
+ StreamSubscription<T> _createSubscription(
+ void onData(T data),
+ void onError(Object error),
+ void onDone(),
+ bool cancelOnError) {
+ return new _MultiplexSubscription<T>(
+ _controller, onData, onError, onDone, cancelOnError);
+ }
+
+ void _onListen(_BufferingStreamSubscription subscription) {
+ _controller._recordListen(subscription);
+ }
+}
+
+abstract class _MultiplexSubscriptionLink {
+ _MultiplexSubscriptionLink _next;
+ _MultiplexSubscriptionLink _previous;
+}
+
+class _MultiplexSubscription<T> extends _ControllerSubscription<T>
+ implements _MultiplexSubscriptionLink {
+ static const int _STATE_EVENT_ID = 1;
+ static const int _STATE_FIRING = 2;
+ static const int _STATE_REMOVE_AFTER_FIRING = 4;
+ int _eventState;
+
+ _MultiplexSubscriptionLink _next;
+ _MultiplexSubscriptionLink _previous;
+
+ _MultiplexSubscription(_StreamControllerLifecycle controller,
+ void onData(T data),
+ void onError(Object error),
+ void onDone(),
+ bool cancelOnError)
+ : super(controller, onData, onError, onDone, cancelOnError) {
+ _next = _previous = this;
+ }
+
+ _MultiplexStreamController get _controller => super._controller;
+
+ bool _expectsEvent(int eventId) {
+ return (_eventState & _STATE_EVENT_ID) == eventId;
+ }
+
+ void _toggleEventId() {
+ _eventState ^= _STATE_EVENT_ID;
+ }
+
+ bool get _isFiring => (_eventState & _STATE_FIRING) != 0;
+
+ bool _setRemoveAfterFiring() {
+ assert(_isFiring);
+ _eventState |= _STATE_REMOVE_AFTER_FIRING;
+ }
+
+ bool get _removeAfterFiring =>
+ (_eventState & _STATE_REMOVE_AFTER_FIRING) != 0;
+}
+
+
class _MultiplexStreamController<T> implements StreamController<T>,
- _StreamControllerLifecycle<T> {
+ _StreamControllerLifecycle<T>,
+ _MultiplexSubscriptionLink {
+ static const int _STATE_INITIAL = 0;
+ static const int _STATE_EVENT_ID = 1;
+ static const int _STATE_FIRING = 2;
+ static const int _STATE_CLOSED = 4;
+
final _NotificationHandler _onListen;
final _NotificationHandler _onCancel;
- /** Set when the [close] method is called. */
- bool _isClosed = false;
- // TODO(lrn): Make a more efficient implementation of these subscriptions,
- // e.g., the traditional double-linked list with concurrent add and remove
- // while firing.
- Set<_BufferingStreamSubscription<T>> _streams;
+ // State of the controller.
+ int _state = _STATE_INITIAL;
- _MultiplexStreamController(this._onListen, this._onCancel)
- : _streams = new Set<_BufferingStreamSubscription<T>>();
+ // Double-linked list of active listeners.
+ _MultiplexSubscriptionLink _next;
+ _MultiplexSubscriptionLink _previous;
+
+ _MultiplexStreamController(this._onListen, this._onCancel) {
+ _next = _previous = this;
+ }
// StreamController interface.
- Stream<T> get stream => new _ControllerStream<T>(this);
+ Stream<T> get stream => new _MultiplexStream<T>(this);
EventSink<T> get sink => new _EventSinkView<T>(this);
- bool get isClosed => _isClosed;
+ bool get isClosed => (_state & _STATE_CLOSED) != 0;
/**
* A multiplex controller is never paused.
@@ -350,22 +434,50 @@ class _MultiplexStreamController<T> implements StreamController<T>,
bool get isPaused => false;
/** Whether there are currently a subscriber on the [Stream]. */
- bool get hasListener => !_streams.isEmpty;
+ bool get hasListener => !_isEmpty;
+
+ // Linked list helpers
+
+ bool get _isEmpty => identical(_next, this);
+
+ /** Adds subscription to linked list of active listeners. */
+ void _addListener(_MultiplexSubscription<T> subscription) {
+ _MultiplexSubscriptionLink previous = _previous;
+ previous._next = subscription;
+ _previous = subscription._previous;
+ subscription._previous._next = this;
+ subscription._previous = previous;
+ subscription._eventState = (_state & _STATE_EVENT_ID);
+ }
+
+ void _removeListener(_MultiplexSubscription<T> subscription) {
+ assert(identical(subscription._controller, this));
+ assert(!identical(subscription._next, subscription));
+ subscription._previous._next = subscription._next;
+ subscription._next._previous = subscription._previous;
+ subscription._next = subscription._previous = subscription;
+ }
// _StreamControllerLifecycle interface.
- void _recordListen(_BufferingStreamSubscription<T> subscription) {
- bool isFirst = _streams.isEmpty;
- _streams.add(subscription);
- if (isFirst) {
+ void _recordListen(_MultiplexSubscription<T> subscription) {
+ _addListener(subscription);
+ if (identical(_next, _previous)) {
+ // Only one listener, so it must be the first listener.
_runGuarded(_onListen);
}
}
- void _recordCancel(_BufferingStreamSubscription<T> subscription) {
- _streams.remove(subscription);
- if (_streams.isEmpty) {
- _runGuarded(_onCancel);
+ void _recordCancel(_MultiplexSubscription<T> subscription) {
+ if (subscription._isFiring) {
+ subscription._setRemoveAfterFiring();
+ } else {
+ _removeListener(subscription);
+ // If we are currently firing an event, the empty-check is performed at
+ // the end of the listener loop instead of here.
+ if ((_state & _STATE_FIRING) == 0 && _isEmpty) {
+ _runGuarded(_onCancel);
+ }
}
}
@@ -375,36 +487,69 @@ class _MultiplexStreamController<T> implements StreamController<T>,
// EventSink interface.
void add(T data) {
- if (_streams.isEmpty) return;
+ assert(!isClosed);
+ if (_isEmpty) return;
_forEachListener((_BufferingStreamSubscription<T> subscription) {
subscription._add(data);
});
}
void addError(Object error, [Object stackTrace]) {
- if (_streams.isEmpty) return;
+ assert(!isClosed);
+ if (_isEmpty) return;
_forEachListener((_BufferingStreamSubscription<T> subscription) {
subscription._addError(error);
});
}
void close() {
- _isClosed = true;
- if (_streams.isEmpty) return;
- _forEachListener((_BufferingStreamSubscription<T> subscription) {
- _streams.remove(subscription);
+ assert(!isClosed);
+ _state |= _STATE_CLOSED;
+ if (_isEmpty) return;
+ _forEachListener((_MultiplexSubscription<T> subscription) {
subscription._close();
+ subscription._eventState |=
+ _MultiplexSubscription._STATE_REMOVE_AFTER_FIRING;
});
}
void _forEachListener(
void action(_BufferingStreamSubscription<T> subscription)) {
- List<_BufferingStreamSubscription<T>> subscriptions = _streams.toList();
- for (_BufferingStreamSubscription<T> subscription in subscriptions) {
- if (_streams.contains(subscription)) {
+ if ((_state & _STATE_FIRING) != 0) {
+ throw new StateError(
+ "Cannot fire new event. Controller is already firing an event");
+ }
+ if (_isEmpty) return;
+
+ // Get event id of this event.
+ int id = (_state & _STATE_EVENT_ID);
+ // Start firing (set the _STATE_FIRING bit). We don't do [_onCancel]
+ // callbacks while firing, and we prevent reentrancy of this function.
+ //
+ // Set [_state]'s event id to the next event's id.
+ // Any listeners added while firing this event will expect the next event,
+ // not this one, and won't get notified.
+ _state ^= _STATE_EVENT_ID | _STATE_FIRING;
+ _MultiplexSubscriptionLink link = _next;
+ while (!identical(link, this)) {
+ _MultiplexSubscription<T> subscription = link;
+ if (subscription._expectsEvent(id)) {
+ subscription._eventState |= _MultiplexSubscription._STATE_FIRING;
action(subscription);
+ subscription._toggleEventId();
+ link = subscription._next;
+ if (subscription._removeAfterFiring) {
+ _removeListener(subscription);
+ }
+ subscription._eventState &= ~_MultiplexSubscription._STATE_FIRING;
+ } else {
+ link = subscription._next;
}
}
+ _state &= ~_STATE_FIRING;
+
+ if (_isEmpty) {
+ _runGuarded(_onCancel);
+ }
}
}
-
« no previous file with comments | « pkg/scheduled_test/lib/src/mock_clock.dart ('k') | sdk/lib/html/dart2js/html_dart2js.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698