Fix crash on continuing an active connection (#369)

Fix ServerConnector reaching an invalid state if a continue operation is
performed on an active connection.  The continue would cause the
connection established logic to be runned before the connection lost
logic of the pervious connection is perfomed, resulting in a leaked
heartbeat interval and the connection lost logic being performed on the
new connection instead of the old.  Prevent this by closing both the new
and the old connection in this case.
This commit is contained in:
Hornwitser authored and GitHub committed 2021-03-17 20:20:12 +01:00
1 parent 72d0994ca6
commit a2d56d4a8d
4 files changed
+50 -2

No files matched your search

+3
View File
@@ -75,6 +75,9 @@ class WebSocketBaseConnector extends events.EventEmitter {
* Start sending heartbeats
*/
startHeartbeat() {
if (this._heartbeatId) {
throw new Error("heartbeat is already running");
}
this._lastHeartbeat = Date.now();
this._heartbeatId = setInterval(() => {
this._doHeartbeat();
+18 -1
View File
@@ -1399,7 +1399,24 @@ class WebSocketServerConnector extends libLink.WebSocketBaseConnector {
* @param {number} lastSeq - The last message the client received.
*/
continue(socket, lastSeq) {
this._socket.terminate();
// It's possible the previous connection hasn't closed yet due to a
// stale connection. If this is the case the connector will have to
// wait for the previous socket to close down before it can continue
// from a new socket, or there will be overlapping events from both.
if (["connected", "closing"].includes(this._state)) {
this._socket.close(4003, "Session Hijacked");
// Correctly waiting for the connector to close and then
// handling all the edge cases like multiple continues happening
// in parallel or connections going stale is far too difficult.
// Kill the connection here and let the client reconnect later
// when the connector is not held up by an active connection.
socket.close(1013, "Session Busy");
socket.terminate();
return;
}
this._socket = socket;
if (this._timeoutId) {
+28 -1
View File
@@ -2,11 +2,15 @@
const assert = require("assert").strict;
const fs = require("fs-extra");
const path = require("path");
const events = require("events");
const libLink = require("@clusterio/lib/link");
const libUsers = require("@clusterio/lib/users");
const { slowTest, get, exec, execCtl, sendRcon, getControl, instancesDir } = require("./index");
const {
TestControl, TestControlConnector, url, controlToken, slowTest,
get, exec, execCtl, sendRcon, getControl, instancesDir,
} = require("./index");
async function getInstances() {
@@ -32,6 +36,29 @@ describe("Integration of Clusterio", function() {
);
});
});
describe("run", function() {
it("should handle resume of an active connection", async function() {
slowTest(this);
let tlsCa = await fs.readFile("test/file/tls/cert.pem");
let connectorA = new TestControlConnector(url, 2, tlsCa);
connectorA.token = controlToken;
let controlA = new TestControl(connectorA);
await connectorA.connect();
connectorA._state = "closing";
connectorA.stopHeartbeat();
connectorA.on("error", () => {});
let connectorB = new TestControlConnector(url, 2, tlsCa);
connectorB.token = controlToken;
let controlB = new TestControl(connectorB);
connectorB._sessionToken = connectorA._sessionToken;
connectorB._state = "handshake";
await connectorB._doConnect();
await events.once(connectorB, "resume");
await connectorB.close();
});
});
});
describe("clusterioctl", function() {
+1
View File
@@ -197,6 +197,7 @@ module.exports = {
getControl,
url,
controlToken,
instancesDir,
databaseDir,
masterConfigPath,