LCOV - code coverage report
Current view: top level - stream_channel-2.1.0/lib/src - multi_channel.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 33 57 57.9 %
Date: 2021-11-28 14:37:50 Functions: 0 0 -

          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<T> implements StreamChannel<T> {
      45             :   /// The default input stream.
      46             :   ///
      47             :   /// This connects to the remote [sink].
      48             :   @override
      49             :   Stream<T> get stream;
      50             : 
      51             :   /// The default output stream.
      52             :   ///
      53             :   /// This connects to the remote [stream]. If this is closed, the remote
      54             :   /// [stream] will close, but other virtual channels will remain open and new
      55             :   /// virtual channels may be opened.
      56             :   @override
      57             :   StreamSink<T> get sink;
      58             : 
      59             :   /// Creates a new [MultiChannel] that sends and receives messages over
      60             :   /// [inner].
      61             :   ///
      62             :   /// The inner channel must take JSON-like objects.
      63          22 :   factory MultiChannel(StreamChannel<dynamic> inner) => _MultiChannel<T>(inner);
      64             : 
      65             :   /// Creates a new virtual channel.
      66             :   ///
      67             :   /// If [id] is not passed, this creates a virtual channel from scratch. Before
      68             :   /// it's used, its [VirtualChannel.id] must be sent to the remote endpoint
      69             :   /// where [virtualChannel] should be called with that id.
      70             :   ///
      71             :   /// If [id] is passed, this creates a virtual channel corresponding to the
      72             :   /// channel with that id on the remote channel.
      73             :   ///
      74             :   /// Throws an [ArgumentError] if a virtual channel already exists for [id].
      75             :   /// Throws a [StateError] if the underlying channel is closed.
      76             :   VirtualChannel<T> virtualChannel([int? id]);
      77             : }
      78             : 
      79             : /// The implementation of [MultiChannel].
      80             : ///
      81             : /// This is private so that [VirtualChannel] can inherit from [MultiChannel]
      82             : /// without having to implement all the private members.
      83             : class _MultiChannel<T> extends StreamChannelMixin<T>
      84             :     implements MultiChannel<T> {
      85             :   /// The inner channel over which all communication is conducted.
      86             :   ///
      87             :   /// This will be `null` if the underlying communication channel is closed.
      88             :   StreamChannel<dynamic>? _inner;
      89             : 
      90             :   /// The subscription to [_inner].stream.
      91             :   StreamSubscription<dynamic>? _innerStreamSubscription;
      92             : 
      93          11 :   @override
      94          33 :   Stream<T> get stream => _mainController.foreign.stream;
      95          11 :   @override
      96          33 :   StreamSink<T> get sink => _mainController.foreign.sink;
      97             : 
      98             :   /// The controller for this channel.
      99             :   final _mainController = StreamChannelController<T>(sync: true);
     100             : 
     101             :   /// A map from input IDs to [StreamChannelController]s that should be used to
     102             :   /// communicate over those channels.
     103             :   final _controllers = <int, StreamChannelController<T>>{};
     104             : 
     105             :   /// Input IDs of controllers in [_controllers] that we've received messages
     106             :   /// for but that have not yet had a local [virtualChannel] created.
     107             :   final _pendingIds = <int>{};
     108             : 
     109             :   /// Input IDs of virtual channels that used to exist but have since been
     110             :   /// closed.
     111             :   final _closedIds = <int>{};
     112             : 
     113             :   /// The next id to use for a local virtual channel.
     114             :   ///
     115             :   /// Ids are used to identify virtual channels. Each message is tagged with an
     116             :   /// id; the receiving [MultiChannel] uses this id to look up which
     117             :   /// [VirtualChannel] the message should be dispatched to.
     118             :   ///
     119             :   /// The id scheme for virtual channels is somewhat complicated. This is
     120             :   /// necessary to ensure that there are no conflicts even when both endpoints
     121             :   /// have virtual channels with the same id; since both endpoints can send and
     122             :   /// receive messages across each virtual channel, a naïve scheme would make it
     123             :   /// impossible to tell whether a message was from a channel that originated in
     124             :   /// the remote endpoint or a reply on a channel that originated in the local
     125             :   /// endpoint.
     126             :   ///
     127             :   /// The trick is that each endpoint only uses odd ids for its own channels.
     128             :   /// When sending a message over a channel that was created by the remote
     129             :   /// endpoint, the channel's id plus one is used. This way each [MultiChannel]
     130             :   /// knows that if an incoming message has an odd id, it's coming from a
     131             :   /// channel that was originally created remotely, but if it has an even id,
     132             :   /// it's coming from a channel that was originally created locally.
     133             :   var _nextId = 1;
     134             : 
     135          11 :   _MultiChannel(StreamChannel<dynamic> inner) : _inner = inner {
     136             :     // The default connection is a special case which has id 0 on both ends.
     137             :     // This allows it to begin connected without having to send over an id.
     138          33 :     _controllers[0] = _mainController;
     139          44 :     _mainController.local.stream.listen(
     140          55 :         (message) => _inner!.sink.add(<Object?>[0, message]),
     141           0 :         onDone: () => _closeChannel(0, 0));
     142             : 
     143          66 :     _innerStreamSubscription = _inner!.stream.cast<List>().listen((message) {
     144          11 :       var id = message[0] as int;
     145             : 
     146             :       // If the channel was closed before an incoming message was processed,
     147             :       // ignore that message.
     148          22 :       if (_closedIds.contains(id)) return;
     149             : 
     150          22 :       var controller = _controllers.putIfAbsent(id, () {
     151             :         // If we receive a message for a controller that doesn't have a local
     152             :         // counterpart yet, create a controller for it to buffer incoming
     153             :         // messages for when a local connection is created.
     154           0 :         _pendingIds.add(id);
     155           0 :         return StreamChannelController(sync: true);
     156             :       });
     157             : 
     158          22 :       if (message.length > 1) {
     159          44 :         controller.local.sink.add(message[1] as T);
     160             :       } else {
     161             :         // A message without data indicates that the channel has been closed. We
     162             :         // can just close the sink here without doing any more cleanup, because
     163             :         // the sink closing will cause the stream to emit a done event which
     164             :         // will trigger more cleanup.
     165           0 :         controller.local.sink.close();
     166             :       }
     167             :     },
     168          11 :         onDone: _closeInnerChannel,
     169          44 :         onError: _mainController.local.sink.addError);
     170             :   }
     171             : 
     172          11 :   @override
     173             :   VirtualChannel<T> virtualChannel([int? id]) {
     174             :     int inputId;
     175             :     int outputId;
     176             :     if (id != null) {
     177             :       // Since the user is passing in an id, we're connected to a remote
     178             :       // VirtualChannel. This means messages they send over this channel will
     179             :       // have the original odd id, but our replies will have an even id.
     180             :       inputId = id;
     181          11 :       outputId = id + 1;
     182             :     } else {
     183             :       // Since we're generating an id, we originated this VirtualChannel. This
     184             :       // means messages we send over this channel will have the original odd id,
     185             :       // but the remote channel's replies will have an even id.
     186          22 :       inputId = _nextId + 1;
     187          11 :       outputId = _nextId;
     188          22 :       _nextId += 2;
     189             :     }
     190             : 
     191             :     // If the inner channel has already closed, create new virtual channels in a
     192             :     // closed state.
     193          11 :     if (_inner == null) {
     194           0 :       return VirtualChannel._(this, inputId, Stream.empty(), NullStreamSink());
     195             :     }
     196             : 
     197             :     late StreamChannelController<T> controller;
     198          22 :     if (_pendingIds.remove(inputId)) {
     199             :       // If we've already received messages for this channel, use the controller
     200             :       // where those messages are buffered.
     201           0 :       controller = _controllers[inputId]!;
     202          22 :     } else if (_controllers.containsKey(inputId) ||
     203          22 :         _closedIds.contains(inputId)) {
     204           0 :       throw ArgumentError('A virtual channel with id $id already exists.');
     205             :     } else {
     206          11 :       controller = StreamChannelController(sync: true);
     207          22 :       _controllers[inputId] = controller;
     208             :     }
     209             : 
     210          33 :     controller.local.stream.listen(
     211          55 :         (message) => _inner!.sink.add(<Object?>[outputId, message]),
     212           0 :         onDone: () => _closeChannel(inputId, outputId));
     213          11 :     return VirtualChannel._(
     214          44 :         this, outputId, controller.foreign.stream, controller.foreign.sink);
     215             :   }
     216             : 
     217             :   /// Closes the virtual channel for which incoming messages have [inputId] and
     218             :   /// outgoing messages have [outputId].
     219           0 :   void _closeChannel(int inputId, int outputId) {
     220           0 :     _closedIds.add(inputId);
     221           0 :     var controller = _controllers.remove(inputId)!;
     222           0 :     controller.local.sink.close();
     223             : 
     224           0 :     if (_inner == null) return;
     225             : 
     226             :     // A message without data indicates that the virtual channel has been
     227             :     // closed.
     228           0 :     _inner!.sink.add([outputId]);
     229           0 :     if (_controllers.isEmpty) _closeInnerChannel();
     230             :   }
     231             : 
     232             :   /// Closes the underlying communication channel.
     233           0 :   void _closeInnerChannel() {
     234           0 :     _inner!.sink.close();
     235           0 :     _innerStreamSubscription!.cancel();
     236           0 :     _inner = null;
     237             : 
     238             :     // Convert this to a list because the close is dispatched synchronously, and
     239             :     // that could conceivably remove a controller from [_controllers].
     240           0 :     for (var controller in List.from(_controllers.values)) {
     241           0 :       controller.local.sink.close();
     242             :     }
     243           0 :     _controllers.clear();
     244             :   }
     245             : }
     246             : 
     247             : /// A virtual channel created by [MultiChannel].
     248             : ///
     249             : /// This implements [MultiChannel] for convenience.
     250             : /// [VirtualChannel.virtualChannel] is semantically identical to the parent's
     251             : /// [MultiChannel.virtualChannel].
     252             : class VirtualChannel<T> extends StreamChannelMixin<T>
     253             :     implements MultiChannel<T> {
     254             :   /// The [MultiChannel] that created this.
     255             :   final MultiChannel<T> _parent;
     256             : 
     257             :   /// The identifier for this channel.
     258             :   ///
     259             :   /// This can be sent across the [MultiChannel] to provide the remote endpoint
     260             :   /// a means to connect to this channel. Nothing about this is guaranteed
     261             :   /// except that it will be JSON-serializable.
     262             :   final int id;
     263             : 
     264             :   @override
     265             :   final Stream<T> stream;
     266             :   @override
     267             :   final StreamSink<T> sink;
     268             : 
     269          11 :   VirtualChannel._(this._parent, this.id, this.stream, this.sink);
     270             : 
     271           0 :   @override
     272           0 :   VirtualChannel<T> virtualChannel([id]) => _parent.virtualChannel(id);
     273             : }

Generated by: LCOV version 1.14