Skip to content

ADFA-5103 feat(env): tell a stopped service from a stopped environment; startEnvironment ensures, not stacks - #446

Merged
luisguzman-adfa merged 2 commits into
mainfrom
fix/ADFA-5103-environment-alive-detection
Aug 20, 2026
Merged

ADFA-5103 feat(env): tell a stopped service from a stopped environment; startEnvironment ensures, not stacks#446
luisguzman-adfa merged 2 commits into
mainfrom
fix/ADFA-5103-environment-alive-detection

Conversation

@luisguzman-adfa

@luisguzman-adfa luisguzman-adfa commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

What

startEnvironment now means "ensure it is up": it detects whether our environment
proot is alive (from /proc, off the main thread) and launches, no-ops, waits out
a boot grace, or kills+relaunches a stuck orphan — so Retry can no longer stack a
second proot over a live one (the ADR-4832 collision).

Why

pdsm stop leaves the proot running while the services die; the app's only runtime
signal is an HTTP ping, so "services down, environment alive" and "everything
down" were indistinguishable, and canStartServer read both as "nothing running".

How

  • EnvironmentEnsure (pure, JVM-tested): LAUNCH / NOOP_HEALTHY / WAIT_BOOT_GRACE /
    KILL_AND_RELAUNCH.
  • Boot grace measured from the proot's own /proc age (EnvironmentProcess.environmentAgeMs),
    so a young proot is never killed — including a force-closed predecessor's (the
    observed 3.5 s double-boot). Unknown age never kills.
  • Decision runs on the IO executor; /proc is never walked on the main thread. An
    ensuring guard serialises concurrent calls.
  • A proot cannot be re-entered, so a live-but-serviceless environment is recovered
    by ending it and booting fresh.

Scope / notes

Detection (EnvironmentProcess/Matcher) and killOrphan already existed and were
deliberately unwired; this is the decision layer that holds them. Background:
ADR-5061 action item 9.

Verification

Unit: EnvironmentEnsureTest (kill-mid-boot, unknown-age, grace boundary).
Device: (1) pdsm stop + Retry recovers with a single proot; (2) a tap during the
first ~15 s of boot does not kill the booting proot; (3) force-close + restore
mid-boot does not double-boot; (4) backup/restore recovers without stacking.

…t; startEnvironment ensures, not stacks

pdsm stop leaves the proot running while the services die, and the app's only
runtime signal is an HTTP ping — so "services down, environment alive" and
"everything down" were one observation, and Retry could start a second proot
over a live one (the ADR-4832 collision).

startEnvironment now means "ensure it is up", decided off the main thread:
launch when nothing of ours runs; no-op when it is already up or still inside
its boot grace; kill+relaunch only a stuck orphan (alive, services down, past
the grace). A proot cannot be re-entered, so a live-but-serviceless environment
is recovered by ending it and booting a fresh one.

The guards are what the earlier attempt lacked. The boot grace is measured from
the proot's own age (/proc/<pid>/stat starttime), not from when this process
launched it, so a young proot is protected whether we started it or a
force-closed predecessor did (the observed 3.5 s double-boot after an Activity
restore). An unknown age never kills. /proc is read on the IO executor, never on
the main thread — and it is the authoritative, cross-process source, so a second
controller no longer sees a live proot as an orphan (the "handle per process"
the ADR asked for, without a cached duplicate to keep honest).

Because the decision is now asynchronous, an `ensuring` guard restores the
serialisation the old synchronous path had: two concurrent startEnvironment()
calls cannot both read /proc, both see nothing, and both launch.

- EnvironmentEnsure (pure, JVM-tested): the LAUNCH / NOOP / WAIT_BOOT_GRACE /
  KILL_AND_RELAUNCH decision.
- EnvironmentProcess.environmentAgeMs(): the proot age for the boot grace.
- ServerController: startEnvironment -> ensure-up (async, guarded) +
  doLaunchEnvironment; BOOT_GRACE_MS.

Detection (EnvironmentProcess/Matcher) and killOrphan already existed and were
deliberately unwired; this is the decision layer that holds them. Background:
ADR-5061 action item 9.
@luisguzman-adfa
luisguzman-adfa force-pushed the fix/ADFA-5103-environment-alive-detection branch from 26e27f1 to c3938e0 Compare August 20, 2026 17:13
@luisguzman-adfa
luisguzman-adfa merged commit 820d2af into main Aug 20, 2026
3 checks passed
@luisguzman-adfa
luisguzman-adfa deleted the fix/ADFA-5103-environment-alive-detection branch August 20, 2026 17:21
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