├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── benchmark.js ├── example.js ├── index.js ├── package.json └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.10' 4 | - '0.12' 5 | - '4' 6 | - '6' 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Mathias Buus 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # stream-channels 2 | 3 | Simple one-way stream multiplexer with very few features. 4 | For two-way (full duplex) multiplexing see the [multiplex](https://github.com/maxogden/multiplex) module. 5 | 6 | ``` 7 | npm install stream-channels 8 | ``` 9 | 10 | [![build status](http://img.shields.io/travis/mafintosh/stream-channels.svg?style=flat)](http://travis-ci.org/mafintosh/stream-channels) 11 | 12 | ## Usage 13 | 14 | ``` js 15 | var channels = require('stream-channels') 16 | var stream = channels() 17 | 18 | stream.on('channel', function (channel) { 19 | console.log('new channel') 20 | channel.on('data', console.log) 21 | channel.on('end', function () { 22 | console.log('(no more data)') 23 | }) 24 | }) 25 | 26 | var ch1 = stream.createChannel() 27 | var ch2 = stream.createChannel() 28 | 29 | ch1.write('hello') 30 | ch2.write('world') 31 | 32 | ch1.end() 33 | ch2.end() 34 | 35 | stream.pipe(stream) 36 | ``` 37 | 38 | ## API 39 | 40 | #### `var stream = channels([options], [onchannel])` 41 | 42 | Create a new instance. Options include: 43 | 44 | ``` js 45 | { 46 | limit: maxChannelsAllowedOpen // defaults to 1024 47 | } 48 | ``` 49 | 50 | #### `var writeableStream = stream.createChannel()` 51 | 52 | Create a new channel. 53 | 54 | #### `stream.on('channel', readableStream)` 55 | 56 | Emitted when a remote creates a new channel. Will emit the data the remote writes to it. 57 | 58 | #### `stream.setTimeout(ms, [ontimeout])` 59 | 60 | Emit a timeout when if the stream is inactive for ~ms milliseconds. 61 | Will start a heartbeat as well. 62 | 63 | ## Wire Protocol 64 | 65 | The wire protocol is as follows. 66 | 67 | ``` 68 | ------------------------------ 69 | | length | channel-id | data | 70 | ------------------------------ 71 | ``` 72 | 73 | * Length is a [varint](https://github.com/chrisdickinson/varint) containing the binary length of channel-id and data. 74 | * Channel id is a [varint](https://github.com/chrisdickinson/varint) representing the current channel. 75 | * Data is the buffer you wrote to a channel 76 | 77 | Channels are lazily opened., The first time you receive data on a channel id, that channel is opened. 78 | Receiving an empty data buffer indicates that the channel is closed. 79 | 80 | Messages received with length 0 should be ignored and can be used as a keep alive signal. 81 | 82 | ## Back pressure 83 | 84 | Back pressure will trigger on all channels when the *slowest* channel starts to back pressure. 85 | 86 | ## License 87 | 88 | MIT 89 | -------------------------------------------------------------------------------- /benchmark.js: -------------------------------------------------------------------------------- 1 | var bench = require('nanobench') 2 | var zero = require('dev-zero-stream') 3 | var channels = require('stream-channels') 4 | 5 | bench('1 channel with 1gb data', function (b) { 6 | var ch = channels(function (stream) { 7 | stream.resume() 8 | stream.on('end', function () { 9 | b.end() 10 | }) 11 | }) 12 | 13 | zero(1024 * 1024 * 1024).pipe(ch.createChannel()) 14 | 15 | ch.pipe(ch) 16 | }) 17 | 18 | bench('5 channels with 1gb data each', function (b) { 19 | var missing = 5 20 | 21 | var ch = channels(function (stream) { 22 | stream.resume() 23 | stream.on('end', function () { 24 | if (!--missing) b.end() 25 | }) 26 | }) 27 | 28 | for (var i = 0; i < 5; i++) { 29 | zero(1024 * 1024 * 1024).pipe(ch.createChannel()) 30 | } 31 | 32 | ch.pipe(ch) 33 | }) 34 | 35 | bench('1 channel with 1gb data (preallocated)', function (b) { 36 | var ch = channels(function (stream) { 37 | stream.resume() 38 | stream.on('end', function () { 39 | b.end() 40 | }) 41 | }) 42 | 43 | var stream = ch.createChannel({preallocated: true}) 44 | var buf = stream.preallocate(65532) 45 | 46 | zero(1024 * 1024 * 1024, buf).pipe(stream) 47 | 48 | ch.pipe(ch) 49 | }) 50 | 51 | bench('5 channel with 1gb data each (preallocated)', function (b) { 52 | var missing = 5 53 | 54 | var ch = channels(function (stream) { 55 | stream.resume() 56 | stream.on('end', function () { 57 | if (!--missing) b.end() 58 | }) 59 | }) 60 | 61 | for (var i = 0; i < 5; i++) { 62 | var stream = ch.createChannel({preallocated: true}) 63 | var buf = stream.preallocate(65532) 64 | zero(1024 * 1024 * 1024, buf).pipe(stream) 65 | } 66 | 67 | ch.pipe(ch) 68 | }) 69 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | var channels = require('./') 2 | 3 | var stream = channels() 4 | 5 | stream.on('channel', function (channel) { 6 | console.log('new channel') 7 | channel.on('data', console.log) 8 | channel.on('end', function () { 9 | console.log('(no more data)') 10 | }) 11 | }) 12 | 13 | var ch1 = stream.createChannel() 14 | var ch2 = stream.createChannel() 15 | 16 | ch1.write('hello') 17 | ch2.write('world') 18 | 19 | ch1.end() 20 | ch2.end() 21 | 22 | stream.pipe(stream) 23 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var lpstream = require('length-prefixed-stream') 2 | var duplexify = require('duplexify') 3 | var inherits = require('inherits') 4 | var varint = require('varint') 5 | var stream = require('readable-stream') 6 | 7 | var KEEP_ALIVE = new Buffer([0]) 8 | 9 | module.exports = StreamChannels 10 | 11 | function StreamChannels (opts, onchannel) { 12 | if (!(this instanceof StreamChannels)) return new StreamChannels(opts, onchannel) 13 | if (typeof opts === 'function') { 14 | onchannel = opts 15 | opts = null 16 | } 17 | if (!opts) opts = {} 18 | 19 | duplexify.call(this) 20 | 21 | var self = this 22 | 23 | this.destroyed = false 24 | this.limit = opts.limit || 1024 25 | this.state = null // set by someone else. here for perf 26 | 27 | this._outgoing = [] 28 | this._incoming = [] 29 | this._waiting = 0 30 | this._encode = new Sink() 31 | this._decode = lpstream.decode({allowEmpty: true, limit: opts.messageLimit || 5 * 1024 * 1024}) 32 | this._decode.on('data', parse) 33 | 34 | this._keepAlive = 0 35 | this._remoteKeepAlive = 0 36 | this._interval = null 37 | 38 | this.on('finish', this._finalize) 39 | this.on('close', this._finalize) 40 | 41 | this.setWritable(this._decode) 42 | this.setReadable(this._encode) 43 | 44 | if (onchannel) this.on('channel', onchannel) 45 | 46 | function parse (data) { 47 | self._parse(data) 48 | } 49 | } 50 | 51 | inherits(StreamChannels, duplexify) 52 | 53 | StreamChannels.prototype.setTimeout = function (ms, ontimeout) { 54 | if (this.destroyed) return 55 | 56 | if (ontimeout) this.once('timeout', ontimeout) 57 | 58 | var self = this 59 | 60 | this._keepAlive = 0 61 | this._remoteKeepAlive = 0 62 | 63 | clearInterval(this._interval) 64 | this._interval = setInterval(kick, (ms / 4) | 0) 65 | if (this._interval.unref) this._interval.unref() 66 | 67 | function kick () { 68 | self._kick() 69 | } 70 | } 71 | 72 | StreamChannels.prototype.createChannel = function (opts) { 73 | if (this.destroyed) return null 74 | 75 | var next = this._outgoing.indexOf(null) 76 | if (next === -1) next = this._outgoing.push(null) - 1 77 | 78 | var ch = new OutgoingChannel(this, next, opts) 79 | this._outgoing[next] = ch 80 | 81 | return ch 82 | } 83 | 84 | StreamChannels.prototype._parse = function (data) { 85 | this._remoteKeepAlive = 0 86 | 87 | if (this.destroyed) return 88 | if (!data.length) return 89 | 90 | var offset = 0 91 | var channel = decodeVarint(data, offset) 92 | if (channel === -1) return this.destroy(new Error('Invalid channel id')) 93 | 94 | offset += varint.decode.bytes 95 | 96 | if (channel >= this.limit) return this.destroy(new Error('Too many open channels')) 97 | while (channel >= this._incoming.length) this._incoming.push(null) 98 | 99 | var ch = this._incoming[channel] 100 | 101 | if (!ch) { // new channel 102 | ch = new IncomingChannel(this, channel) 103 | this._incoming[channel] = ch 104 | this.emit('channel', ch) 105 | } 106 | 107 | if (data.length === offset) { // channel close 108 | this._incoming[channel] = null 109 | } 110 | 111 | ch._push(data.slice(offset)) 112 | } 113 | 114 | StreamChannels.prototype.destroy = function (err) { 115 | if (this.destroyed) return 116 | this.destroyed = true 117 | 118 | clearInterval(this._interval) 119 | 120 | if (err) this.emit('error', err) 121 | this.emit('close') 122 | 123 | var i = 0 124 | 125 | for (i = 0; i < this._incoming.length; i++) { 126 | if (this._incoming[i]) this._incoming[i].destroy() 127 | } 128 | 129 | for (i = 0; i < this._outgoing.length; i++) { 130 | if (this._outgoing[i]) this._outgoing[i].destroy() 131 | } 132 | } 133 | 134 | StreamChannels.prototype.finalize = function () { 135 | this._encode.end() 136 | } 137 | 138 | StreamChannels.prototype._kick = function () { 139 | if (this._remoteKeepAlive > 4) { 140 | clearInterval(this._interval) 141 | this.emit('timeout') 142 | return 143 | } 144 | 145 | this.emit('tick') 146 | this._remoteKeepAlive++ 147 | 148 | if (this._keepAlive > 2) { 149 | this._encode.push(KEEP_ALIVE) 150 | this._keepAlive = 0 151 | } else { 152 | this._keepAlive++ 153 | } 154 | } 155 | 156 | StreamChannels.prototype._finalize = function () { 157 | this.destroy() 158 | } 159 | 160 | function Sink () { 161 | stream.Readable.call(this) 162 | this._pending = [] 163 | this._ended = false 164 | } 165 | 166 | inherits(Sink, stream.Readable) 167 | 168 | Sink.prototype._read = function () { 169 | var pending = this._pending 170 | if (pending.length > 1) this._pending = [] 171 | while (pending.length) pending.shift()() 172 | } 173 | 174 | Sink.prototype._push = function (data, cb) { 175 | if (this._ended) return cb() 176 | if (this.push(data)) cb() 177 | else this._pending.push(cb) 178 | } 179 | 180 | Sink.prototype.end = function () { 181 | this._ended = true 182 | this.push(null) 183 | } 184 | 185 | function IncomingChannel (parent, channel) { 186 | stream.Readable.call(this) 187 | 188 | this.id = channel 189 | this.idLength = varint.encodingLength(channel) 190 | this.stream = parent 191 | this.destroyed = false 192 | this.state = null // set by someone else. here for perf 193 | 194 | this._waiting = 0 195 | } 196 | 197 | inherits(IncomingChannel, stream.Readable) 198 | 199 | IncomingChannel.prototype.destroy = function (err) { 200 | if (this.destroyed) return 201 | this.destroyed = true 202 | if (err) this.emit('error', err) 203 | this.emit('close') 204 | } 205 | 206 | IncomingChannel.prototype._push = function (data) { 207 | if (this.destroyed) return 208 | 209 | if (!data.length) { 210 | this.push(null) 211 | this.destroy() 212 | return 213 | } 214 | 215 | if (!this.push(data)) { 216 | this.stream._waiting++ 217 | this._waiting++ 218 | if (this.stream._waiting === 1) this.stream._decode.pause() 219 | } 220 | } 221 | 222 | IncomingChannel.prototype._read = function () { 223 | if (!this._waiting) return 224 | this.stream._waiting -= this._waiting 225 | this._waiting = 0 226 | if (this.stream._waiting === 0) this.stream._decode.resume() 227 | } 228 | 229 | function OutgoingChannel (parent, channel, opts) { 230 | stream.Writable.call(this, opts) 231 | 232 | this.id = channel 233 | this.idLength = varint.encodingLength(channel) 234 | this.stream = parent 235 | this.destroyed = false 236 | this.state = null // set by someone else. here for perf 237 | 238 | this._preallocated = !!(opts && opts.preallocated) 239 | this.on('finish', this._finalize) 240 | } 241 | 242 | inherits(OutgoingChannel, stream.Writable) 243 | 244 | OutgoingChannel.prototype.destroy = function (err) { 245 | if (this.destroyed) return 246 | this.destroyed = true 247 | this.stream._encode._push(this.preallocate(0), noop) 248 | this.stream._outgoing[this.id] = null 249 | if (err) this.emit('error', err) 250 | this.emit('close') 251 | } 252 | 253 | OutgoingChannel.prototype.preallocate = function (length) { 254 | var payload = this.idLength + length 255 | var buf = new Buffer(varint.encodingLength(payload) + payload) 256 | var offset = 0 257 | 258 | varint.encode(payload, buf, offset) 259 | offset += varint.encode.bytes 260 | 261 | varint.encode(this.id, buf, offset) 262 | offset += varint.encode.bytes 263 | 264 | return buf 265 | } 266 | 267 | OutgoingChannel.prototype._finalize = function () { 268 | this.destroy() 269 | } 270 | 271 | OutgoingChannel.prototype._write = function (data, enc, cb) { 272 | if (this.stream.destroyed) return cb() 273 | 274 | this.stream._keepAlive = 0 275 | 276 | if (this._preallocated) { 277 | this.stream._encode._push(data, cb) 278 | } else { 279 | var buf = this.preallocate(data.length) 280 | data.copy(buf, buf.length - data.length) 281 | this.stream._encode._push(buf, cb) 282 | } 283 | } 284 | 285 | function noop () {} 286 | 287 | function decodeVarint (buf, offset) { 288 | try { 289 | return varint.decode(buf, offset) 290 | } catch (err) { 291 | return -1 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stream-channels", 3 | "version": "1.4.1", 4 | "description": "Simple one-way stream multiplexer with very few features", 5 | "main": "index.js", 6 | "dependencies": { 7 | "duplexify": "^3.5.0", 8 | "inherits": "^2.0.3", 9 | "length-prefixed-stream": "^1.5.0", 10 | "readable-stream": "^2.1.5", 11 | "varint": "^5.0.0" 12 | }, 13 | "devDependencies": { 14 | "dev-zero-stream": "^1.1.0", 15 | "nanobench": "^1.0.3", 16 | "standard": "^8.4.0", 17 | "tape": "^4.6.2" 18 | }, 19 | "scripts": { 20 | "test": "standard && tape test.js", 21 | "bench": "nanobench benchmark.js" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/mafintosh/stream-channels.git" 26 | }, 27 | "author": "Mathias Buus (@mafintosh)", 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/mafintosh/stream-channels/issues" 31 | }, 32 | "homepage": "https://github.com/mafintosh/stream-channels" 33 | } 34 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var tape = require('tape') 2 | var channels = require('./') 3 | 4 | tape('create channel', function (t) { 5 | var stream = channels(function (channel) { 6 | channel.on('data', function (data) { 7 | t.same(data, new Buffer('hello'), 'first buffer is hello') 8 | }) 9 | channel.on('end', function () { 10 | t.pass('stream ended') 11 | t.end() 12 | }) 13 | }) 14 | 15 | var channel = stream.createChannel() 16 | 17 | channel.write('hello') 18 | channel.end() 19 | 20 | stream.pipe(stream) 21 | }) 22 | 23 | tape('create multiple channels', function (t) { 24 | t.plan(10) 25 | 26 | var stream = channels(function (channel) { 27 | channel.on('data', function (data) { 28 | t.same(data, new Buffer('hello #' + channel.id), 'first buffer is hello') 29 | }) 30 | channel.on('end', function () { 31 | t.pass('stream ended') 32 | }) 33 | }) 34 | 35 | for (var i = 0; i < 5; i++) { 36 | var channel = stream.createChannel() 37 | channel.write('hello #' + i) 38 | channel.end() 39 | } 40 | 41 | stream.pipe(stream) 42 | }) 43 | 44 | tape('limit exceeded', function (t) { 45 | var stream = channels({limit: 1}, function (channel) { 46 | channel.resume() 47 | }) 48 | 49 | for (var i = 0; i < 2; i++) { 50 | var channel = stream.createChannel() 51 | channel.write('hello #' + i) 52 | channel.end() 53 | } 54 | 55 | stream.on('error', function (err) { 56 | t.ok(err, 'too many channels') 57 | t.end() 58 | }) 59 | 60 | stream.pipe(stream) 61 | }) 62 | 63 | tape('re-use channels', function (t) { 64 | t.plan(10) 65 | 66 | var runs = 0 67 | var stream = channels(function (channel) { 68 | t.same(channel.id, 0, 'channel id is re-used') 69 | }) 70 | 71 | loop() 72 | 73 | function loop () { 74 | if (runs++ === 10) return 75 | var channel = stream.createChannel() 76 | channel.on('finish', loop) 77 | channel.write('hello') 78 | channel.end() 79 | } 80 | 81 | stream.pipe(stream) 82 | }) 83 | 84 | tape('back pressure', function (t) { 85 | var stream = channels() 86 | 87 | var a = stream.createChannel() 88 | var b = stream.createChannel() 89 | var buf = new Buffer(4 * 1024) 90 | 91 | for (var i = 0; i < 1000; i++) { 92 | a.write(buf) 93 | b.write(buf) 94 | } 95 | 96 | t.notOk(a.write('more'), 'a hit hwm') 97 | t.notOk(b.write('more'), 'b hit hwm') 98 | 99 | a.once('drain', function () { 100 | t.fail('a should not drain') 101 | }) 102 | 103 | b.once('drain', function () { 104 | t.fail('b should not drain') 105 | }) 106 | 107 | setTimeout(function () { 108 | t.end() 109 | }, 100) 110 | 111 | stream.pipe(stream) 112 | }) 113 | 114 | tape('back pressure (slowest one is the bottleneck)', function (t) { 115 | var stream = channels(function (channel) { 116 | if (channel.id) channel.resume() 117 | else channel.pause() 118 | }) 119 | 120 | var a = stream.createChannel() 121 | var b = stream.createChannel() 122 | var buf = new Buffer(4 * 1024) 123 | 124 | for (var i = 0; i < 1000; i++) { 125 | a.write(buf) 126 | b.write(buf) 127 | } 128 | 129 | t.notOk(a.write('more'), 'a hit hwm') 130 | t.notOk(b.write('more'), 'b hit hwm') 131 | 132 | a.once('drain', function () { 133 | t.fail('a should not drain') 134 | }) 135 | 136 | b.once('drain', function () { 137 | t.fail('b should not drain') 138 | }) 139 | 140 | setTimeout(function () { 141 | t.end() 142 | }, 100) 143 | 144 | stream.pipe(stream) 145 | }) 146 | 147 | tape('back pressure resumes', function (t) { 148 | t.plan(4) 149 | 150 | var resumed = false 151 | var stream = channels(function (channel) { 152 | if (channel.id) channel.resume() 153 | else channel.pause() 154 | 155 | setTimeout(function () { 156 | resumed = true 157 | channel.resume() 158 | }, 200) 159 | }) 160 | 161 | var a = stream.createChannel() 162 | var b = stream.createChannel() 163 | var buf = new Buffer(4 * 1024) 164 | 165 | for (var i = 0; i < 1000; i++) { 166 | a.write(buf) 167 | b.write(buf) 168 | } 169 | 170 | t.notOk(a.write('more'), 'a hit hwm') 171 | t.notOk(b.write('more'), 'b hit hwm') 172 | 173 | a.once('drain', function () { 174 | t.ok(resumed, 'a drains after resume') 175 | }) 176 | 177 | b.once('drain', function () { 178 | t.ok(resumed, 'b drains after resume') 179 | }) 180 | 181 | stream.pipe(stream) 182 | }) 183 | 184 | tape('close channels on stream close', function (t) { 185 | t.plan(10) 186 | 187 | var stream = channels(function (c) { 188 | c.once('close', function () { 189 | t.pass('remote channel closed') 190 | }) 191 | }) 192 | 193 | for (var i = 0; i < 5; i++) { 194 | var c = stream.createChannel() 195 | c.write('open please') 196 | c.once('close', function () { 197 | t.pass('local channel closed') 198 | }) 199 | } 200 | 201 | stream.pipe(stream) 202 | 203 | process.nextTick(function () { 204 | stream.destroy() 205 | }) 206 | }) 207 | 208 | tape('close channels on stream end', function (t) { 209 | t.plan(10) 210 | 211 | var stream = channels(function (c) { 212 | c.once('close', function () { 213 | t.pass('remote channel closed') 214 | }) 215 | }) 216 | 217 | for (var i = 0; i < 5; i++) { 218 | var c = stream.createChannel() 219 | c.write('open please') 220 | c.once('close', function () { 221 | t.pass('local channel closed') 222 | }) 223 | } 224 | 225 | stream.pipe(stream) 226 | 227 | process.nextTick(function () { 228 | stream.end() 229 | }) 230 | }) 231 | 232 | tape('timeout', function (t) { 233 | var stream = channels() 234 | 235 | var timeout = setTimeout(function () {}, 100000) // keep process running 236 | 237 | stream.setTimeout(1000, function () { 238 | clearTimeout(timeout) 239 | t.pass('timed out') 240 | t.end() 241 | }) 242 | }) 243 | 244 | tape('keep aliving', function (t) { 245 | var stream = channels() 246 | 247 | var timeout = setTimeout(function () {}, 100000) // keep process running 248 | 249 | stream.setTimeout(1000, function () { 250 | t.fail('should not timeout') 251 | }) 252 | 253 | stream.pipe(stream) 254 | 255 | setTimeout(function () { 256 | clearTimeout(timeout) 257 | stream.destroy() 258 | t.pass('did not timeout') 259 | t.end() 260 | }, 3000) 261 | }) 262 | 263 | tape('max message limit', function (t) { 264 | var stream = channels(function (s) { 265 | s.resume() 266 | }) 267 | 268 | var ch = stream.createChannel() 269 | ch.write(new Buffer(5 * 1024 * 1024)) 270 | stream.on('error', function () { 271 | t.pass('had error') 272 | t.end() 273 | }) 274 | stream.pipe(stream) 275 | }) 276 | --------------------------------------------------------------------------------