fix(itc): direct-route component status responses; drop wasteful broadcast - #609
Conversation
…dcast componentStatusRequestHandler fell back to broadcasting status responses to every thread whenever the originator port was missing from connectedPorts — which happened on every main-thread-originated request (since sendItcEvent only stamped originator on workers) and on every race where the originator exited mid-request. - sendItcEvent now always stamps originator = threadId. Main thread's id is 0 by worker_threads convention. - parentPort.threadId = 0 is set in workers, so sendToThread(0, ...) routes back to main. - componentStatusRequestHandler drops silently at trace when sendToThread returns false; the collector's own 5s timeout handles missing replies. - connectedPorts.sendToThread wraps postMessage in try/catch so a port that closes between find() and postMessage() is treated as unreachable instead of throwing. - CrossThreadStatusCollector's 30s safety-net cleanup also reaps stale responseCheckers entries. Closes #574 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Reviewed; no blockers found. |
|
CI status: 33/36 checks green. The 2 failing checks are pre-existing Windows-only flakes that also fail on
All Linux Unit Tests, Linux Integration Tests, Build, Lint, Format, and Socket Security checks pass. 🤖 — Claude |
| port.postMessage(message); | ||
| return true; | ||
| } catch { | ||
| // Port may have closed between find() and postMessage() — treat as unreachable. |
There was a problem hiding this comment.
💡 Could we catch more specific errors here for the exceptions we expect?
There was a problem hiding this comment.
Narrowed the catch in 871427d — sendToThread now only swallows err.code === 'ERR_CLOSED_MESSAGE_PORT' (the documented closed-port race) and rethrows everything else. That keeps DataCloneError serialization bugs and other unexpected failures visible instead of silently turning into false. See server/threads/manageThreads.js:55-69.
🤖 — Claude
| expect(log_trace_stub).to.have.been.called; | ||
| }); | ||
|
|
||
| // Tests direct-response path: when sendToThread succeeds, log a trace and do not broadcast |
There was a problem hiding this comment.
The comments before the unit tests don't seem particularly useful
There was a problem hiding this comment.
Good call — removed the leading comments above the two new componentStatusRequestHandler test cases in 871427d (unitTests/server/itc/serverHandlers.test.js). The it(...) names already describe what each one covers.
🤖 — Claude
…ilerplate Address PR #609 review feedback: - sendToThread now only swallows ERR_CLOSED_MESSAGE_PORT (the documented port-closed-between-find-and-postMessage race) and rethrows everything else, so DataCloneError serialization bugs and other unexpected errors remain visible. - Remove redundant leading comments above the two new componentStatusRequestHandler tests; the test names already describe what they cover. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tus-direct-response-574 # Conflicts: # server/threads/itc.js
Summary
componentStatusRequestHandlerfell back to broadcasting the status response to every thread whenever the originator port wasn't inconnectedPorts. That fired on every main-thread-originated request and on every race where the originator exited mid-request — wasteful and noisy.This PR makes direct routing actually work for main-thread originators and drops the broadcast fallback entirely.
Changes
server/threads/itc.js—sendItcEventnow always stampsevent.message.originator = threadId. Removed the!isMainThreadguard. Main thread'sthreadIdis0byworker_threadsconvention.server/threads/manageThreads.js:parentPort.threadId = 0soconnectedPorts.sendToThread(0, ...)routes back to main.sendToThreadwrapspostMessageintry/catch— a port closing betweenfind()andpostMessage()is now treated as unreachable instead of throwing.server/itc/serverHandlers.js— whensendToThreadreturnsfalse, log attraceand drop. TheCrossThreadStatusCollector's own 5s timeout already handles missing replies, so a broadcast just costs every other thread a Map lookup for a response it didn't ask for.components/status/crossThread.ts— the 30s safety-net cleanup also clearsresponseCheckers(it previously only clearedawaitingResponses).unitTests/server/itc/serverHandlers.test.js— new tests for both the direct-send and silent-drop paths.Purpose
Closes #574 (CORE-3049). Acceptance criteria from the issue:
awaitingResponsescleanup safety net audited; extended to also reap staleresponseCheckers.Where to look
parentPort.threadId = 0(manageThreads.js) — please double-check this doesn't collide with any Node behavior I haven't considered. The Node convention is that the main thread'sthreadIdis0;parentPortis aMessagePort, not aWorker, so we're free to attach athreadIdproperty. All existing call sites that compareport.threadId === someIdwill find it.awaitingResponses), so the consequences of broadcasting were small; but the volume could be high in fabric setups. After this change, a stuck/crashed originator just times out via the collector's 5s timeout instead of getting a useless reply blast.sendToThreadtry/catch aroundpostMessage— silently swallows post failures and returnsfalse. This is correct for the race the issue describes, but worth a sanity check that we don't want to surface a different error class here.Test plan
unitTests/server/itc,unitTests/components/status) — all 156 tests pass.serverHandlers.test.js.[debug]: Failed to send direct response to thread N, falling back to broadcastlog is gone.Notes
requestIdin the drop trace) is applied.🤖 Generated with Claude Code