Skip to content

Commit 5acb8ce

Browse files
committed
fix: address review feedback on duplicate instance prevention
- Add stale-instance guard in self-start setTimeout: skip if this instance was already replaced by a newer register() call. - ViewerServer.stop(): add 3s timeout fallback and Node 18.0.x compat (closeIdleConnections + unref when closeAllConnections is missing). - Use explicit error formatting in cleanup catch block. - Bind error handler before listen() in test helper.
1 parent a330c4f commit 5acb8ce

3 files changed

Lines changed: 18 additions & 5 deletions

File tree

apps/memos-local-openclaw/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2424,7 +2424,7 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
24242424
await instanceToReplace.telemetry.shutdown().catch(() => {});
24252425
instanceToReplace.store.close();
24262426
} catch (err) {
2427-
api.logger.warn(`memos-local: previous instance cleanup error: ${err}`);
2427+
api.logger.warn(`memos-local: previous instance cleanup error: ${err instanceof Error ? err.message : String(err)}`);
24282428
}
24292429
api.logger.info("memos-local: previous instance stopped");
24302430
}
@@ -2489,6 +2489,8 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
24892489
// service.start() immediately after registration.
24902490
const SELF_START_DELAY_MS = 0;
24912491
setTimeout(() => {
2492+
// Abort if this instance was already replaced by a newer register() call
2493+
if (activeInstances.get(stateDir)?.viewer !== viewer) return;
24922494
if (!serviceStarted) {
24932495
api.logger.info("memos-local: service.start() not called by host, self-starting viewer...");
24942496
startServiceCore().catch((err) => {

apps/memos-local-openclaw/src/viewer/server.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -220,9 +220,20 @@ export class ViewerServer {
220220
const srv = this.server;
221221
this.server = null;
222222
return new Promise<void>((resolve) => {
223-
srv.close(() => resolve());
224-
// Force-close idle keep-alive connections so close() doesn't hang
225-
srv.closeAllConnections?.();
223+
const timeout = setTimeout(() => resolve(), 3000);
224+
srv.close(() => { clearTimeout(timeout); resolve(); });
225+
// Force-close idle keep-alive sockets. closeAllConnections is
226+
// available from Node 18.2; fall back to destroying tracked sockets.
227+
if (typeof srv.closeAllConnections === "function") {
228+
srv.closeAllConnections();
229+
} else {
230+
// Older Node: close idle connections via closeIdleConnections
231+
// (18.0+) or just unref so the event loop can exit.
232+
if (typeof (srv as any).closeIdleConnections === "function") {
233+
(srv as any).closeIdleConnections();
234+
}
235+
srv.unref();
236+
}
226237
});
227238
}
228239

apps/memos-local-openclaw/tests/e2e-duplicate-instance.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@ fs.mkdirSync(path.join(tmpDir, "skills"), { recursive: true });
2424
function findFreePort(): Promise<number> {
2525
return new Promise((resolve, reject) => {
2626
const srv = net.createServer();
27+
srv.on("error", reject);
2728
srv.listen(0, "127.0.0.1", () => {
2829
const port = (srv.address() as net.AddressInfo).port;
2930
srv.close(() => resolve(port));
3031
});
31-
srv.on("error", reject);
3232
});
3333
}
3434

0 commit comments

Comments
 (0)