You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
146 lines
3.9 KiB
146 lines
3.9 KiB
// |
|
// |
|
// |
|
|
|
'use strict'; |
|
|
|
// A Mux is an object into which other readable streams may be piped; |
|
// it then writes 'packets' from the upstreams to the given |
|
// downstream. |
|
|
|
var inherits = require('util').inherits; |
|
var assert = require('assert'); |
|
|
|
var schedule = (typeof setImmediate === 'function') ? |
|
setImmediate : process.nextTick; |
|
|
|
function Mux(downstream) { |
|
this.newStreams = []; |
|
this.oldStreams = []; |
|
this.blocked = false; |
|
this.scheduledRead = false; |
|
|
|
this.out = downstream; |
|
var self = this; |
|
downstream.on('drain', function() { |
|
self.blocked = false; |
|
self._readIncoming(); |
|
}); |
|
} |
|
|
|
// There are 2 states we can be in: |
|
|
|
// - waiting for outbound capacity, which will be signalled by a |
|
// - 'drain' event on the downstream; or, |
|
|
|
// - no packets to send, waiting for an inbound buffer to have |
|
// packets, which will be signalled by a 'readable' event |
|
|
|
// If we write all packets available whenever there is outbound |
|
// capacity, we will either run out of outbound capacity (`#write` |
|
// returns false), or run out of packets (all calls to an |
|
// `inbound.read()` have returned null). |
|
|
|
Mux.prototype._readIncoming = function() { |
|
|
|
// We may be sent here speculatively, if an incoming stream has |
|
// become readable |
|
if (this.blocked) return; |
|
|
|
var self = this; |
|
var accepting = true; |
|
var out = this.out; |
|
|
|
// Try to read a chunk from each stream in turn, until all streams |
|
// are empty, or we exhaust our ability to accept chunks. |
|
function roundrobin(streams) { |
|
var s; |
|
// if there's just one incoming stream we don't have to |
|
// go through all the dequeue/enqueueing |
|
if (streams.length === 1) { |
|
s = streams.shift(); |
|
while (accepting) { |
|
var chunk = s.read(); |
|
if (chunk !== null) { |
|
accepting = out.write(chunk); |
|
} |
|
else break; |
|
} |
|
if (!accepting) streams.push(s); |
|
} |
|
else { |
|
while (accepting && (s = streams.shift())) { |
|
var chunk = s.read(); |
|
if (chunk !== null) { |
|
accepting = out.write(chunk); |
|
streams.push(s); |
|
} |
|
} |
|
} |
|
} |
|
|
|
roundrobin(this.newStreams); |
|
|
|
// Either we exhausted the new queues, or we ran out of capacity. If |
|
// we ran out of capacity, all the remaining new streams (i.e., |
|
// those with packets left) become old streams. This effectively |
|
// prioritises streams that keep their buffers close to empty over |
|
// those that are constantly near full. |
|
|
|
if (accepting) { // all new queues are exhausted, write as many as |
|
// we can from the old streams |
|
assert.equal(0, this.newStreams.length); |
|
roundrobin(this.oldStreams); |
|
} |
|
else { // ran out of room |
|
assert(this.newStreams.length > 0, "Expect some new streams to remain"); |
|
this.oldStreams = this.oldStreams.concat(this.newStreams); |
|
this.newStreams = []; |
|
} |
|
// We may have exhausted all the old queues, or run out of room; |
|
// either way, all we need to do is record whether we have capacity |
|
// or not, so any speculative reads will know |
|
this.blocked = !accepting; |
|
}; |
|
|
|
Mux.prototype._scheduleRead = function() { |
|
var self = this; |
|
|
|
if (!self.scheduledRead) { |
|
schedule(function() { |
|
self.scheduledRead = false; |
|
self._readIncoming(); |
|
}); |
|
self.scheduledRead = true; |
|
} |
|
}; |
|
|
|
Mux.prototype.pipeFrom = function(readable) { |
|
var self = this; |
|
|
|
function enqueue() { |
|
self.newStreams.push(readable); |
|
self._scheduleRead(); |
|
} |
|
|
|
function cleanup() { |
|
readable.removeListener('readable', enqueue); |
|
readable.removeListener('error', cleanup); |
|
readable.removeListener('end', cleanup); |
|
readable.removeListener('unpipeFrom', cleanupIfMe); |
|
} |
|
function cleanupIfMe(dest) { |
|
if (dest === self) cleanup(); |
|
} |
|
|
|
readable.on('unpipeFrom', cleanupIfMe); |
|
readable.on('end', cleanup); |
|
readable.on('error', cleanup); |
|
readable.on('readable', enqueue); |
|
}; |
|
|
|
Mux.prototype.unpipeFrom = function(readable) { |
|
readable.emit('unpipeFrom', this); |
|
}; |
|
|
|
module.exports.Mux = Mux;
|
|
|