Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@
* (a dead launch) — and folds the three scattered in-memory "is the planter still running" checks into
* one durable fact.
*
* <p><b>K2GO-384: a fourth reading, {@link #isDamaged DAMAGED}</b> (a sentinel token, {@link #markDamaged}).
* A dead-launch marker is <em>inferred</em> damage — the base might be fine, so it is tried. A destructive
* write force-cancelled mid-rootfs (a restore extract) is <em>known</em> damage — a distinct token says so,
* so the one reader that would otherwise try to boot it ({@code isSystemInstalled}) does not. It still reads
* {@code isInterrupted}, so recovery owns it with no other change. See ADR-5343c.
*
* <p><b>Deliberately NOT self-healing like {@link org.appdevforall.k2go.env.EnvironmentLock}.</b>
* EnvironmentLock deletes a stale marker on read, because a killed <em>coordination</em> lock means no
* op is running so the lock must not stay held. InstallGuard also carries a <em>damage</em> job: a
Expand All @@ -44,6 +50,14 @@ public final class InstallGuard {

private static final String MARKER = ".install_in_progress";

/**
* K2GO-384: the sentinel token for KNOWN damage — a destructive write (today: a force-cancelled restore
* extract) that deliberately abandoned a half-applied rootfs. Not a per-launch UUID, so it can never
* equal a {@link ProcessSession#ID}: it reads as not-{@link #isLive}, still {@link #isInterrupted}
* (recovery owns it, unchanged), and additionally {@link #isDamaged}. See ADR-5343c.
*/
private static final String DAMAGED_TOKEN = "DAMAGED";

private InstallGuard() {
}

Expand All @@ -69,6 +83,24 @@ public static void end(Context ctx) {
marker(ctx).delete();
}

/**
* K2GO-384: downgrade the marker to KNOWN-DAMAGED. Written by the owner of a destructive write that has
* decided to abandon a half-applied rootfs (today: a force-cancelled restore extract, {@code
* DeepOpService}). It overwrites the LIVE token this launch planted at the extract boundary, so the base
* stops reading as "an install running now" — the {@code k2go_busy_install} gate lifts and the box is no
* longer held down by a live-install holder. The marker stays present and reads {@link #isInterrupted},
* so the entire recovery/verdict path owns it exactly as it owns a dead-launch marker — the only added
* fact is {@link #isDamaged} (see it for the one reader that treats known damage differently). Overwrites,
* like {@link #begin}.
*/
public static void markDamaged(Context ctx) {
try (FileWriter w = new FileWriter(marker(ctx), false)) {
w.write(DAMAGED_TOKEN);
} catch (IOException ignored) {
// Best-effort: a marker left LIVE still recovers next launch (token mismatch -> INTERRUPTED).
}
}

/**
* A marker planted by THIS process launch — an install is running now, in this process. The
* coordination readers (is-installed, the holder, the toggle gate, canStartServer) read this: only a
Expand All @@ -90,6 +122,20 @@ public static boolean isInterrupted(Context ctx) {
return token != null && !ProcessSession.ID.equals(token);
}

/**
* K2GO-384: a marker a destructive op left after deliberately abandoning a half-applied rootfs — KNOWN
* damage, as opposed to the {@link #isInterrupted} state's INFERRED damage (a dead launch's token, which
* might be a perfectly fine base). A known-damaged marker also reads {@code isInterrupted} (so recovery
* owns it unchanged); the one place the distinction matters is {@code
* SystemStateEvaluator.isSystemInstalled}: an interrupted base falls through to {@code rootfsPresent} and
* is <em>tried</em> — booting it is how recovery tells a fine base from a damaged one (ADFA-5330) — but a
* known-damaged base is <em>not installed</em> and must not be booted at all (there is nothing to learn
* from trying; it would only flap the reconciler on a torn rootfs). See ADR-5343c. Never deletes.
*/
public static boolean isDamaged(Context ctx) {
return DAMAGED_TOKEN.equals(read(ctx));
}

/**
* The marker exists at all — a LIVE or an INTERRUPTED install, regardless of which launch planted it.
* The right query where an interrupted install must count the same as a live one: "is anything here or
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ public static boolean isSystemInstalled(Context ctx) {
if (InstallGuard.isLive(ctx)) {
return false;
}
// K2GO-384 (ADR-5343c): KNOWN damage — a destructive write (a force-cancelled restore extract) left a
// half-applied rootfs and said so with a sentinel token. Unlike an INTERRUPTED marker (a dead launch,
// maybe-fine → falls through to rootfsPresent so the reconciler may TRY to boot it, ADFA-5330), this
// base is not installed: there is nothing to learn from booting a rootfs we tore ourselves. Forcing
// false keeps desired=DOWN (no flap on the torn base); recovery still owns it via isInterrupted.
if (InstallGuard.isDamaged(ctx)) {
return false;
}
return rootfsPresent(ctx);
}

Expand Down
Loading
Loading