LCOV - code coverage report
Current view: top level - stream_channel-1.6.2/lib/src - multi_channel.dart (source / functions) Hit Total Coverage
Test: coverage.lcov Lines: 0 45 0.0 %
Date: 2017-10-10 20:17:03 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 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             : }

Generated by: LCOV version 1.13