]> Piment Noir Git Repositories - e-mobility-charging-stations-simulator.git/commit
fix(bootstrap): make start/stop idempotent and signal handler re-entrant safe (#1905)
authorJérôme Benoit <jerome.benoit@piment-noir.org>
Tue, 16 Jun 2026 17:02:22 +0000 (19:02 +0200)
committerGitHub <noreply@github.com>
Tue, 16 Jun 2026 17:02:22 +0000 (19:02 +0200)
commit85c1fb034ee9659418cccbe4dc47679ae0edbd74
treec96a7530ed15b9ef715aa5c39d1f01257f8b5f68
parentf9e8ddfabec327b1ef2d0b76652fed9b1f334408
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.
cspell.config.yaml
src/charging-station/Bootstrap.ts
tests/charging-station/Bootstrap.test.ts [new file with mode: 0644]