mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-15 07:24:52 +09:00
Tag prefetched copies with the controller's run token
If the controller crashes after sending generation N but before saving it, the destination keeps that prefetch while the controller restarts at N-1, and another instance can then upload different data as generation N. The controller now picks a random token per run, sends it with every prefetch and acquire reply, and the scenario applies a held copy only when its generation and token both match, and lets a new run's copy replace one with another token. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
ca7caedfbe
commit
5e4dcada50
6 files changed
+84
-18
No files matched your search
@@ -5,7 +5,7 @@ import * as lib from "@clusterio/lib";
|
||||
|
||||
import * as mock from "../../../test/mock.js";
|
||||
import loadInstancePlugin, { recipeNotificationDelta, applyRecipeNotificationDelta } from "../dist/node/instance.js";
|
||||
import { DownloadResponse, PrefetchEvent } from "../dist/node/messages.js";
|
||||
import { AcquireResponse, DownloadResponse, PrefetchEvent } from "../dist/node/messages.js";
|
||||
import { components } from "../dist/node/components.js";
|
||||
import { plugin as info } from "../dist/node/index.js";
|
||||
|
||||
@@ -271,13 +271,13 @@ describe("player_sync", function() {
|
||||
}
|
||||
});
|
||||
|
||||
const prefetch = playerData => mock.getHandler(instance, PrefetchEvent)(new PrefetchEvent(playerData));
|
||||
const prefetch = playerData => mock.getHandler(instance, PrefetchEvent)(new PrefetchEvent(playerData, "tok"));
|
||||
function prefetchCommands(playerData, enabled = Object.keys(components)) {
|
||||
const chunks = JSON.stringify(playerData).match(/.{1,10}/g);
|
||||
const sent = lib.escapeString(JSON.stringify(Object.fromEntries(enabled.map(c => [c, true]))));
|
||||
return chunks.map((chunk, i) => "/sc player_sync.prefetch_inventory(" +
|
||||
`'test',${playerData.generation},'${lib.escapeString(chunk)}',${i + 1},${chunks.length}` +
|
||||
`${i === 0 ? `,'${sent}'` : ""})`);
|
||||
`${i === 0 ? `,'${sent}','tok'` : ""})`);
|
||||
}
|
||||
|
||||
it("should send prefetch_inventory chunks with the generation and the full recipe list", async function() {
|
||||
@@ -296,6 +296,12 @@ describe("player_sync", function() {
|
||||
assert.ok(commands.length > 1, "more than one chunk");
|
||||
assert.deepEqual(instance.server.rconCommands, commands);
|
||||
});
|
||||
it("should send the token with the first chunk only", async function() {
|
||||
await prefetch({ generation: 3, name: "test" });
|
||||
const commands = instance.server.rconCommands;
|
||||
assert.ok(commands[0].endsWith(",'tok')"));
|
||||
assert.ok(commands.slice(1).every(command => !command.includes("tok")));
|
||||
});
|
||||
it("should stop when the save refuses the first chunk", async function() {
|
||||
const playerData = { generation: 3, name: "test" };
|
||||
const commands = prefetchCommands(playerData);
|
||||
@@ -312,4 +318,22 @@ describe("player_sync", function() {
|
||||
assert.deepEqual(instance.server.rconCommands, commands.slice(0, 2));
|
||||
});
|
||||
});
|
||||
|
||||
describe("acquire reply", function() {
|
||||
it("should pass the controller's token to acquire_response", async function() {
|
||||
try {
|
||||
lib.registerPluginMessages([info]);
|
||||
} catch (err) {
|
||||
// Already registered by the full test suite
|
||||
}
|
||||
const { instance, host } = await mock.loadInstancePlugin(loadInstancePlugin, info);
|
||||
host.connector.connected = true;
|
||||
instance.sendTo = async () => new AcquireResponse("acquired", 4, true, undefined, undefined, "tok");
|
||||
instance.server.emit("ipc-player_sync_acquire", { player_name: "test" });
|
||||
await lib.wait(10);
|
||||
const [command] = instance.server.rconCommands;
|
||||
assert.ok(command.startsWith("/sc player_sync.acquire_response("), command);
|
||||
assert.ok(command.includes(lib.escapeString('"token":"tok"')), command);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user