mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 10:10:45 +00:00
fix(gateway): stop lazy cron startup on hot reload
This commit is contained in:
@@ -23,6 +23,7 @@ Docs: https://docs.openclaw.ai
|
||||
### Fixes
|
||||
|
||||
- Gateway/update: run `doctor --non-interactive --fix` after Control UI global package updates before reporting success, so legacy config is migrated before the gateway restart. Thanks @stevenchouai.
|
||||
- Gateway/cron: stop a lazy cron startup that loses a hot-reload race, preventing the old cron service from starting after reload has already replaced cron state.
|
||||
- Active Memory: apply `setupGraceTimeoutMs` to the embedded recall runner as well as the outer prompt-build watchdog, so very-cold first recalls keep the configured setup grace end-to-end. (#74480) Thanks @volcano303.
|
||||
- CLI/config: keep JSON dry-run patches validating touched channel configuration against bundled channel schemas even when the patch only contains SecretRef objects.
|
||||
- Plugins/tools: keep disabled bundled tool plugins out of explicit runtime allowlist ownership and fall back from loaded-but-empty channel registries to tool-bearing plugin registries, so Active Memory can use bundled `memory-core` search/get tools even when `memory-lancedb` is disabled. Fixes #76603. Thanks @jwong-art.
|
||||
|
||||
@@ -56,6 +56,35 @@ describe("createLazyGatewayCronState", () => {
|
||||
expect(cron.start).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("does not start cron after stop wins the lazy startup race", async () => {
|
||||
const cron = createCronService();
|
||||
hoisted.setState(createCronState(cron));
|
||||
|
||||
const lazy = createLazyGatewayCronState(createParams());
|
||||
const startPromise = lazy.cron.start();
|
||||
|
||||
lazy.cron.stop();
|
||||
await startPromise;
|
||||
|
||||
expect(cron.start).not.toHaveBeenCalled();
|
||||
expect(cron.stop).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("allows a stopped loaded cron service to start again", async () => {
|
||||
const cron = createCronService();
|
||||
hoisted.setState(createCronState(cron));
|
||||
|
||||
const lazy = createLazyGatewayCronState(createParams());
|
||||
|
||||
await lazy.cron.start();
|
||||
lazy.cron.stop();
|
||||
await lazy.cron.start();
|
||||
|
||||
expect(hoisted.buildGatewayCronService).toHaveBeenCalledTimes(1);
|
||||
expect(cron.stop).toHaveBeenCalledTimes(1);
|
||||
expect(cron.start).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("keeps synchronous wake non-blocking before the cron service is loaded", async () => {
|
||||
const cron = createCronService();
|
||||
hoisted.setState(createCronState(cron));
|
||||
|
||||
@@ -20,6 +20,7 @@ export function createLazyGatewayCronState(params: LazyGatewayCronParams): Gatew
|
||||
const cronEnabled = process.env.OPENCLAW_SKIP_CRON !== "1" && params.cfg.cron?.enabled !== false;
|
||||
let loaded: LoadedGatewayCronState | null = null;
|
||||
let loading: Promise<LoadedGatewayCronState> | null = null;
|
||||
let stopped = false;
|
||||
|
||||
const load = async (): Promise<LoadedGatewayCronState> => {
|
||||
if (loaded) {
|
||||
@@ -37,15 +38,39 @@ export function createLazyGatewayCronState(params: LazyGatewayCronParams): Gatew
|
||||
|
||||
const cron: CronServiceContract = {
|
||||
async start() {
|
||||
stopped = false;
|
||||
const resolved = await load();
|
||||
if (stopped) {
|
||||
return;
|
||||
}
|
||||
if (resolved.started) {
|
||||
return;
|
||||
}
|
||||
resolved.started = true;
|
||||
await resolved.state.cron.start();
|
||||
if (stopped && resolved.started) {
|
||||
resolved.started = false;
|
||||
resolved.state.cron.stop();
|
||||
}
|
||||
},
|
||||
stop() {
|
||||
loaded?.state.cron.stop();
|
||||
stopped = true;
|
||||
if (loaded) {
|
||||
loaded.started = false;
|
||||
loaded.state.cron.stop();
|
||||
return;
|
||||
}
|
||||
if (loading) {
|
||||
void loading
|
||||
.then((resolved) => {
|
||||
if (!stopped) {
|
||||
return;
|
||||
}
|
||||
resolved.started = false;
|
||||
resolved.state.cron.stop();
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
},
|
||||
async status() {
|
||||
return await (await load()).state.cron.status();
|
||||
|
||||
@@ -704,8 +704,11 @@ describe("gateway hot reload", () => {
|
||||
);
|
||||
|
||||
expect(hoisted.cronInstances.length).toBe(2);
|
||||
expect(hoisted.cronInstances[0].stop).toHaveBeenCalledTimes(1);
|
||||
expect(hoisted.cronInstances[1].start).toHaveBeenCalledTimes(1);
|
||||
await vi.waitFor(() => {
|
||||
expect(
|
||||
hoisted.cronInstances.some((instance) => instance.start.mock.calls.length === 1),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
expect(hoisted.providerManager.stopChannel).toHaveBeenCalledTimes(5);
|
||||
expect(hoisted.providerManager.startChannel).toHaveBeenCalledTimes(5);
|
||||
|
||||
Reference in New Issue
Block a user