Two related leaks on the embedded Postgres startup-race join. The flagged one: the catch dropped `nonAdminHandle` to null without stopping it, so a wrapper that onLaunched had already published leaked. The obvious fix -- call handle.stop() first -- is worse than the leak. stop() runs killAll(), which resolves its target by reading line 1 of the data dir's postmaster.pid. On this path that file belongs to the process that WON the race, so stop() would taskkill the instance we are joining. pg.stop() is the same trap via pg_ctl -D on the shared dir, which is why settleCancelledStart (it calls both) cannot be reused here. Added NonAdminServerHandle.stopWrapperOnly(), which kills only our wrapper pid and its children, and called it before the handle is dropped. A racing winner is another process's child, so /t cannot reach it. The one found while making that safe: the catch joined on ANY start failure. A start that took the lock and then failed later (readiness timeout, non-admin poll error) reads back its OWN postmaster.pid, so isAlreadyRunning hands back our own port and we "join" ourselves with ownsProcess=false -- nothing ever stops it, orphaning a live postmaster for the life of the host. The join now fires only on a lock-collision error, which is the one failure proving our postgres refused to start and someone else owns the dir. Every other failure returns to the existing cancellation/cleanup paths, which stop what they started. That is also what makes the wrapper-only kill provably safe: on this path our postgres never took the lock. Tests: a non-lock failure must propagate even with a postmaster.pid present (fails without the fix -- the old catch swallowed it and joined), and a lock collision must still join. Both always-on with a mocked ctor. Pre-existing and unrelated: taskstore-remaining.test.ts fails identically on a clean tree with these changes stashed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1.1 KiB
@runfusion/fusion
| @runfusion/fusion |
|---|
| patch |
summary: Fix startup failures and a leaked server when two Fusion processes start embedded Postgres at the same time.
category: fix
dev: A lifecycle joining an already-running instance returned a URL before the owner's ensureDatabase() had created the database, so the joiner's first connect failed. Both join paths now verify the database on the joined instance's port (never getPort(), which prefers this instance's requested port) and create it if absent. Verification is best-effort so an unreachable/stale-pid join still resolves optimistically as before. CREATE DATABASE races tolerate both 42P04 and 23505 on pg_database_datname_index. The startup-race join now fires only on a lock-collision error — joining on any failure let a start that took the lock and then failed later join its own postmaster with ownsProcess=false, orphaning it — and reaps its own losing wrapper via the new NonAdminServerHandle.stopWrapperOnly(), since stop()/pg.stop() resolve through the shared data dir and would kill the winner.