Line data Source code
1 : // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file 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. 4 : 5 : import 'dart:async'; 6 : 7 : import 'package:async/async.dart'; 8 : 9 : import '../stream_channel.dart'; 10 : 11 : /// A [StreamChannel] that enforces the stream channel guarantees. 12 : /// 13 : /// This is exposed via [new StreamChannel.withGuarantees]. 14 : class GuaranteeChannel<T> extends StreamChannelMixin<T> { 15 11 : @override 16 22 : Stream<T> get stream => _streamController.stream; 17 : 18 11 : @override 19 11 : StreamSink<T> get sink => _sink; 20 : late final _GuaranteeSink<T> _sink; 21 : 22 : /// The controller for [stream]. 23 : /// 24 : /// This intermediate controller allows us to continue listening for a done 25 : /// event even after the user has canceled their subscription, and to send our 26 : /// own done event when the sink is closed. 27 : late final StreamController<T> _streamController; 28 : 29 : /// The subscription to the inner stream. 30 : StreamSubscription<T>? _subscription; 31 : 32 : /// Whether the sink has closed, causing the underlying channel to disconnect. 33 : bool _disconnected = false; 34 : 35 11 : GuaranteeChannel(Stream<T> innerStream, StreamSink<T> innerSink, 36 : {bool allowSinkErrors = true}) { 37 22 : _sink = _GuaranteeSink<T>(innerSink, this, allowErrors: allowSinkErrors); 38 : 39 : // Enforce the single-subscription guarantee by changing a broadcast stream 40 : // to single-subscription. 41 11 : if (innerStream.isBroadcast) { 42 : innerStream = 43 0 : innerStream.transform(SingleSubscriptionTransformer<T, T>()); 44 : } 45 : 46 22 : _streamController = StreamController<T>( 47 11 : onListen: () { 48 : // If the sink has disconnected, we've already called 49 : // [_streamController.close]. 50 11 : if (_disconnected) return; 51 : 52 44 : _subscription = innerStream.listen(_streamController.add, 53 22 : onError: _streamController.addError, onDone: () { 54 0 : _sink._onStreamDisconnected(); 55 0 : _streamController.close(); 56 : }); 57 : }, 58 : sync: true); 59 : } 60 : 61 : /// Called by [_GuaranteeSink] when the user closes it. 62 : /// 63 : /// The sink closing indicates that the connection is closed, so the stream 64 : /// should stop emitting events. 65 0 : void _onSinkDisconnected() { 66 0 : _disconnected = true; 67 0 : var subscription = _subscription; 68 0 : if (subscription != null) subscription.cancel(); 69 0 : _streamController.close(); 70 : } 71 : } 72 : 73 : /// The sink for [GuaranteeChannel]. 74 : /// 75 : /// This wraps the inner sink to ignore events and cancel any in-progress 76 : /// [addStream] calls when the underlying channel closes. 77 : class _GuaranteeSink<T> implements StreamSink<T> { 78 : /// The inner sink being wrapped. 79 : final StreamSink<T> _inner; 80 : 81 : /// The [GuaranteeChannel] this belongs to. 82 : final GuaranteeChannel<T> _channel; 83 : 84 0 : @override 85 0 : Future<void> get done => _doneCompleter.future; 86 : final _doneCompleter = Completer(); 87 : 88 : /// Whether connection is disconnected. 89 : /// 90 : /// This can happen because the stream has emitted a done event, or because 91 : /// the user added an error when [_allowErrors] is `false`. 92 : bool _disconnected = false; 93 : 94 : /// Whether the user has called [close]. 95 : bool _closed = false; 96 : 97 : /// The subscription to the stream passed to [addStream], if a stream is 98 : /// currently being added. 99 : StreamSubscription<T>? _addStreamSubscription; 100 : 101 : /// The completer for the future returned by [addStream], if a stream is 102 : /// currently being added. 103 : Completer? _addStreamCompleter; 104 : 105 : /// Whether we're currently adding a stream with [addStream]. 106 22 : bool get _inAddStream => _addStreamSubscription != null; 107 : 108 : /// Whether errors are passed on to the underlying sink. 109 : /// 110 : /// If this is `false`, any error passed to the sink is piped to [done] and 111 : /// the underlying sink is closed. 112 : final bool _allowErrors; 113 : 114 11 : _GuaranteeSink(this._inner, this._channel, {bool allowErrors = true}) 115 : : _allowErrors = allowErrors; 116 : 117 11 : @override 118 : void add(T data) { 119 11 : if (_closed) throw StateError('Cannot add event after closing.'); 120 11 : if (_inAddStream) { 121 0 : throw StateError('Cannot add event while adding stream.'); 122 : } 123 11 : if (_disconnected) return; 124 : 125 22 : _inner.add(data); 126 : } 127 : 128 0 : @override 129 : void addError(error, [StackTrace? stackTrace]) { 130 0 : if (_closed) throw StateError('Cannot add event after closing.'); 131 0 : if (_inAddStream) { 132 0 : throw StateError('Cannot add event while adding stream.'); 133 : } 134 0 : if (_disconnected) return; 135 : 136 0 : _addError(error, stackTrace); 137 : } 138 : 139 : /// Like [addError], but doesn't check to ensure that an error can be added. 140 : /// 141 : /// This is called from [addStream], so it shouldn't fail if a stream is being 142 : /// added. 143 0 : void _addError(Object error, [StackTrace? stackTrace]) { 144 0 : if (_allowErrors) { 145 0 : _inner.addError(error, stackTrace); 146 : return; 147 : } 148 : 149 0 : _doneCompleter.completeError(error, stackTrace); 150 : 151 : // Treat an error like both the stream and sink disconnecting. 152 0 : _onStreamDisconnected(); 153 0 : _channel._onSinkDisconnected(); 154 : 155 : // Ignore errors from the inner sink. We're already surfacing one error, and 156 : // if the user handles it we don't want them to have another top-level. 157 0 : _inner.close().catchError((_) {}); 158 : } 159 : 160 11 : @override 161 : Future<void> addStream(Stream<T> stream) { 162 11 : if (_closed) throw StateError('Cannot add stream after closing.'); 163 11 : if (_inAddStream) { 164 0 : throw StateError('Cannot add stream while adding stream.'); 165 : } 166 11 : if (_disconnected) return Future.value(); 167 : 168 22 : _addStreamCompleter = Completer.sync(); 169 44 : _addStreamSubscription = stream.listen(_inner.add, 170 33 : onError: _addError, onDone: _addStreamCompleter!.complete); 171 33 : return _addStreamCompleter!.future.then((_) { 172 0 : _addStreamCompleter = null; 173 0 : _addStreamSubscription = null; 174 : }); 175 : } 176 : 177 0 : @override 178 : Future<void> close() { 179 0 : if (_inAddStream) { 180 0 : throw StateError('Cannot close sink while adding stream.'); 181 : } 182 : 183 0 : if (_closed) return done; 184 0 : _closed = true; 185 : 186 0 : if (!_disconnected) { 187 0 : _channel._onSinkDisconnected(); 188 0 : _doneCompleter.complete(_inner.close()); 189 : } 190 : 191 0 : return done; 192 : } 193 : 194 : /// Called by [GuaranteeChannel] when the stream emits a done event. 195 : /// 196 : /// The stream being done indicates that the connection is closed, so the 197 : /// sink should stop forwarding events. 198 0 : void _onStreamDisconnected() { 199 0 : _disconnected = true; 200 0 : if (!_doneCompleter.isCompleted) _doneCompleter.complete(); 201 : 202 0 : if (!_inAddStream) return; 203 0 : _addStreamCompleter!.complete(_addStreamSubscription!.cancel()); 204 0 : _addStreamCompleter = null; 205 0 : _addStreamSubscription = null; 206 : } 207 : }