Skip to content

fix(dispatcher): guard eventEmitters.emit() against a torn-down project (aicshud/WHEEL#1027) - #142

Merged
so5 merged 4 commits into
RIKEN-RCCS:mainfrom
so5:fix/issue-1027-dispatcher-unguarded-emit
Sep 16, 2026
Merged

so5 merged 4 commits into
RIKEN-RCCS:mainfrom
so5:fix/issue-1027-dispatcher-unguarded-emit

Conversation

@so5

@so5 so5 commented Sep 16, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes aicshud/WHEEL#1027: dispatcher.js had several unguarded eventEmitters.get(projectRootDir).emit(...) call sites, the same defect class aicshud/WHEEL#1019 already fixed for _setComponentState/setTaskState. Once a project is fully torn down, eventEmitters.delete(projectRootDir) has already run, so eventEmitters.get(...) returns undefined and any late-resolving work that still tries to emit on it crashes with TypeError: Cannot read properties of undefined (reading 'emit').

A live occurrence from _dispatchTask was confirmed on real Fugaku hardware during aicshud/WHEEL#1024's investigation. Auditing the rest of the file found the identical unguarded pattern in four more places.

What changed

Applied the same if (ee) { ee.emit(...) } guard #1019 established to:

  • _dispatchTask (emit "taskDispatched") - the one confirmed live.
  • _delegate, parameterStudy branch (emit "componentStateChanged").
  • _delegate, workflow/stepjob branch (emit "componentStateChanged").
  • _PSHandler's debounced updateComponentJson (emit "componentStateChanged").
  • _viewerHandler (emit "resultFilesReady").

Testing

  • Test-first: red tests committed separately, then the fix.
  • Dedicated reproduction tests added for the three that are reasonably testable in isolation (_dispatchTask, _delegate's "workflow" branch, _viewerHandler). The parameterStudy branch of _delegate and _PSHandler's debounced callback are fixed by the identical one-line pattern already proven correct by the other three (and by #1019) but have no dedicated reproduction test: exercising them for real requires a full parameterStudy dispatch (scatter/gather files, a populated parameterSetting.json, nested child dispatch) - fixture infrastructure this test file does not otherwise have, disproportionate to build solely for this defensive-guard fix.
  • npm run testDocker -w server — full suite green (1665 passing, 15 pending, 0 failing).

Related

Filed against aicshud/WHEEL#1027 on GitLab (issue tracker for this project). Builds directly on aicshud/WHEEL#1023 (#141, merged) and #1020/#1021/#1022 (#140, merged).

🤖 Generated with Claude Code

https://claude.ai/code/session_01C3jKNM1qubomM8UTRdkEWu

so5 and others added 4 commits September 16, 2026 20:25
onCleanProject() must reconnect SSH for any remotehost with a pending
deferred cleanup (left behind by stopProject(), aicshud/WHEEL#1020/#1021)
before it can call runDeferredCleanups() - the SSH connection was already
unconditionally torn down by stopProject().

- transferrer.js: getDeferredCleanupRemotehostIDs() does not exist yet.
- handlers/projectController.js: onCleanProject() does not attempt any
  SSH reconnection, so a pending deferred cleanup is silently dropped
  once the project is cleaned instead of resumed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C3jKNM1qubomM8UTRdkEWu
…s on manual clean (aicshud/WHEEL#1023)

stopProject() (aicshud/WHEEL#1020/#1021) unconditionally disconnects
every SSH connection on stop, but deliberately leaves
deferredCleanupRegistry populated: a not-yet-executed downstream task
may still need the preserved remote-symlink target file if the project
is resumed. If the user cleans the project instead of resuming it,
that entry never gets a chance to run naturally - only runProject()'s
own natural-completion path calls runDeferredCleanups() - permanently
leaking the preserved remote files.

- transferrer.js: add getDeferredCleanupRemotehostIDs(), a non-mutating
  peek at which remotehosts a project still has pending deferred
  cleanups for.
- handlers/projectController.js: add reconnectAndRunDeferredCleanups(),
  called from onCleanProject() before cleanProject()'s git reset. It
  resolves each pending remotehostID's connection info the same way
  runDispatcher() does, reconnects via createSsh() (which may prompt
  for a password/passphrase - acceptable here since cleanProject is
  only ever triggered by an explicit user action), runs the deferred
  cleanups, then disconnects again.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C3jKNM1qubomM8UTRdkEWu
Same defect class as aicshud/WHEEL#1019: eventEmitters.get(projectRootDir)
returns undefined once a project has been fully torn down
(eventEmitters.delete(projectRootDir) already ran), so any late-resolving
work that still tries to emit on it crashes with "TypeError: Cannot read
properties of undefined (reading 'emit')". #1019 fixed this for
_setComponentState/setTaskState; a live "TypeError: Cannot read
properties of undefined (reading 'emit')" from _dispatchTask was then
confirmed on real Fugaku hardware during aicshud/WHEEL#1024's
investigation, and auditing the rest of dispatcher.js found the same
unguarded pattern in _delegate (two call sites) and _viewerHandler too.

Add reproduction tests for the three that are reasonably testable in
isolation (_dispatchTask, _delegate's "workflow" branch, _viewerHandler);
all three currently reject.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C3jKNM1qubomM8UTRdkEWu
…ct (aicshud/WHEEL#1027)

Apply the same if (ee) { ee.emit(...) } guard aicshud/WHEEL#1019 added to
_setComponentState/setTaskState to the remaining unguarded
eventEmitters.get(...).emit(...) call sites in dispatcher.js:

- _dispatchTask (emit "taskDispatched") - the one confirmed live on real
  Fugaku hardware during aicshud/WHEEL#1024's investigation.
- _delegate, parameterStudy branch (emit "componentStateChanged").
- _delegate, workflow/stepjob branch (emit "componentStateChanged").
- _PSHandler's debounced updateComponentJson (emit "componentStateChanged").
- _viewerHandler (emit "resultFilesReady").

The parameterStudy branch of _delegate and _PSHandler's debounced
callback are fixed by the identical one-line pattern already proven
correct by the other three (now test-covered) sites and by #1019, but
have no dedicated reproduction test here: exercising them for real
requires a full parameterStudy dispatch (scatter/gather files, a
populated parameterSetting.json, nested child dispatch), fixture
infrastructure this test file does not otherwise have and that would be
disproportionate to build solely for this defensive-guard fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C3jKNM1qubomM8UTRdkEWu
@so5
so5 merged commit 354000b into RIKEN-RCCS:main Sep 16, 2026
@so5
so5 deleted the fix/issue-1027-dispatcher-unguarded-emit branch September 16, 2026 12:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant