UNPKG

amqplib

Version:

An AMQP 0-9-1 (e.g., RabbitMQ) library and client.

117 lines (100 loc) 3.59 kB
// A Mux is an object into which other readable streams may be piped; // it then writes 'packets' from the upstreams to the given // downstream. const assert = require('node:assert'); const schedule = typeof setImmediate === 'function' ? setImmediate : process.nextTick; class Mux { constructor(downstream) { this.newStreams = []; this.oldStreams = []; this.blocked = false; this.scheduledRead = false; this.out = downstream; downstream.on('drain', () => { this.blocked = false; this._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). _readIncoming() { // We may be sent here speculatively, if an incoming stream has // become readable if (this.blocked) return; let accepting = true; const 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) { while (accepting && streams.length > 0) { const stream = streams.shift(); const chunk = stream.read(); if (chunk !== null) { accepting = out.write(chunk); streams.push(stream); } } } 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'); Array.prototype.push.apply(this.oldStreams, 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; } _scheduleRead() { if (!this.scheduledRead) { schedule(() => { this.scheduledRead = false; this._readIncoming(); }); this.scheduledRead = true; } } pipeFrom(readable) { const 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); } unpipeFrom(readable) { readable.emit('unpipeFrom', this); } } module.exports.Mux = Mux;