diff --git a/plugins/research_sync/instance.ts b/plugins/research_sync/instance.ts index 7e7b5109..02771abf 100644 --- a/plugins/research_sync/instance.ts +++ b/plugins/research_sync/instance.ts @@ -26,6 +26,7 @@ type IpcQueue = { queue: unknown, player?: string }; export default async function(context: InstancePluginContext) { const { instance, logger, plugin } = context; let syncStarted = false; + let queueEventsApplied = 0; // Commands are sent one at a time so they execute in order let rconQueue: Promise = Promise.resolve(); @@ -50,7 +51,13 @@ export default async function(context: InstancePluginContext) { // queue null asks for the cluster queue without seeding it async function followQueue(queue: string[] | null, player: string | null) { + let applied = queueEventsApplied; let result = await instance.sendTo("controller", new QueueSyncRequest(queue, player)); + // A QueueEvent handled meanwhile is at least as new as this reply, since the controller broadcasts every + // queue change in order with its replies. Arriving in the same read, it is applied before the reply. + if (queueEventsApplied !== applied) { + return; + } await applyQueue({ queue: result }); } @@ -116,6 +123,7 @@ export default async function(context: InstancePluginContext) { if (event.player !== null) { data.player = event.player; } + queueEventsApplied += 1; await applyQueue(data); }); diff --git a/plugins/research_sync/test/instance.js b/plugins/research_sync/test/instance.js index 0ef10c6e..844d16f2 100644 --- a/plugins/research_sync/test/instance.js +++ b/plugins/research_sync/test/instance.js @@ -117,6 +117,17 @@ describe("research_sync instance", function() { instance.sendTo = async () => { throw new Error("gone"); }; await instance.hooks.controllerConnectionEvent.invoke("connect"); }); + it("should not let the reply overwrite a newer QueueEvent delivered with it", async function() { + // As if the reply and the broadcast came in one socket read: the event is handled before the + // reply resolves the request's await + instance.sendTo = async (dst, message) => { + sent.push(message); + mock.getHandler(instance, QueueEvent)(new QueueEvent(["new"], null, "B")); + return ["old"]; + }; + await instance.hooks.controllerConnectionEvent.invoke("connect"); + assert.equal(lastCommand(), applyCommand({ queue: ["new"], instance: "B" })); + }); }); it("should forward queue changes from the game", async function() {