@@ -93,10 +93,18 @@ class _MultiChannel extends StreamChannelMixin implements MultiChannel {
93
93
/// The controller for this channel.
94
94
final _mainController = new StreamChannelController (sync : true );
95
95
96
- /// A map from virtual channel ids to [StreamChannelController] s that should
97
- /// be used to communicate over those channels.
96
+ /// A map from input IDs to [StreamChannelController] s that should be used to
97
+ /// communicate over those channels.
98
98
final _controllers = < int , StreamChannelController > {};
99
99
100
+ /// Input IDs of controllers in [_controllers] that we've received messages
101
+ /// for but that have not yet had a local [virtualChannel] created.
102
+ final _pendingIds = new Set <int >();
103
+
104
+ /// Input IDs of virtual channels that used to exist but have since been
105
+ /// closed.
106
+ final _closedIds = new Set <int >();
107
+
100
108
/// The next id to use for a local virtual channel.
101
109
///
102
110
/// Ids are used to identify virtual channels. Each message is tagged with an
@@ -114,8 +122,9 @@ class _MultiChannel extends StreamChannelMixin implements MultiChannel {
114
122
/// The trick is that each endpoint only uses odd ids for its own channels.
115
123
/// When sending a message over a channel that was created by the remote
116
124
/// 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.
125
+ /// knows that if an incoming message has an odd id, it's coming from a
126
+ /// channel that was originally created remotely, but if it has an even id,
127
+ /// it's coming from a channel that was originally created locally.
119
128
var _nextId = 1 ;
120
129
121
130
_MultiChannel (this ._inner) {
@@ -128,21 +137,28 @@ class _MultiChannel extends StreamChannelMixin implements MultiChannel {
128
137
129
138
_innerStreamSubscription = _inner.stream.listen ((message) {
130
139
var id = message[0 ];
131
- var controller = _controllers[id];
132
140
133
- // A controller might not exist if the channel was closed before an
134
- // incoming message was processed.
135
- if (controller == null ) return ;
141
+ // If the channel was closed before an incoming message was processed,
142
+ // ignore that message.
143
+ if (_closedIds.contains (id)) return ;
144
+
145
+ var controller = _controllers.putIfAbsent (id, () {
146
+ // If we receive a message for a controller that doesn't have a local
147
+ // counterpart yet, create a controller for it to buffer incoming
148
+ // messages for when a local connection is created.
149
+ _pendingIds.add (id);
150
+ return new StreamChannelController (sync : true );
151
+ });
152
+
136
153
if (message.length > 1 ) {
137
154
controller.local.sink.add (message[1 ]);
138
- return ;
155
+ } else {
156
+ // A message without data indicates that the channel has been closed. We
157
+ // can just close the sink here without doing any more cleanup, because
158
+ // the sink closing will cause the stream to emit a done event which
159
+ // will trigger more cleanup.
160
+ controller.local.sink.close ();
139
161
}
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
- controller.local.sink.close ();
146
162
},
147
163
onDone: _closeInnerChannel,
148
164
onError: _mainController.local.sink.addError);
@@ -173,23 +189,30 @@ class _MultiChannel extends StreamChannelMixin implements MultiChannel {
173
189
this , inputId, new Stream .empty (), new NullStreamSink ());
174
190
}
175
191
176
- if (_controllers.containsKey (inputId)) {
192
+ StreamChannelController controller;
193
+ if (_pendingIds.remove (inputId)) {
194
+ // If we've already received messages for this channel, use the controller
195
+ // where those messages are buffered.
196
+ controller = _controllers[inputId];
197
+ } else if (_controllers.containsKey (inputId) ||
198
+ _closedIds.contains (inputId)) {
177
199
throw new ArgumentError ("A virtual channel with id $id already exists." );
200
+ } else {
201
+ controller = new StreamChannelController (sync : true );
202
+ _controllers[inputId] = controller;
178
203
}
179
204
180
- var controller = new StreamChannelController (sync : true );
181
- _controllers[inputId] = controller;
182
205
controller.local.stream.listen (
183
206
(message) => _inner.sink.add ([outputId, message]),
184
207
onDone: () => _closeChannel (inputId, outputId));
185
-
186
208
return new VirtualChannel ._(
187
209
this , outputId, controller.foreign.stream, controller.foreign.sink);
188
210
}
189
211
190
212
/// Closes the virtual channel for which incoming messages have [inputId] and
191
213
/// outgoing messages have [outputId] .
192
214
void _closeChannel (int inputId, int outputId) {
215
+ _closedIds.add (inputId);
193
216
var controller = _controllers.remove (inputId);
194
217
controller.local.sink.close ();
195
218
0 commit comments