apps/engine 独立进程(syncRound + 防重叠调度);上游 log/token;QPU 采样; server 读 engine_status;时刻出参 toUnixSec;中文日志;zod-openapi 扩展顺序修复。
76 lines
1.9 KiB
TypeScript
76 lines
1.9 KiB
TypeScript
/**
|
|
* 调度循环(方案 A):一轮结束 → setTimeout 下一枪;inFlight 防重叠。
|
|
* 启停不得抹掉已有 last_round。
|
|
*/
|
|
import { appLogger } from "@new-api-key-usage/core";
|
|
import { syncRound, type SyncRoundDeps } from "./sync-round.ts";
|
|
|
|
const log = appLogger("engine");
|
|
|
|
export interface LoopOptions extends SyncRoundDeps {
|
|
intervalMs: number;
|
|
}
|
|
|
|
export function createSyncLoop(opts: LoopOptions) {
|
|
let inFlight = false;
|
|
let stopped = false;
|
|
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
let last_round: unknown = null;
|
|
|
|
async function persist(running: boolean) {
|
|
await opts.repos.engine.upsertSnapshot({
|
|
running,
|
|
sync_interval_ms: opts.intervalMs,
|
|
last_round,
|
|
});
|
|
}
|
|
|
|
async function runOnce(): Promise<void> {
|
|
if (stopped) return;
|
|
if (inFlight) {
|
|
log.warn("上一轮尚未结束,跳过本枪");
|
|
schedule();
|
|
return;
|
|
}
|
|
inFlight = true;
|
|
try {
|
|
last_round = await syncRound(opts);
|
|
await persist(!stopped);
|
|
} catch (e) {
|
|
log.error(`同步轮次异常:${e}`);
|
|
await persist(!stopped).catch(() => {});
|
|
} finally {
|
|
inFlight = false;
|
|
schedule();
|
|
}
|
|
}
|
|
|
|
function schedule() {
|
|
if (stopped) return;
|
|
timer = setTimeout(() => {
|
|
void runOnce();
|
|
}, opts.intervalMs);
|
|
}
|
|
|
|
return {
|
|
start() {
|
|
log.info(`同步引擎启动:周期=${opts.intervalMs}ms`);
|
|
void (async () => {
|
|
const snap = await opts.repos.engine.getSnapshot();
|
|
last_round = snap?.last_round ?? null;
|
|
await persist(true).catch(() => {});
|
|
void runOnce();
|
|
})();
|
|
},
|
|
async stop() {
|
|
stopped = true;
|
|
if (timer) clearTimeout(timer);
|
|
// 等在跑的一轮结束,避免 stop 与 upsert 交错
|
|
for (let i = 0; i < 50 && inFlight; i++) {
|
|
await new Promise((r) => setTimeout(r, 100));
|
|
}
|
|
await persist(false).catch(() => {});
|
|
},
|
|
};
|
|
}
|