fix(server): rebind message-stream upstreams after a managed OpenCode restart
When the managed OpenCode process exits but a server survives on the old port (Windows: killProcessOnPort is a no-op, so the orphaned process tree keeps the port), restartOpenCode() times out waiting for the port and spawns a fresh server on a NEW port. HTTP/proxy traffic follows the new port, but the global message-stream hub's upstream SSE reader stays pinned to the old server's /global/event stream — that connection never closes — so new events never reach the UI and the chat stops updating until the app is restarted (#2638). Lifecycle now fires an optional onOpenCodeRestarted hook after a successful managed restart; index.js wires it to the new messageStreamRuntime.rebindUpstream(), which restarts the shared hub (its reader re-dials buildOpenCodeUrl → the current port) and closes directory-scoped sockets so their per-connection readers rebuild against the new port. External servers are untouched (their port cannot change). Fixes #2638
This commit is contained in:
@@ -70,6 +70,12 @@ export function createMessageStreamWsRuntime({
|
||||
noServer: true,
|
||||
});
|
||||
|
||||
// Directory-scoped streams create one upstream reader per client
|
||||
// connection. Track those sockets so a managed OpenCode restart can close
|
||||
// them: each reader is pinned to the port it connected at and would
|
||||
// otherwise keep streaming from an orphaned process on the old port (#2638).
|
||||
const directorySockets = new Set();
|
||||
|
||||
const ownsGlobalHub = !globalEventHub;
|
||||
const globalHub = globalEventHub ?? createGlobalMessageStreamHub({
|
||||
buildOpenCodeUrl,
|
||||
@@ -103,6 +109,11 @@ export function createMessageStreamWsRuntime({
|
||||
return;
|
||||
}
|
||||
|
||||
directorySockets.add(socket);
|
||||
socket.on('close', () => {
|
||||
directorySockets.delete(socket);
|
||||
});
|
||||
|
||||
acceptDirectoryMessageStreamWsConnection({
|
||||
socket,
|
||||
requestedLastEventId,
|
||||
@@ -156,6 +167,27 @@ export function createMessageStreamWsRuntime({
|
||||
|
||||
return {
|
||||
wsServer,
|
||||
/**
|
||||
* Rebind all upstream readers to the current OpenCode port. Called after
|
||||
* a managed process restart: the restart can land on a NEW port while
|
||||
* the old process (or an orphaned survivor of it) still holds the
|
||||
* previous one, and a healthy-but-pinned SSE connection never notices —
|
||||
* so the UI would stop receiving events until the app restarts (#2638).
|
||||
* Restarting the shared hub re-dials `buildOpenCodeUrl` (which reads the
|
||||
* current port) on its next attempt; directory-scoped readers are
|
||||
* rebuilt by closing their client sockets, which reconnect with
|
||||
* `Last-Event-ID` and re-establish the stream against the new port.
|
||||
*/
|
||||
rebindUpstream() {
|
||||
globalHub.stop();
|
||||
globalHub.start();
|
||||
for (const socket of Array.from(directorySockets)) {
|
||||
try {
|
||||
socket.close(1012, 'OpenCode upstream restarted');
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
},
|
||||
async close() {
|
||||
server.off('upgrade', upgradeHandler);
|
||||
globalBridge.close();
|
||||
|
||||
Reference in New Issue
Block a user