v0.3.1: survival behaviour overhaul — storyline, biome-aware scout, wedge-relocate, food/perf fixes, monitor TUI #28
@@ -39,7 +39,15 @@ export function attach({ intervalMs = TUNE_INTERVAL_MS } = {}) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_timer = setInterval(() => {
|
_timer = setInterval(() => {
|
||||||
runOnce().catch((e) => warn("tuner", `tick err: ${e?.message ?? e}`));
|
// runOnce is synchronous (pure SQL, no await) — must NOT call
|
||||||
|
// .catch on its plain-object return. A try/catch here is the
|
||||||
|
// correct guard. (This exact bug crashed the live bot after
|
||||||
|
// ~1h uptime when the first tuner tick fired.)
|
||||||
|
try {
|
||||||
|
runOnce();
|
||||||
|
} catch (e) {
|
||||||
|
warn("tuner", `tick err: ${e?.message ?? e}`);
|
||||||
|
}
|
||||||
}, intervalMs);
|
}, intervalMs);
|
||||||
_timer.unref?.();
|
_timer.unref?.();
|
||||||
info("tuner", `attached; tune every ${Math.round(intervalMs / 60000)} min`);
|
info("tuner", `attached; tune every ${Math.round(intervalMs / 60000)} min`);
|
||||||
|
|||||||
@@ -6,10 +6,31 @@ import { join } from "node:path";
|
|||||||
|
|
||||||
import { initKnowledge, isAvailable, listImprovements } from "../knowledge/index.js";
|
import { initKnowledge, isAvailable, listImprovements } from "../knowledge/index.js";
|
||||||
import { closeStore, __resetForTests } from "../knowledge/store.js";
|
import { closeStore, __resetForTests } from "../knowledge/store.js";
|
||||||
import { runOnce, __testing } from "./trigger-tuner.js";
|
import { runOnce, attach, detach, __testing } from "./trigger-tuner.js";
|
||||||
|
|
||||||
const { MIN_SAMPLE } = __testing;
|
const { MIN_SAMPLE } = __testing;
|
||||||
|
|
||||||
|
test("attach: timer tick does not crash (runOnce is sync, regression for .catch bug)", async () => {
|
||||||
|
// Reproduces the crash that killed the live bot after ~1h: the
|
||||||
|
// setInterval body called runOnce().catch(...) but runOnce returns
|
||||||
|
// a plain object, not a Promise. attach must guard with try/catch.
|
||||||
|
detach();
|
||||||
|
let threw = false;
|
||||||
|
const origSetInterval = globalThis.setInterval;
|
||||||
|
let captured = null;
|
||||||
|
// capture the interval callback without actually waiting
|
||||||
|
globalThis.setInterval = (fn) => { captured = fn; return { unref() {} }; };
|
||||||
|
try {
|
||||||
|
attach({ intervalMs: 999999 });
|
||||||
|
// invoke the captured tick synchronously — must not throw
|
||||||
|
try { captured?.(); } catch { threw = true; }
|
||||||
|
} finally {
|
||||||
|
globalThis.setInterval = origSetInterval;
|
||||||
|
detach();
|
||||||
|
}
|
||||||
|
assert.equal(threw, false, "tuner timer tick must not throw");
|
||||||
|
});
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const tmp = mkdtempSync(join(tmpdir(), "pepa-tuner-test-"));
|
const tmp = mkdtempSync(join(tmpdir(), "pepa-tuner-test-"));
|
||||||
__resetForTests();
|
__resetForTests();
|
||||||
|
|||||||
Reference in New Issue
Block a user