use pump to pipe sockets

Ensures that destination socket close or destroy also does the same for
the source socket.
This commit is contained in:
Roman Shtylman
2018-05-16 10:21:56 -04:00
parent 743895720c
commit 317db73bdc
7 changed files with 97 additions and 50 deletions

View File

@@ -25,10 +25,10 @@ class TunnelAgent extends Agent {
// once a socket is available it is handed out to the next callback
this.waitingCreateConn = [];
this.debug = Debug('lt:TunnelAgent');
this.debug = Debug(`lt:TunnelAgent[${options.clientId}]`);
// track maximum allowed sockets
this.activeSockets = 0;
this.connectedSockets = 0;
this.maxTcpSockets = options.maxTcpSockets || DEFAULT_MAX_SOCKETS;
// new tcp server to service requests for this client
@@ -36,6 +36,7 @@ class TunnelAgent extends Agent {
// flag to avoid double starts
this.started = false;
this.closed = false;
}
listen() {
@@ -48,8 +49,7 @@ class TunnelAgent extends Agent {
server.on('close', this._onClose.bind(this));
server.on('connection', this._onConnection.bind(this));
server.on('error', (err) => {
// where do these errors come from?
// other side creates a connection and then is killed?
// These errors happen from killed connections, we don't worry about them
if (err.code == 'ECONNRESET' || err.code == 'ETIMEDOUT') {
return;
}
@@ -70,11 +70,12 @@ class TunnelAgent extends Agent {
}
_onClose() {
this.closed = true;
this.debug('closed tcp socket');
clearTimeout(this.connTimeout);
// we will not invoke these callbacks?
// TODO(roman): we could invoke these with errors...?
// this makes downstream have to handle this
// flush any waiting connections
for (const conn of this.waitingCreateConn) {
conn(new Error('closed'), null);
}
this.waitingCreateConn = [];
this.emit('end');
}
@@ -82,37 +83,23 @@ class TunnelAgent extends Agent {
// new socket connection from client for tunneling requests to client
_onConnection(socket) {
// no more socket connections allowed
if (this.activeSockets >= this.maxTcpSockets) {
if (this.connectedSockets >= this.maxTcpSockets) {
this.debug('no more sockets allowed');
socket.destroy();
return false;
}
// a new socket becomes available
if (this.activeSockets == 0) {
this.emit('online');
}
this.activeSockets += 1;
this.debug('new connection from: %s:%s', socket.address().address, socket.address().port);
// a single connection is enough to keep client id slot open
clearTimeout(this.connTimeout);
socket.once('close', (had_error) => {
this.debug('closed socket (error: %s)', had_error);
this.debug('removing socket');
this.activeSockets -= 1;
socket.once('close', (hadError) => {
this.debug('closed socket (error: %s)', hadError);
this.connectedSockets -= 1;
// remove the socket from available list
const idx = this.availableSockets.indexOf(socket);
if (idx >= 0) {
this.availableSockets.splice(idx, 1);
}
// need to track total sockets, not just active available
this.debug('remaining client sockets: %s', this.availableSockets.length);
// no more sockets for this session
// the session will become inactive if client does not reconnect
if (this.availableSockets.length <= 0) {
this.debug('connected sockets: %s', this.connectedSockets);
if (this.connectedSockets <= 0) {
this.debug('all sockets disconnected');
this.emit('offline');
}
@@ -125,28 +112,38 @@ class TunnelAgent extends Agent {
socket.destroy();
});
// make socket available for those waiting on sockets
this.availableSockets.push(socket);
if (this.connectedSockets === 0) {
this.emit('online');
}
// flush anyone waiting on sockets
this._callWaitingCreateConn();
}
this.connectedSockets += 1;
this.debug('new connection from: %s:%s', socket.address().address, socket.address().port);
// invoke when a new socket is available and there may be waiting createConnection calls
_callWaitingCreateConn() {
// if there are queued callbacks, give this socket now and don't queue into available
const fn = this.waitingCreateConn.shift();
if (!fn) {
if (fn) {
this.debug('giving socket to queued conn request');
setTimeout(() => {
fn(null, socket);
}, 0);
return;
}
this.debug('handling queued request');
this.createConnection({}, fn);
// make socket available for those waiting on sockets
this.availableSockets.push(socket);
}
// fetch a socket from the available socket pool for the agent
// if no socket is available, queue
// cb(err, socket)
createConnection(options, cb) {
if (this.closed) {
cb(new Error('closed'));
return;
}
this.debug('create connection');
// socket is a tcp connection back to the user hosting the site
const sock = this.availableSockets.shift();
@@ -154,7 +151,8 @@ class TunnelAgent extends Agent {
// wait until we have one
if (!sock) {
this.waitingCreateConn.push(cb);
this.debug('waiting');
this.debug('waiting connected: %s', this.connectedSockets);
this.debug('waiting available: %s', this.availableSockets.length);
return;
}