What's Changed
Fixed
- A dependency-gated daemon no longer wedges permanently after its dependency's readiness flaps. Since 2.0.0's hold/release refactor,
Daemon.term()unconditionally destroyed the daemon'sSubContainer, andHealthDaemoncallsterm()every time a dependency goes not-ready. When the dependency's health then recovered, the daemon'sstart()calledhold()on the already-destroyed subcontainer and threwcannot hold subcontainer …: already destroyed. Because thatstart()was un-awaited, it surfaced as an unhandled rejection and the daemon never recovered — the health check reported "not ready" / "daemon crashed" indefinitely, curable only by a full package restart. Any service with a dependency-gated daemon was exposed (e.g.c-lightning's and BTCPay's web UIs). The 2.0.0 refactor had removed thedestroySubcontainerflag that previously kept the subcontainer alive across a stop, collapsing "pause" and "teardown" into one destroyingterm(). This restores the distinction under the hold/release model:Daemongains a non-destroyingstop()(aborts the loop, terminates the process, releases the hold — but leaves theSubContainerintact so a laterstart()re-holds it), andHealthDaemon.changeRunning(false)now callsstop()for a dependency-driven pause.Daemon.term()(used byHealthDaemon.term()andDaemons.term()for genuine teardown) still releases the hold and destroys the subcontainer.HealthDaemon.changeRunning(true)additionally awaits and catchesstart(), so a start failure becomes a health failure rather than a silent unhandled-rejection wedge - Dependency-driven pause/resume transitions are now serialized.
HealthDaemon.updateStatus()runs fire-and-forget from dependency watchers, so a fast readiness flap (not-ready → ready before the pause'sstop()finished draining the process) could overlap the pause with the following resume. BecauseDaemon.start()is a no-op while the previous run loop is still winding down, the resume'sstart()could silently do nothing and leave the daemon stopped while the health session believed it was running — the same class of wedge, reachable via a fast flap.HealthDaemonnow applies pause/resume transitions on an in-order promise chain, so a resume always waits for the in-flight pause to complete before re-holding and restarting