diff --git a/packages/controller/src/ControlConnection.ts b/packages/controller/src/ControlConnection.ts index 60f3e263..3a3af9f4 100644 --- a/packages/controller/src/ControlConnection.ts +++ b/packages/controller/src/ControlConnection.ts @@ -48,9 +48,13 @@ export default class ControlConnection extends BaseConnection { this._version = registerData.version; - this.connector.on("connect", () => { - this.connector._socket!.clusterio_ignore_dump = Boolean(this.ws_dumper); - }); + // A resume installs a fresh socket, so re-apply the ignore flag or the + // dumper's own frames get echoed back into it and recurse forever. + for (let event of ["connect", "resume"] as const) { + this.connector.on(event, () => { + this.connector._socket!.clusterio_ignore_dump = Boolean(this.ws_dumper); + }); + } this.connector.on("close", () => { if (this.logTransport) { this._controller.clusterLogger.remove(this.logTransport); diff --git a/test/controller/ControlConnection.test.js b/test/controller/ControlConnection.test.js index b97c5527..d82f6cc9 100644 --- a/test/controller/ControlConnection.test.js +++ b/test/controller/ControlConnection.test.js @@ -1,12 +1,44 @@ import assert from "node:assert/strict"; import events from "node:events"; import * as lib from "@clusterio/lib"; -import { ControlConnection, ControllerHooks } from "@clusterio/controller"; +import { Controller, ControlConnection, ControllerHooks } from "@clusterio/controller"; import { MockConnector, MockLogger } from "../mock.js"; const addr = lib.Address.fromShorthand; describe("controller/src/ControlConnection", function() { + describe(".handleDebugDumpWsRequest()", function() { + function makeConnection() { + const controllerConfig = new lib.ControllerConfig("controller"); + const connector = new lib.VirtualConnector( + lib.Address.fromShorthand("controller"), + lib.Address.fromShorthand({ controlId: 1 }), + ); + connector._socket = {}; + const controller = new Controller(lib.logger, [], controllerConfig); + const user = controller.users.getOrCreateUser("test"); + return new ControlConnection({ version: "2.0.0" }, connector, controller, user, 1); + } + + it("re-applies clusterio_ignore_dump to the socket on resume", async function() { + const connection = makeConnection(); + await connection.handleDebugDumpWsRequest(new lib.DebugDumpWsRequest()); + // A resume installs a fresh socket without the flag. + const resumedSocket = {}; + connection.connector._socket = resumedSocket; + connection.connector.emit("resume"); + assert.equal(resumedSocket.clusterio_ignore_dump, true); + }); + + it("leaves the flag off on resume without a dumper", function() { + const connection = makeConnection(); + const resumedSocket = {}; + connection.connector._socket = resumedSocket; + connection.connector.emit("resume"); + assert.equal(resumedSocket.clusterio_ignore_dump, false); + }); + }); + describe(".handleControllerRestartRequest()", function() { let mockController;