fix(bootstrap): make start/stop idempotent and signal handler re-entrant safe (#1905)
- Memoize startPromise/stopPromise so concurrent callers await the same
in-flight transition instead of silently no-op'ing.
- Extract start()/stop() bodies into private doStart()/doStop() so the
public methods are thin memoization wrappers.
- Demote idempotent guard hits from error to warn (matches sister
ChargingStation convention) for "Cannot start/stop already ..." paths
and to debug for "Awaiting in-flight ..." concurrency observations.
- Make gracefulShutdown re-entrant safe via shuttingDown flag --
multiple SIGTERM/SIGINT/SIGQUIT no longer race the in-flight stop.
- On the no-op stop path with reason=user, ensure
.simulator-state.json still reflects the authoritative state (writes
started:false if file says started:true). Fixes UI clients reading
stale started:true after a UI-driven stop on an already-stopped sim.
- Document Bootstrap.stop coalescing semantics: the FIRST caller's
reason controls the in-flight transition; later callers' reasons are
ignored.
- Add 'entrancy' to cspell dictionary (re-entrancy is the standard
concurrency-engineering spelling).
Tests: tests/charging-station/Bootstrap.test.ts (new file, 11 tests)
- concurrent stop() / start() callers observe the same in-flight
transition
- gracefulShutdown is re-entrant: 3 synchronous calls invoke stop()
exactly once (direct unit test on Bootstrap.prototype, runs on every
platform including Windows)
- multiple SIGTERM produce a single 'Graceful shutdown' log line
(POSIX-only spawn-based integration smoke; skipped on Windows where
child.kill('SIGTERM') maps to TerminateProcess and bypasses the
handler -- coverage for that platform comes from the unit test
above)
- state-file consistency on the no-op stop path (3 sub-cases:
reason=user, reason=shutdown, real-stop-then-no-op-stop)
- idempotent guards log at warn or debug, not error (4 sub-cases)
Reproduction: 5x kill -TERM in tight loop, before fix produces 'Cannot
stop an already stopping' error log; after fix produces a single
'Graceful shutdown' info log with no error.
Refs: review feedback on PR #1905 -- 1 BLOCKER, 4 MAJOR, 4 MINOR
findings, all addressed in this amended commit.