mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-15 23: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
@@ -2,6 +2,7 @@ import type { ControllerPluginContext, InstanceRecord } from "@clusterio/control
|
||||
import type { IpcPlayerData } from "./messages.js";
|
||||
import { mergePlayerData } from "./components.js";
|
||||
|
||||
import crypto from "node:crypto";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "path";
|
||||
import * as lib from "@clusterio/lib";
|
||||
@@ -67,6 +68,8 @@ async function saveDatabase(
|
||||
|
||||
export default async function(context: ControllerPluginContext) {
|
||||
const { controller, logger, plugin } = context;
|
||||
// Differs on every controller start: a prefetched copy holding another run's token may be one this run never saved
|
||||
const runToken = crypto.randomBytes(8).toString("hex");
|
||||
const acquiredPlayers = new Map<string, { instanceId: number, expiresMs?: number }>();
|
||||
const { playerDatastore, tombstones } = await loadDatabase(controller.config, logger);
|
||||
let playerDatastoreDirty = false;
|
||||
@@ -125,7 +128,7 @@ export default async function(context: ControllerPluginContext) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
controller.sendTo({ instanceId: targetId }, new msg.PrefetchEvent(playerData));
|
||||
controller.sendTo({ instanceId: targetId }, new msg.PrefetchEvent(playerData, runToken));
|
||||
logger.verbose(`Prefetching ${playerData.name} to ${target.config.get("instance.name")}`);
|
||||
} catch (err: any) {
|
||||
// sendEvent throws when the instance's host isn't connected; the upload is stored either way
|
||||
@@ -186,6 +189,7 @@ export default async function(context: ControllerPluginContext) {
|
||||
playerDatastore.has(playerName),
|
||||
undefined,
|
||||
epoch || undefined,
|
||||
runToken,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user