mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-13 15:14:52 +09:00
Refine the state flow used in the connector abstractions to better handle close while connecting and resuming. The new logic is to close the connection immediately in these two states instead of continuing trying to connect and then giving up some point later. For the connectors the handshake state is split into connecting and resuming, new is replaced by closed, and the closing state is turned into a boolean flag as it's possible that closing is desired while connecting, connected or resuming. The connecting state is not present in the server connector because there's nothing for the server to do in that connector state. The timeout logic is completely removed, once a connection attempt is started for the client it will continue to attempt to connect, attempt to resume dropped connections and attempt reconnects until either an authentication error occurs or the client explicitly requests to close the connection. A new session timeout is added to control how long to attempt to resume the connection until it's considered dead and invalidated. This allows stale connections to give up earlier, and swallow up less data while in the dropped but not yet invalidated state. For the client side sending messages while it's connecting has been changed to throw an error instead of queuing up the message and (possibly) sending it after the connection is established. Code shouldn't try to send messages without knowing if the connection it's trying to send them over is active or not.
136 lines
4.6 KiB
JavaScript
136 lines
4.6 KiB
JavaScript
"use strict";
|
|
const assert = require("assert").strict;
|
|
const events = require("events");
|
|
|
|
const mock = require("../../mock");
|
|
const libLink = require("@clusterio/lib/link");
|
|
|
|
|
|
describe("lib/link/connectors", function() {
|
|
describe("class WebSocketBaseConnector", function() {
|
|
let testConnector = new libLink.WebSocketBaseConnector();
|
|
|
|
describe("._dropSendBufferSeq()", function() {
|
|
beforeEach(function() {
|
|
testConnector._sendBuffer = [
|
|
{ seq: 1, type: "test", data: {} },
|
|
{ seq: 2, type: "test", data: {} },
|
|
{ seq: 4, type: "test", data: {} },
|
|
];
|
|
});
|
|
it("should do nothing if passed null", function() {
|
|
testConnector._dropSendBufferSeq(null);
|
|
assert(testConnector._sendBuffer.length === 3, "entries were dropped");
|
|
});
|
|
it("should drop up to sequence", function() {
|
|
testConnector._dropSendBufferSeq(2);
|
|
assert.equal(testConnector._sendBuffer.length, 1, "incorrect number of entries dropped");
|
|
});
|
|
it("should ignore holes", function() {
|
|
testConnector._dropSendBufferSeq(3);
|
|
assert.equal(testConnector._sendBuffer.length, 1, "incorrect number of entries dropped");
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("class WebSocketClientConnector", function() {
|
|
let testConnector = new libLink.WebSocketClientConnector("url", 1);
|
|
testConnector._socket = new mock.MockSocket();
|
|
|
|
describe(".register()", function() {
|
|
it("is abstract", function() {
|
|
assert.throws(
|
|
() => testConnector.register(),
|
|
new Error("Abstract function")
|
|
);
|
|
});
|
|
});
|
|
|
|
describe(".sendHandshake()", function() {
|
|
it("calls send on the socket", function() {
|
|
testConnector._state = "connecting";
|
|
testConnector._socket.sentMessages = [];
|
|
testConnector.sendHandshake("test", { test: true });
|
|
assert.deepEqual(
|
|
testConnector._socket.sentMessages.map(JSON.parse),
|
|
[{ seq: null, type: "test", data: { test: true }}]
|
|
);
|
|
});
|
|
});
|
|
|
|
describe(".send()", function() {
|
|
it("calls send on the socket", function() {
|
|
testConnector._state = "connected";
|
|
testConnector._socket.sentMessages = [];
|
|
let seq = testConnector.send("test", { test: true });
|
|
assert.deepEqual(
|
|
testConnector._socket.sentMessages.map(JSON.parse),
|
|
[{ seq, type: "test", data: { test: true }}]
|
|
);
|
|
});
|
|
});
|
|
|
|
describe(".close()", function() {
|
|
it("should call close", function() {
|
|
testConnector._socket.closeCalled = false;
|
|
testConnector.close("test reason");
|
|
assert(testConnector._socket.closeCalled, "Close was not called on the socket");
|
|
});
|
|
});
|
|
|
|
describe(".processHandshake()", function() {
|
|
it("should close on invalid message", function() {
|
|
testConnector._socket.closeCalled = false;
|
|
testConnector._processHandshake({ data: "invalid message" });
|
|
assert(testConnector._socket.closeCalled, "Close was not called on the socket");
|
|
});
|
|
it("should call register on hello", function() {
|
|
let called = false;
|
|
testConnector.register = () => { called = true; };
|
|
testConnector._processHandshake({ seq: 1, type: "hello", data: { version: "test", plugins: {} }});
|
|
assert(called, "register was not called");
|
|
});
|
|
it("should emit connect on ready", async function() {
|
|
let result = events.once(testConnector, "connect");
|
|
testConnector._processHandshake(
|
|
{ seq: 1, type: "ready", data: {
|
|
session_token: "x",
|
|
session_timeout: 20,
|
|
heartbeat_interval: 10,
|
|
}}
|
|
);
|
|
await result;
|
|
clearInterval(testConnector._heartbeatId);
|
|
});
|
|
});
|
|
|
|
describe("._attachSocketHandlers()", function() {
|
|
it("should attach handlers", function() {
|
|
testConnector._attachSocketHandlers();
|
|
assert(testConnector._socket.events.size > 0, "No handlers were attached");
|
|
});
|
|
it("should throw on message received in invalid state", function() {
|
|
testConnector._state = "closed";
|
|
assert.throws(
|
|
() => testConnector._socket.events.get("message")("{}"),
|
|
new Error("Received message in unexpected state closed")
|
|
);
|
|
});
|
|
it("should call _processHandshake on message in handshake state", function() {
|
|
testConnector._state = "connecting";
|
|
let called = false;
|
|
testConnector._processHandshake = () => { called = true; };
|
|
testConnector._socket.events.get("message")("{}");
|
|
assert(called, "_processHandshake was not called");
|
|
});
|
|
it("should emit message on message in connected state", async function() {
|
|
testConnector._socket.sentMessages = [];
|
|
testConnector._state = "connected";
|
|
let result = events.once(testConnector, "message");
|
|
testConnector._socket.events.get("message")('{"message":true}');
|
|
assert.deepEqual(await result, [{ message: true }]);
|
|
});
|
|
});
|
|
});
|
|
});
|