Skip to content

fix(session): don't resurrect a session stopped/deleted during start() - #405

Merged
rmyndharis merged 1 commit into
mainfrom
fix/session-start-stop-race
Jun 21, 2026
Merged

fix(session): don't resurrect a session stopped/deleted during start()#405
rmyndharis merged 1 commit into
mainfrom
fix/session-start-stop-race

Conversation

@rmyndharis

Copy link
Copy Markdown
Owner

Summary

start() reserves its slot, clears the stop flag, then awaits initializeEngine() (which registers the engine and runs engine.initialize()). If a stop() or delete() lands during that await, it sets the stopping flag and tears down — but start() did not re-check afterward, so the freshly-created engine was left registered and driven to READY. The session came back up even though it was meant to be down (and could orphan a Chromium against a deleted session row).

start() now mirrors the proven post-init guard already used by executeReconnect(): after initializeEngine(), if the session is marked stopping, tear down the just-registered engine via teardownEngineSafely and remove it before returning. A concurrent stop/delete wins.

Scope note: the narrower window where initialize()'s async callbacks fire after start() returns is intentionally left unchanged — that matches the existing executeReconnect path, and the alternative (an engine-identity check inside the callbacks) is higher-risk (it could suppress a legitimate status write during a normal reconnect). This PR takes only the low-risk, proven guard.

Tests

  • session.service.spec.ts: a stop/delete simulated during initialize() now results in the engine being destroy()ed and removed (getEngine undefined) rather than left READY.
  • Full gate: lint 0 · build OK · 1083 unit (incl. coverage thresholds) · 26 e2e pass.

Risk

Low. The guard mirrors logic already in production on the reconnect path; it only fires when a stop/delete genuinely raced the start, and the safe outcome is "the session stays down" (what the operator asked for).

If a stop()/delete() landed while start() was awaiting the engine's initialize(),
the freshly-created engine was left registered and READY — the session came back
up even though it was meant to be down. start() now re-checks stoppingSessions
after initializeEngine() and, if set, tears down the just-registered engine and
removes it (mirroring the proven post-init guard in executeReconnect). The
narrower async-callback window is intentionally left as-is, matching the existing
reconnect path, to avoid the higher-risk callback-identity change.
@rmyndharis
rmyndharis merged commit 6755299 into main Jun 21, 2026
5 checks passed
@rmyndharis
rmyndharis deleted the fix/session-start-stop-race branch June 21, 2026 13:03
mmr94 added a commit to mmr94/unisoft-OpenWA that referenced this pull request Jun 24, 2026
Le fork avait 315 commits / 6 versions de retard. Ce merge corrige la cause
racine d'un bug d'envoi de média WhatsApp : MessageMedia.fromUrl() sans timeout
(fetch interne + DNS non borné) bloquait Chromium jusqu'au protocolTimeout, vu
côté Kehila comme un NETWORK_ERROR en boucle. Upstream remplace ce chemin par
loadRemoteMedia() (fetch borné + SSRF-gardé, rmyndharis#404) et ajoute POST /:id/force-kill
pour tuer un moteur Chromium bloqué.

Conflits résolus en préservant la feature maison d'hibernation de sessions ET les
fixes de fiabilité upstream (rmyndharis#404 SSRF/DNS, rmyndharis#405 no-resurrect, rmyndharis#410 superseded
engine, rmyndharis#415 reconcile-ready) :
- session.service.ts (9 zones) : intentionalStops + stoppingSessions combinés
  dans onDisconnected ; hibernate/wake/ensureEngineReady/markActivity cohabitent
  avec forceKill ; cleanup hibernation greffé dans onModuleDestroy parallèle.
- session.controller.ts : endpoints wake + force-kill ; transformSession délègue
  à SessionResponseDto.fromEntity (mapper complété avec lastSent).
- bulk-message.service.ts : markActivity + persistSentMessage conservés.
- events.gateway / spec / docs : fusion des deux côtés.

Corrections post-merge : registre HOOK_EVENT_REGISTRY complété (events hibernation),
await manquant sur getEngine dans getChatHistory, mocks ensureEngineReady/markActivity
dans bulk-message.spec.

Vérifié : nest build OK ; session.service.spec 105/105 ; bulk-message.spec 15/15.
(Échecs sqlite3 résiduels = binding natif absent en local, sans rapport avec le code.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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