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