Files
Stefan-Sanger 1fad2ef1d2 fix: replace shared telnet singleton with connection pool
Addresses PERFORMANCE-FINDINGS #1, #4, #6 (and #2 in creategroup flow):
- Add app/telnet-pool.js: pool of N connections (config.telnet.poolSize,
  default 4) with wait-queue, lazy reconnect, and respawn on dead socket.
- Remove module-level new Telnet() singleton and startup socket dump from
  app/routes/index.js; switch all mutating endpoints to telnetPool.send().
- Set telnet debug:false; use config.telnet.timeout (default 5s) instead
  of hardcoded 30000.
- Add telnet.poolSize to config/prosody-muc-rest.js.
- await doesMucExist in /creategroup so the existence check is no longer dead.

Part-of: <http://gitlab.vnc.biz/uxf/prosody-muc-rest/-/merge_requests/3>
2026-07-10 14:44:43 +02:00

100 lines
2.5 KiB
JavaScript

var Telnet = require('telnet-client');
var STATE = { IDLE: 0, BUSY: 1, DEAD: 2 };
function TelnetPool(params, size) {
this.params = params;
this.size = size;
this.conns = [];
this.queue = [];
for (var i = 0; i < size; i++) {
this.conns.push(this._spawn('#' + i));
}
}
TelnetPool.prototype._spawn = function (label) {
var self = this;
var conn = new Telnet();
conn._poolState = STATE.DEAD;
conn._poolLabel = label;
conn.connect(self.params).then(function () {
conn._poolState = STATE.IDLE;
console.log('[telnet-pool] connected', label);
self._drain();
}).catch(function (error) {
conn._poolState = STATE.DEAD;
console.log('[telnet-pool] connect error', label, error);
});
return conn;
};
TelnetPool.prototype._drain = function () {
if (this.queue.length === 0) return;
for (var i = 0; i < this.conns.length; i++) {
var conn = this.conns[i];
if (conn._poolState === STATE.IDLE) {
conn._poolState = STATE.BUSY;
var resolve = this.queue.shift();
resolve(conn);
if (this.queue.length === 0) return;
}
}
};
TelnetPool.prototype._acquire = function () {
var self = this;
for (var i = 0; i < self.conns.length; i++) {
var conn = self.conns[i];
if (conn._poolState === STATE.IDLE) {
conn._poolState = STATE.BUSY;
return Promise.resolve(conn);
}
}
return new Promise(function (resolve) {
self.queue.push(resolve);
});
};
TelnetPool.prototype._release = function (conn) {
conn._poolState = STATE.IDLE;
this._drain();
};
TelnetPool.prototype._ensureConnected = function (conn) {
var self = this;
if (conn && conn.socket && conn.socket.writable) {
return Promise.resolve(conn);
}
console.log('[telnet-pool] socket not writable, reconnecting', conn._poolLabel);
return conn.connect(self.params).then(function () {
console.log('[telnet-pool] reconnected', conn._poolLabel);
return conn;
});
};
TelnetPool.prototype._replace = function (deadConn) {
var idx = this.conns.indexOf(deadConn);
if (idx > -1) {
this.conns[idx] = this._spawn(deadConn._poolLabel);
}
};
TelnetPool.prototype.send = function (cmd) {
var self = this;
return self._acquire().then(function (conn) {
return self._ensureConnected(conn).then(function () {
return conn.send(cmd);
}).then(function (response) {
self._release(conn);
return response;
}).catch(function (error) {
self._replace(conn);
throw error;
});
});
};
module.exports = function (params, size) {
return new TelnetPool(params, size);
};