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 class that multiplexes multiple virtual channels across a single
12 : /// underlying transport layer.
13 : ///
14 : /// This should be connected to another [MultiChannel] on the other end of the
15 : /// underlying channel. It starts with a single default virtual channel,
16 : /// accessible via [stream] and [sink]. Additional virtual channels can be
17 : /// created with [virtualChannel].
18 : ///
19 : /// When a virtual channel is created by one endpoint, the other must connect to
20 : /// it before messages may be sent through it. The first endpoint passes its
21 : /// [VirtualChannel.id] to the second, which then creates a channel from that id
22 : /// also using [virtualChannel]. For example:
23 : ///
24 : /// ```dart
25 : /// // First endpoint
26 : /// var virtual = multiChannel.virtualChannel();
27 : /// multiChannel.sink.add({
28 : /// "channel": virtual.id
29 : /// });
30 : ///
31 : /// // Second endpoint
32 : /// multiChannel.stream.listen((message) {
33 : /// var virtual = multiChannel.virtualChannel(message["channel"]);
34 : /// // ...
35 : /// });
36 : /// ```
37 : ///
38 : /// Sending errors across a [MultiChannel] is not supported. Any errors from the
39 : /// underlying stream will be reported only via the default
40 : /// [MultiChannel.stream].
41 : ///
42 : /// Each virtual channel may be closed individually. When all of them are
43 : /// closed, the underlying [StreamSink] is closed automatically.
44 : abstract class MultiChannel implements StreamChannel {
45 : /// The default input stream.
46 : ///
47 : /// This connects to the remote [sink].
48 : Stream get stream;
49 :
50 : /// The default output stream.
51 : ///
52 : /// This connects to the remote [stream]. If this is closed, the remote
53 : /// [stream] will close, but other virtual channels will remain open and new
54 : /// virtual channels may be opened.
55 : StreamSink get sink;
56 :
57 : /// Creates a new [MultiChannel] that sends and receives messages over
58 : /// [inner].
59 : ///
60 : /// The inner channel must take JSON-like objects.
61 0 : factory MultiChannel(StreamChannel inner) => new _MultiChannel(inner);
62 :
63 : /// Creates a new virtual channel.
64 : ///
65 : /// If [id] is not passed, this creates a virtual channel from scratch. Before
66 : /// it's used, its [VirtualChannel.id] must be sent to the remote endpoint
67 : /// where [virtualChannel] should be called with that id.
68 : ///
69 : /// If [id] is passed, this creates a virtual channel corresponding to the
70 : /// channel with that id on the remote channel.
71 : ///
72 : /// Throws an [ArgumentError] if a virtual channel already exists for [id].
73 : /// Throws a [StateError] if the underlying channel is closed.
74 : VirtualChannel virtualChannel([id]);
75 : }
76 :
77 : /// The implementation of [MultiChannel].
78 : ///
79 : /// This is private so that [VirtualChannel] can inherit from [MultiChannel]
80 : /// without having to implement all the private members.
81 : class _MultiChannel extends StreamChannelMixin implements MultiChannel {
82 : /// The inner channel over which all communication is conducted.
83 : ///
84 : /// This will be `null` if the underlying communication channel is closed.
85 : StreamChannel _inner;
86 :
87 : /// The subscription to [_inner.stream].
88 : StreamSubscription _innerStreamSubscription;
89 :
90 0 : Stream get stream => _mainController.foreign.stream;
91 0 : StreamSink get sink => _mainController.foreign.sink;
92 :
93 : /// The controller for this channel.
94 : final _mainController = new StreamChannelController(sync: true);
95 :
96 : /// A map from virtual channel ids to [StreamChannelController]s that should
97 : /// be used to communicate over those channels.
98 : final _controllers = <int, StreamChannelController>{};
99 :
100 : /// The next id to use for a local virtual channel.
101 : ///
102 : /// Ids are used to identify virtual channels. Each message is tagged with an
103 : /// id; the receiving [MultiChannel] uses this id to look up which
104 : /// [VirtualChannel] the message should be dispatched to.
105 : ///
106 : /// The id scheme for virtual channels is somewhat complicated. This is
107 : /// necessary to ensure that there are no conflicts even when both endpoints
108 : /// have virtual channels with the same id; since both endpoints can send and
109 : /// receive messages across each virtual channel, a naïve scheme would make it
110 : /// impossible to tell whether a message was from a channel that originated in
111 : /// the remote endpoint or a reply on a channel that originated in the local
112 : /// endpoint.
113 : ///
114 : /// The trick is that each endpoint only uses odd ids for its own channels.
115 : /// When sending a message over a channel that was created by the remote
116 : /// endpoint, the channel's id plus one is used. This way each [MultiChannel]
117 : /// knows that if an incoming message has an odd id, it's using the local id
118 : /// scheme, but if it has an even id, it's using the remote id scheme.
119 : var _nextId = 1;
120 :
121 0 : _MultiChannel(this._inner) {
122 : // The default connection is a special case which has id 0 on both ends.
123 : // This allows it to begin connected without having to send over an id.
124 0 : _controllers[0] = _mainController;
125 0 : _mainController.local.stream.listen(
126 0 : (message) => _inner.sink.add([0, message]),
127 0 : onDone: () => _closeChannel(0, 0));
128 :
129 0 : _innerStreamSubscription = _inner.stream.listen((message) {
130 0 : var id = message[0];
131 0 : var controller = _controllers[id];
132 :
133 : // A controller might not exist if the channel was closed before an
134 : // incoming message was processed.
135 : if (controller == null) return;
136 0 : if (message.length > 1) {
137 0 : controller.local.sink.add(message[1]);
138 : return;
139 : }
140 :
141 : // A message without data indicates that the channel has been closed. We
142 : // can only close the sink here without doing any more cleanup, because
143 : // the sink closing will cause the stream to emit a done event which will
144 : // trigger more cleanup.
145 0 : controller.local.sink.close();
146 : },
147 0 : onDone: _closeInnerChannel,
148 0 : onError: _mainController.local.sink.addError);
149 : }
150 :
151 : VirtualChannel virtualChannel([id]) {
152 : var inputId;
153 : var outputId;
154 : if (id != null) {
155 : // Since the user is passing in an id, we're connected to a remote
156 : // VirtualChannel. This means messages they send over this channel will
157 : // have the original odd id, but our replies will have an even id.
158 : inputId = id;
159 0 : outputId = (id as int) + 1;
160 : } else {
161 : // Since we're generating an id, we originated this VirtualChannel. This
162 : // means messages we send over this channel will have the original odd id,
163 : // but the remote channel's replies will have an even id.
164 0 : inputId = _nextId + 1;
165 0 : outputId = _nextId;
166 0 : _nextId += 2;
167 : }
168 :
169 : // If the inner channel has already closed, create new virtual channels in a
170 : // closed state.
171 0 : if (_inner == null) {
172 0 : return new VirtualChannel._(
173 0 : this, inputId, new Stream.empty(), new NullStreamSink());
174 : }
175 :
176 0 : if (_controllers.containsKey(inputId)) {
177 0 : throw new ArgumentError("A virtual channel with id $id already exists.");
178 : }
179 :
180 0 : var controller = new StreamChannelController(sync: true);
181 0 : _controllers[inputId] = controller;
182 0 : controller.local.stream.listen(
183 0 : (message) => _inner.sink.add([outputId, message]),
184 0 : onDone: () => _closeChannel(inputId, outputId));
185 :
186 0 : return new VirtualChannel._(
187 0 : this, outputId, controller.foreign.stream, controller.foreign.sink);
188 : }
189 :
190 : /// Closes the virtual channel for which incoming messages have [inputId] and
191 : /// outgoing messages have [outputId].
192 : void _closeChannel(int inputId, int outputId) {
193 0 : var controller = _controllers.remove(inputId);
194 0 : controller.local.sink.close();
195 :
196 0 : if (_inner == null) return;
197 :
198 : // A message without data indicates that the virtual channel has been
199 : // closed.
200 0 : _inner.sink.add([outputId]);
201 0 : if (_controllers.isEmpty) _closeInnerChannel();
202 : }
203 :
204 : /// Closes the underlying communication channel.
205 : void _closeInnerChannel() {
206 0 : _inner.sink.close();
207 0 : _innerStreamSubscription.cancel();
208 0 : _inner = null;
209 :
210 : // Convert this to a list because the close is dispatched synchronously, and
211 : // that could conceivably remove a controller from [_controllers].
212 0 : for (var controller in new List.from(_controllers.values)) {
213 0 : controller.local.sink.close();
214 : }
215 0 : _controllers.clear();
216 : }
217 : }
218 :
219 : /// A virtual channel created by [MultiChannel].
220 : ///
221 : /// This implements [MultiChannel] for convenience.
222 : /// [VirtualChannel.virtualChannel] is semantically identical to the parent's
223 : /// [MultiChannel.virtualChannel].
224 : class VirtualChannel extends StreamChannelMixin implements MultiChannel {
225 : /// The [MultiChannel] that created this.
226 : final MultiChannel _parent;
227 :
228 : /// The identifier for this channel.
229 : ///
230 : /// This can be sent across the [MultiChannel] to provide the remote endpoint
231 : /// a means to connect to this channel. Nothing about this is guaranteed
232 : /// except that it will be JSON-serializable.
233 : final id;
234 :
235 : final Stream stream;
236 : final StreamSink sink;
237 :
238 0 : VirtualChannel._(this._parent, this.id, this.stream, this.sink);
239 :
240 0 : VirtualChannel virtualChannel([id]) => _parent.virtualChannel(id);
241 : }
|