mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-28 20:46:57 +02:00
fix(gateway): clean up retired channel lifecycles
This commit is contained in:
@@ -309,19 +309,33 @@ describe("server-channels auto restart", () => {
|
||||
|
||||
it("force-retires a hung channel task so recovery can start a fresh lifecycle", async () => {
|
||||
const statusSetters: Array<(next: ChannelAccountSnapshot) => void> = [];
|
||||
const startAccount = vi.fn(async ({ setStatus }: ChannelGatewayContext<TestAccount>) => {
|
||||
statusSetters.push(setStatus);
|
||||
await new Promise<void>(() => {});
|
||||
});
|
||||
const channelRuntime = createRuntimeChannel();
|
||||
const contextKey = {
|
||||
channelId: "discord",
|
||||
accountId: DEFAULT_ACCOUNT_ID,
|
||||
capability: "test-lifecycle",
|
||||
};
|
||||
const startAccount = vi.fn(
|
||||
async ({ setStatus, channelRuntime }: ChannelGatewayContext<TestAccount>) => {
|
||||
const lifecycle = statusSetters.length + 1;
|
||||
statusSetters.push(setStatus);
|
||||
channelRuntime?.runtimeContexts.register({
|
||||
...contextKey,
|
||||
context: { lifecycle },
|
||||
});
|
||||
await new Promise<void>(() => {});
|
||||
},
|
||||
);
|
||||
installTestRegistry(
|
||||
createTestPlugin({
|
||||
startAccount,
|
||||
}),
|
||||
);
|
||||
const manager = createManager();
|
||||
const manager = createManager({ channelRuntime });
|
||||
|
||||
await manager.startChannels();
|
||||
await Promise.resolve();
|
||||
expect(channelRuntime.runtimeContexts.get(contextKey)).toEqual({ lifecycle: 1 });
|
||||
|
||||
const stopTask = manager.stopChannel("discord", DEFAULT_ACCOUNT_ID, {
|
||||
forceRetireOnTimeout: true,
|
||||
@@ -335,9 +349,11 @@ describe("server-channels auto restart", () => {
|
||||
expect(account?.connected).toBe(false);
|
||||
expect(account?.activeRuns).toBe(0);
|
||||
expect(account?.lastError).toContain("stale lifecycle force-retired");
|
||||
expect(channelRuntime.runtimeContexts.get(contextKey)).toBeUndefined();
|
||||
|
||||
await manager.startChannel("discord", DEFAULT_ACCOUNT_ID);
|
||||
await Promise.resolve();
|
||||
expect(channelRuntime.runtimeContexts.get(contextKey)).toEqual({ lifecycle: 2 });
|
||||
|
||||
expect(startAccount).toHaveBeenCalledTimes(2);
|
||||
statusSetters[1]?.({
|
||||
|
||||
@@ -39,6 +39,7 @@ type ChannelRuntimeStore = {
|
||||
aborts: Map<string, AbortController>;
|
||||
starting: Map<string, Promise<void>>;
|
||||
tasks: Map<string, Promise<unknown>>;
|
||||
taskCleanups: Map<string, () => Promise<void>>;
|
||||
runtimes: Map<string, ChannelAccountSnapshot>;
|
||||
};
|
||||
|
||||
@@ -61,6 +62,7 @@ function createRuntimeStore(): ChannelRuntimeStore {
|
||||
aborts: new Map(),
|
||||
starting: new Map(),
|
||||
tasks: new Map(),
|
||||
taskCleanups: new Map(),
|
||||
runtimes: new Map(),
|
||||
};
|
||||
}
|
||||
@@ -423,6 +425,10 @@ export function createChannelManager(opts: ChannelManagerOptions): ChannelManage
|
||||
log.error?.(`[${id}] ${label}: ${formatErrorMessage(error)}`);
|
||||
}
|
||||
};
|
||||
const cleanupRetiredLifecycle = async () => {
|
||||
await cleanupTaskScopedApprovalRuntime("channel lifecycle retirement cleanup failed");
|
||||
};
|
||||
store.taskCleanups.set(id, cleanupRetiredLifecycle);
|
||||
|
||||
try {
|
||||
const account = plugin.config.resolveAccount(cfg, id);
|
||||
@@ -542,6 +548,9 @@ export function createChannelManager(opts: ChannelManagerOptions): ChannelManage
|
||||
})
|
||||
.finally(async () => {
|
||||
await cleanupTaskScopedApprovalRuntime("channel cleanup failed");
|
||||
if (store.taskCleanups.get(id) === cleanupRetiredLifecycle) {
|
||||
store.taskCleanups.delete(id);
|
||||
}
|
||||
if (isCurrentLifecycle()) {
|
||||
setRuntime(channelId, id, {
|
||||
accountId: id,
|
||||
@@ -627,6 +636,9 @@ export function createChannelManager(opts: ChannelManagerOptions): ChannelManage
|
||||
if (!handedOffTask && store.aborts.get(id) === abort) {
|
||||
store.aborts.delete(id);
|
||||
}
|
||||
if (!handedOffTask && store.taskCleanups.get(id) === cleanupRetiredLifecycle) {
|
||||
store.taskCleanups.delete(id);
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
@@ -700,6 +712,11 @@ export function createChannelManager(opts: ChannelManagerOptions): ChannelManage
|
||||
if (store.tasks.get(id) === task) {
|
||||
store.tasks.delete(id);
|
||||
}
|
||||
const cleanupRetiredTask = store.taskCleanups.get(id);
|
||||
if (cleanupRetiredTask) {
|
||||
store.taskCleanups.delete(id);
|
||||
await cleanupRetiredTask();
|
||||
}
|
||||
setRuntime(channelId, id, {
|
||||
accountId: id,
|
||||
running: false,
|
||||
@@ -722,6 +739,7 @@ export function createChannelManager(opts: ChannelManagerOptions): ChannelManage
|
||||
}
|
||||
store.aborts.delete(id);
|
||||
store.tasks.delete(id);
|
||||
store.taskCleanups.delete(id);
|
||||
setRuntime(channelId, id, {
|
||||
accountId: id,
|
||||
running: false,
|
||||
|
||||
Reference in New Issue
Block a user