Skip to content

Commit d016926

Browse files
committed
fix(viewer): singleton guard prevents port drift on reactivation
When the host gateway restarts and re-calls activate(), the previous ViewerServer instance may still hold the port. Without cleanup, the new instance hits EADDRINUSE and drifts to 18800+, causing Memory unavailable. - Add module-level singleton tracking for ViewerServer - Stop previous viewer instance before creating new one in activate() - Ensure server.stop() fully releases the HTTP port Fixes #1430
1 parent 45f4c1b commit d016926

2 files changed

Lines changed: 31 additions & 3 deletions

File tree

apps/memos-local-openclaw/index.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ import { MEMORY_GUIDE_SKILL_MD } from "./src/skill/bundled-memory-guide";
3333
import { Telemetry } from "./src/telemetry";
3434

3535

36+
// Module-level singleton tracking for viewer/hub to prevent port drift on re-registration
37+
let _previousViewer: ViewerServer | null = null;
38+
let _previousHubServer: HubServer | null = null;
39+
let _previousStore: import('./src/storage/sqlite').SqliteStore | null = null;
40+
3641
/** Remove near-duplicate hits based on summary word overlap (>70%). Keeps first (highest-scored) hit. */
3742
function deduplicateHits<T extends { summary: string }>(hits: T[]): T[] {
3843
const kept: T[] = [];
@@ -2324,6 +2329,20 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
23242329

23252330
const derivedHubPort = gatewayPort + 11;
23262331

2332+
// Cleanup previous instances to prevent port drift (issue #1430)
2333+
if (_previousViewer) {
2334+
try { _previousViewer.stop(); } catch (e) { api.logger.warn(`memos-local: previous viewer cleanup: ${e}`); }
2335+
_previousViewer = null;
2336+
}
2337+
if (_previousHubServer) {
2338+
try { _previousHubServer.stop(); } catch (e) { api.logger.warn(`memos-local: previous hub cleanup: ${e}`); }
2339+
_previousHubServer = null;
2340+
}
2341+
if (_previousStore) {
2342+
try { _previousStore.close(); } catch (e) { api.logger.warn(`memos-local: previous store cleanup: ${e}`); }
2343+
_previousStore = null;
2344+
}
2345+
23272346
const viewer = new ViewerServer({
23282347
store,
23292348
embedder,
@@ -2337,6 +2356,10 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
23372356
? new HubServer({ store, log: ctx.log, config: ctx.config, dataDir: stateDir, embedder, defaultHubPort: derivedHubPort })
23382357
: null;
23392358

2359+
_previousViewer = viewer;
2360+
_previousHubServer = hubServer;
2361+
_previousStore = store;
2362+
23402363
// ─── Service lifecycle ───
23412364

23422365
let serviceStarted = false;

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -211,13 +211,18 @@ export class ViewerServer {
211211
}
212212
}
213213

214-
stop(): void {
214+
stop(): Promise<void> {
215215
this.stopHubHeartbeat();
216216
this.stopNotifPoll();
217217
for (const c of this.notifSSEClients) { try { c.end(); } catch {} }
218218
this.notifSSEClients = [];
219-
this.server?.close();
220-
this.server = null;
219+
if (!this.server) return Promise.resolve();
220+
return new Promise<void>((resolve) => {
221+
this.server!.close(() => {
222+
this.server = null;
223+
resolve();
224+
});
225+
});
221226
}
222227

223228
getResetToken(): string {

0 commit comments

Comments
 (0)