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

Side by Side Diff: sdk/lib/async/stream_impl.dart

Issue 12319010: Do not allow streams to send events during state-change callbacks. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: More changes to Stream. Created 7 years, 10 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 unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 part of dart.async; 5 part of dart.async;
6 6
7 // States shared by single/multi stream implementations. 7 // States shared by single/multi stream implementations.
8 8
9 /// Initial and default state where the stream can receive and send events. 9 /// Initial and default state where the stream can receive and send events.
10 const int _STREAM_OPEN = 0; 10 const int _STREAM_OPEN = 0;
11 /// The stream has received a request to complete, but hasn't done so yet. 11 /// The stream has received a request to complete, but hasn't done so yet.
12 /// No further events can be added to the stream. 12 /// No further events can be added to the stream.
13 const int _STREAM_CLOSED = 1; 13 const int _STREAM_CLOSED = 1;
14 /// The stream has completed and will no longer receive or send events. 14 /// The stream has completed and will no longer receive or send events.
15 /// Also counts as closed. The stream must not be paused when it's completed. 15 /// Also counts as closed. The stream must not be paused when it's completed.
16 /// Always used in conjunction with [_STREAM_CLOSED]. 16 /// Always used in conjunction with [_STREAM_CLOSED].
17 const int _STREAM_COMPLETE = 2; 17 const int _STREAM_COMPLETE = 2;
18 /// Bit that alternates between events, and listeners are updated to the 18 /// Bit that alternates between events, and listeners are updated to the
19 /// current value when they are notified of the event. 19 /// current value when they are notified of the event.
20 const int _STREAM_EVENT_ID = 4; 20 const int _STREAM_EVENT_ID = 4;
21 const int _STREAM_EVENT_ID_SHIFT = 2; 21 const int _STREAM_EVENT_ID_SHIFT = 2;
22 /// Bit set while firing and clear while not. 22 /// Bit set while firing and clear while not.
23 const int _STREAM_FIRING = 8; 23 const int _STREAM_FIRING = 8;
24 /// Bit set while calling a pause-state or subscription-state change callback.
25 const int _STREAM_CALLBACK = 16;
24 /// The count of times a stream has paused is stored in the 26 /// The count of times a stream has paused is stored in the
25 /// state, shifted by this amount. 27 /// state, shifted by this amount.
26 const int _STREAM_PAUSE_COUNT_SHIFT = 4; 28 const int _STREAM_PAUSE_COUNT_SHIFT = 8;
27 29
28 // States for listeners. 30 // States for listeners.
29 31
30 /// The listener is currently not subscribed to its source stream. 32 /// The listener is currently not subscribed to its source stream.
31 const int _LISTENER_UNSUBSCRIBED = 0; 33 const int _LISTENER_UNSUBSCRIBED = 0;
32 /// The listener is actively subscribed to its source stream. 34 /// The listener is actively subscribed to its source stream.
33 const int _LISTENER_SUBSCRIBED = 1; 35 const int _LISTENER_SUBSCRIBED = 1;
34 /// The listener is subscribed until it has been notified of the current event. 36 /// The listener is subscribed until it has been notified of the current event.
35 /// This flag bit is always used in conjuction with [_LISTENER_SUBSCRIBED]. 37 /// This flag bit is always used in conjuction with [_LISTENER_SUBSCRIBED].
36 const int _LISTENER_PENDING_UNSUBSCRIBE = 2; 38 const int _LISTENER_PENDING_UNSUBSCRIBE = 2;
(...skipping 109 matching lines...) Expand 10 before | Expand all | Expand 10 after
146 /** Whether one or more active subscribers have requested a pause. */ 148 /** Whether one or more active subscribers have requested a pause. */
147 bool get _isPaused => _state >= (1 << _STREAM_PAUSE_COUNT_SHIFT); 149 bool get _isPaused => _state >= (1 << _STREAM_PAUSE_COUNT_SHIFT);
148 150
149 /** Check whether the pending event queue is non-empty */ 151 /** Check whether the pending event queue is non-empty */
150 bool get _hasPendingEvent => 152 bool get _hasPendingEvent =>
151 _pendingEvents != null && !_pendingEvents.isEmpty; 153 _pendingEvents != null && !_pendingEvents.isEmpty;
152 154
153 /** Whether we are currently firing an event. */ 155 /** Whether we are currently firing an event. */
154 bool get _isFiring => (_state & _STREAM_FIRING) != 0; 156 bool get _isFiring => (_state & _STREAM_FIRING) != 0;
155 157
158 /** Whether the state bits allow firing. */
159 bool get _mayFireState {
160 // The state disallows firing if:
161 // - an event is currently firing
162 // - a stat-change callback is being called
163 // - the pause-count is not zero.
164 const int mask =
165 _STREAM_FIRING |
166 _STREAM_CALLBACK |
167 ~((1 << _STREAM_PAUSE_COUNT_SHIFT) - 1);
168 return (_state & mask) == 0;
169 }
170
156 int get _currentEventIdBit => 171 int get _currentEventIdBit =>
157 (_state & _STREAM_EVENT_ID ) >> _STREAM_EVENT_ID_SHIFT; 172 (_state & _STREAM_EVENT_ID ) >> _STREAM_EVENT_ID_SHIFT;
158 173
159 /** Whether there is currently a subscriber on this [Stream]. */ 174 /** Whether there is currently a subscriber on this [Stream]. */
160 bool get _hasSubscribers; 175 bool get _hasSubscribers;
161 176
162 /** Whether the stream can fire a new event. */ 177 /** Whether the stream can fire a new event. */
163 bool get _canFireEvent => !_isFiring && !_isPaused && !_hasPendingEvent; 178 bool get _canFireEvent => _mayFireState && !_hasPendingEvent;
164 179
165 // State modification. 180 // State modification.
166 181
167 /** Record an increases in the number of times the listener has paused. */ 182 /** Record an increases in the number of times the listener has paused. */
168 void _incrementPauseCount(_StreamListener<T> listener) { 183 void _incrementPauseCount(_StreamListener<T> listener) {
169 listener._incrementPauseCount(); 184 listener._incrementPauseCount();
170 _updatePauseCount(1); 185 _updatePauseCount(1);
171 } 186 }
172 187
173 /** Record a decrease in the number of times the listener has paused. */ 188 /** Record a decrease in the number of times the listener has paused. */
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after
209 // This sets the _STREAM_FIRING bit and toggles the _STREAM_EVENT_ID 224 // This sets the _STREAM_FIRING bit and toggles the _STREAM_EVENT_ID
210 // bit. All current subscribers will now have a _LISTENER_EVENT_ID 225 // bit. All current subscribers will now have a _LISTENER_EVENT_ID
211 // that doesn't match _STREAM_EVENT_ID, and they will receive the 226 // that doesn't match _STREAM_EVENT_ID, and they will receive the
212 // event being fired. 227 // event being fired.
213 _state ^= _STREAM_FIRING | _STREAM_EVENT_ID; 228 _state ^= _STREAM_FIRING | _STREAM_EVENT_ID;
214 } 229 }
215 230
216 void _endFiring() { 231 void _endFiring() {
217 assert(_isFiring); 232 assert(_isFiring);
218 _state ^= _STREAM_FIRING; 233 _state ^= _STREAM_FIRING;
219 if (_isPaused) _onPauseStateChange(); 234
Anders Johnsen 2013/02/20 12:42:02 Extra space.
220 if (!_hasSubscribers) _onSubscriptionStateChange(); 235 if (!_hasSubscribers) {
236 _callOnSubscriptionStateChange();
237 } else if (_isPaused) {
238 _callOnPauseStateChange();
239 }
221 } 240 }
222 241
223 /** 242 /**
224 * Record that a listener wants a pause from events. 243 * Record that a listener wants a pause from events.
225 * 244 *
226 * This methods is called from [_StreamListener.pause()]. 245 * This methods is called from [_StreamListener.pause()].
227 * Subclasses can override this method, along with [isPaused] and 246 * Subclasses can override this method, along with [isPaused] and
228 * [createSubscription], if they want to do a different handling of paused 247 * [createSubscription], if they want to do a different handling of paused
229 * subscriptions, e.g., a filtering stream pausing its own source if all its 248 * subscriptions, e.g., a filtering stream pausing its own source if all its
230 * subscribers are paused. 249 * subscribers are paused.
231 */ 250 */
232 void _pause(_StreamListener<T> listener, Future resumeSignal) { 251 void _pause(_StreamListener<T> listener, Future resumeSignal) {
233 assert(identical(listener._source, this)); 252 assert(identical(listener._source, this));
234 if (!listener._isSubscribed) { 253 if (!listener._isSubscribed) {
235 throw new StateError("Subscription has been canceled."); 254 throw new StateError("Subscription has been canceled.");
236 } 255 }
237 assert(!_isComplete); // There can be no subscribers when complete. 256 assert(!_isComplete); // There can be no subscribers when complete.
238 bool wasPaused = _isPaused; 257 bool wasPaused = _isPaused;
239 _incrementPauseCount(listener); 258 _incrementPauseCount(listener);
240 if (resumeSignal != null) { 259 if (resumeSignal != null) {
241 resumeSignal.whenComplete(() { this._resume(listener, true); }); 260 resumeSignal.whenComplete(() { this._resume(listener, true); });
242 } 261 }
243 if (!wasPaused && !_isFiring) { 262 if (!wasPaused && !_isFiring) {
244 _onPauseStateChange(); 263 _callOnPauseStateChange();
245 } 264 }
246 } 265 }
247 266
248 /** Stops pausing due to one request from the given listener. */ 267 /** Stops pausing due to one request from the given listener. */
249 void _resume(_StreamListener<T> listener, bool fromEvent) { 268 void _resume(_StreamListener<T> listener, bool fromEvent) {
250 if (!listener.isPaused) return; 269 if (!listener.isPaused) return;
251 assert(listener._isSubscribed); 270 assert(listener._isSubscribed);
252 assert(_isPaused); 271 assert(_isPaused);
253 _decrementPauseCount(listener); 272 _decrementPauseCount(listener);
254 if (!_isPaused) { 273 if (!_isPaused) {
255 if (!_isFiring) _onPauseStateChange(); 274 if (!_isFiring) _callOnPauseStateChange();
256 if (_hasPendingEvent) { 275 if (_hasPendingEvent) {
257 // If we can fire events now, fire any pending events right away. 276 // If we can fire events now, fire any pending events right away.
258 if (fromEvent && !_isFiring) { 277 if (fromEvent && !_isFiring) {
259 _handlePendingEvents(); 278 _handlePendingEvents();
260 } else { 279 } else {
261 _schedulePendingEvents(); 280 _schedulePendingEvents();
262 } 281 }
263 } 282 }
264 } 283 }
265 } 284 }
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
302 * Any change in the pause state is only reported after all subscribers have 321 * Any change in the pause state is only reported after all subscribers have
303 * received the event. 322 * received the event.
304 * 323 *
305 * The [action] must not throw, or the controller will be left in an 324 * The [action] must not throw, or the controller will be left in an
306 * invalid state. 325 * invalid state.
307 * 326 *
308 * This method must not be called while [isFiring] is true. 327 * This method must not be called while [isFiring] is true.
309 */ 328 */
310 void _forEachSubscriber(void action(_StreamSubscriptionImpl<T> subscription)); 329 void _forEachSubscriber(void action(_StreamSubscriptionImpl<T> subscription));
311 330
331 /** Calls [_onPauseStateChange] while setting callback bit. */
332 void _callOnPauseStateChange() {
333 // After calling [_close], all pauses are handled internally by the Stream.
334 if (_isClosed) return;
335 _state |= _STREAM_CALLBACK;
336 _onPauseStateChange();
337 _state ^= _STREAM_CALLBACK;
338 }
339
340 /** Calls [_onSubscriptionStateChange] while setting callback bit. */
341 void _callOnSubscriptionStateChange() {
342 _state |= _STREAM_CALLBACK;
343 _onSubscriptionStateChange();
344 _state ^= _STREAM_CALLBACK;
345 }
346
312 /** 347 /**
313 * Called when the first subscriber requests a pause or the last a resume. 348 * Called when the first subscriber requests a pause or the last a resume.
314 * 349 *
315 * Read [isPaused] to see the new state. 350 * Read [isPaused] to see the new state.
316 */ 351 */
317 void _onPauseStateChange() {} 352 void _onPauseStateChange() {}
318 353
319 /** 354 /**
320 * Called when the first listener subscribes or the last unsubscribes. 355 * Called when the first listener subscribes or the last unsubscribes.
321 * 356 *
(...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after
423 * * [_onPauseStateChange]: Called when entering or leaving paused mode. 458 * * [_onPauseStateChange]: Called when entering or leaving paused mode.
424 * * [_hasSubscribers]: Test whether there are currently any subscribers. 459 * * [_hasSubscribers]: Test whether there are currently any subscribers.
425 * * [_isPaused]: Test whether the stream is currently paused. 460 * * [_isPaused]: Test whether the stream is currently paused.
426 * The user should not add new events while the stream is paused, but if it 461 * The user should not add new events while the stream is paused, but if it
427 * happens anyway, the stream will enqueue the events just as when new events 462 * happens anyway, the stream will enqueue the events just as when new events
428 * arrive while still firing an old event. 463 * arrive while still firing an old event.
429 */ 464 */
430 class _SingleStreamImpl<T> extends _StreamImpl<T> { 465 class _SingleStreamImpl<T> extends _StreamImpl<T> {
431 _StreamListener _subscriber = null; 466 _StreamListener _subscriber = null;
432 467
433 /** Whether one or more active subscribers have requested a pause. */ 468 // A single-stream is considered paused when it has no subscriber.
469 // Exception is when it's complete (which only matters for pause-state-change
470 // callbacks), where it's not considered paused.
434 bool get _isPaused => (!_hasSubscribers && !_isComplete) || super._isPaused; 471 bool get _isPaused => (!_hasSubscribers && !_isComplete) || super._isPaused;
435 472
473
474 // A single-stream is considered paused when it has no subscriber.
Anders Johnsen 2013/02/20 12:42:02 Comment.
475 bool get _canFireEvent =>
476 _mayFireState && !_hasPendingEvent && _hasSubscribers;
477
478
436 /** Whether there is currently a subscriber on this [Stream]. */ 479 /** Whether there is currently a subscriber on this [Stream]. */
437 bool get _hasSubscribers => _subscriber != null; 480 bool get _hasSubscribers => _subscriber != null;
438 481
439 // ------------------------------------------------------------------- 482 // -------------------------------------------------------------------
440 // Internal implementation. 483 // Internal implementation.
441 484
442 /** 485 /**
443 * Create the new subscription object. 486 * Create the new subscription object.
444 */ 487 */
445 _StreamSubscriptionImpl<T> _createSubscription( 488 _StreamSubscriptionImpl<T> _createSubscription(
446 void onData(T data), 489 void onData(T data),
447 void onError(AsyncError error), 490 void onError(AsyncError error),
448 void onDone(), 491 void onDone(),
449 bool unsubscribeOnError) { 492 bool unsubscribeOnError) {
450 return new _StreamSubscriptionImpl<T>( 493 return new _StreamSubscriptionImpl<T>(
451 this, onData, onError, onDone, unsubscribeOnError); 494 this, onData, onError, onDone, unsubscribeOnError);
452 } 495 }
453 496
454 void _addListener(_StreamListener subscription) { 497 void _addListener(_StreamListener subscription) {
455 if (_hasSubscribers) { 498 if (_hasSubscribers) {
456 throw new StateError("Stream already has subscriber."); 499 throw new StateError("Stream already has subscriber.");
457 } 500 }
458 _subscriber = subscription; 501 _subscriber = subscription;
459 subscription._setSubscribed(0); 502 subscription._setSubscribed(0);
460 _onSubscriptionStateChange(); 503 _callOnSubscriptionStateChange();
461 if (_hasPendingEvent) { 504 if (_hasPendingEvent) {
462 _schedulePendingEvents(); 505 _schedulePendingEvents();
463 } 506 }
464 } 507 }
465 508
466 /** 509 /**
467 * Handle a cancel requested from a [_StreamSubscriptionImpl]. 510 * Handle a cancel requested from a [_StreamSubscriptionImpl].
468 * 511 *
469 * This method is called from [_StreamSubscriptionImpl.cancel]. 512 * This method is called from [_StreamSubscriptionImpl.cancel].
470 * 513 *
471 * If an event is currently firing, the cancel is delayed 514 * If an event is currently firing, the cancel is delayed
472 * until after the subscriber has received the event. 515 * until after the subscriber has received the event.
473 */ 516 */
474 void _cancel(_StreamListener subscriber) { 517 void _cancel(_StreamListener subscriber) {
475 assert(identical(subscriber._source, this)); 518 assert(identical(subscriber._source, this));
476 // We allow unsubscribing the currently firing subscription during 519 // We allow unsubscribing the currently firing subscription during
477 // the event firing, because it is indistinguishable from delaying it since 520 // the event firing, because it is indistinguishable from delaying it since
478 // that event has already received the event. 521 // that event has already received the event.
479 if (!identical(_subscriber, subscriber)) { 522 if (!identical(_subscriber, subscriber)) {
480 // You may unsubscribe more than once, only the first one counts. 523 // You may unsubscribe more than once, only the first one counts.
481 return; 524 return;
482 } 525 }
483 _subscriber = null; 526 _subscriber = null;
484 // Unsubscribing a paused subscription also cancels its pauses. 527 // Unsubscribing a paused subscription also cancels its pauses.
485 int subscriptionPauseCount = subscriber._setUnsubscribed(); 528 int subscriptionPauseCount = subscriber._setUnsubscribed();
486 _updatePauseCount(-subscriptionPauseCount); 529 _updatePauseCount(-subscriptionPauseCount);
487 if (!_isFiring) { 530 if (!_isFiring) {
488 if (subscriptionPauseCount > 0) { 531 _callOnSubscriptionStateChange();
489 _onPauseStateChange();
490 }
491 _onSubscriptionStateChange();
492 } 532 }
493 } 533 }
494 534
495 void _forEachSubscriber( 535 void _forEachSubscriber(
496 void action(_StreamListener<T> subscription)) { 536 void action(_StreamListener<T> subscription)) {
497 assert(!_isPaused); 537 assert(!_isPaused);
498 _StreamListener subscription = _subscriber; 538 _StreamListener subscription = _subscriber;
499 assert(subscription != null); 539 assert(subscription != null);
500 _startFiring(); 540 _startFiring();
501 action(subscription); 541 action(subscription);
(...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after
600 } 640 }
601 } 641 }
602 _endFiring(); 642 _endFiring();
603 } 643 }
604 644
605 void _addListener(_StreamListener listener) { 645 void _addListener(_StreamListener listener) {
606 listener._setSubscribed(_currentEventIdBit); 646 listener._setSubscribed(_currentEventIdBit);
607 bool firstSubscriber = !_hasSubscribers; 647 bool firstSubscriber = !_hasSubscribers;
608 _InternalLinkList.add(this, listener); 648 _InternalLinkList.add(this, listener);
609 if (firstSubscriber) { 649 if (firstSubscriber) {
610 _onSubscriptionStateChange(); 650 _callOnSubscriptionStateChange();
611 } 651 }
612 } 652 }
613 653
614 /** 654 /**
615 * Handle a cancel requested from a [_StreamListener]. 655 * Handle a cancel requested from a [_StreamListener].
616 * 656 *
617 * This method is called from [_StreamListener.cancel]. 657 * This method is called from [_StreamListener.cancel].
618 * 658 *
619 * If an event is currently firing, the cancel is delayed 659 * If an event is currently firing, the cancel is delayed
620 * until after the subscribers have received the event. 660 * until after the subscribers have received the event.
(...skipping 12 matching lines...) Expand all
633 // The listener has been notified of the event (or don't need to, 673 // The listener has been notified of the event (or don't need to,
634 // if it's still pending subscription) so it's safe to remove it. 674 // if it's still pending subscription) so it's safe to remove it.
635 _removeListener(listener); 675 _removeListener(listener);
636 } 676 }
637 // Pause and subscription state changes are reported when we end 677 // Pause and subscription state changes are reported when we end
638 // firing. 678 // firing.
639 } else { 679 } else {
640 bool wasPaused = _isPaused; 680 bool wasPaused = _isPaused;
641 _removeListener(listener); 681 _removeListener(listener);
642 if (wasPaused != _isPaused) _onPauseStateChange(); 682 if (wasPaused != _isPaused) _onPauseStateChange();
643 if (!_hasSubscribers) _onSubscriptionStateChange(); 683 if (!_hasSubscribers) _callOnSubscriptionStateChange();
644 } 684 }
645 } 685 }
646 686
647 /** 687 /**
648 * Removes a listener from this stream and cancels its pauses. 688 * Removes a listener from this stream and cancels its pauses.
649 * 689 *
650 * This is a low-level action that doesn't call [_onSubscriptionStateChange]. 690 * This is a low-level action that doesn't call [_onSubscriptionStateChange].
651 * or [_onPauseStateChange]. 691 * or [_callOnPauseStateChange].
652 */ 692 */
653 void _removeListener(_StreamListener listener) { 693 void _removeListener(_StreamListener listener) {
654 int pauseCount = listener._setUnsubscribed(); 694 int pauseCount = listener._setUnsubscribed();
655 _updatePauseCount(-pauseCount); 695 _updatePauseCount(-pauseCount);
656 _InternalLinkList.remove(listener); 696 _InternalLinkList.remove(listener);
657 } 697 }
658 } 698 }
659 699
660 700
661 /** Stream that generates its own events. */ 701 /** Stream that generates its own events. */
(...skipping 469 matching lines...) Expand 10 before | Expand all | Expand 10 after
1131 _pauseCount = 0; 1171 _pauseCount = 0;
1132 } 1172 }
1133 } 1173 }
1134 1174
1135 class _SingleStreamMultiplexer<T> extends _MultiStreamImpl<T> { 1175 class _SingleStreamMultiplexer<T> extends _MultiStreamImpl<T> {
1136 final _SingleStreamImpl<T> _source; 1176 final _SingleStreamImpl<T> _source;
1137 StreamSubscription<T> _subscription; 1177 StreamSubscription<T> _subscription;
1138 1178
1139 _SingleStreamMultiplexer(this._source); 1179 _SingleStreamMultiplexer(this._source);
1140 1180
1141 void _onPauseStateChange() { 1181 void _callOnPauseStateChange() {
1142 if (_isPaused) { 1182 if (_isPaused) {
1143 if (_subscription != null) { 1183 if (_subscription != null) {
1144 _subscription.pause(); 1184 _subscription.pause();
1145 } 1185 }
1146 } else { 1186 } else {
1147 if (_subscription != null) { 1187 if (_subscription != null) {
1148 _subscription.resume(); 1188 _subscription.resume();
1149 } 1189 }
1150 } 1190 }
1151 } 1191 }
1152 1192
1153 /** 1193 /**
1154 * Subscribe or unsubscribe on [_source] depending on whether 1194 * Subscribe or unsubscribe on [_source] depending on whether
1155 * [_stream] has subscribers. 1195 * [_stream] has subscribers.
1156 */ 1196 */
1157 void _onSubscriptionStateChange() { 1197 void _onSubscriptionStateChange() {
1158 if (_hasSubscribers) { 1198 if (_hasSubscribers) {
1159 assert(_subscription == null); 1199 assert(_subscription == null);
1160 _subscription = _source.listen(this._add, 1200 _subscription = _source.listen(this._add,
1161 onError: this._signalError, 1201 onError: this._signalError,
1162 onDone: this._close); 1202 onDone: this._close);
1163 } else { 1203 } else {
1164 // TODO(lrn): Check why this can happen. 1204 // TODO(lrn): Check why this can happen.
1165 if (_subscription == null) return; 1205 if (_subscription == null) return;
1166 _subscription.cancel(); 1206 _subscription.cancel();
1167 _subscription = null; 1207 _subscription = null;
1168 } 1208 }
1169 } 1209 }
1170 } 1210 }
OLDNEW
« no previous file with comments | « no previous file | tests/lib/async/slow_consumer_test.dart » ('j') | tests/lib/async/slow_consumer_test.dart » ('J')

Powered by Google App Engine
This is Rietveld 408576698