Skip to content

K2GO-393 fix(maps): recover a half-done install (runrole mode, completion gate, stall kill) - #557

Merged
luisguzman-adfa merged 6 commits into
mainfrom
fix/K2GO-393-maps-recover-half-done
Sep 7, 2026
Merged

K2GO-393 fix(maps): recover a half-done install (runrole mode, completion gate, stall kill)#557
luisguzman-adfa merged 6 commits into
mainfrom
fix/K2GO-393-maps-recover-half-done

Conversation

@luisguzman-adfa

@luisguzman-adfa luisguzman-adfa commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

Maps is a proot install. Three defects left a half-done install stuck with no way
out; this branch fixes recovery for all three.

What changed:

  • Runrole mode is chosen at runtime from the completion marker (A1): --reinstall over
    a finished/base-seeded install, plain runrole maps to recover a half-done one. A
    bare --reinstall errored when a prior failed run had already deleted the marker --
    the retry bug this removes.
  • "Maps installed" now reads the iiab_state completion marker (maps_installed), not
    the local_vars intent (maps_install) (A3). An app death mid-install left the intent
    claiming maps was installed with nothing downloaded; the completion marker is written
    only when the role finishes, so a half-done install reads not-installed and stays
    recoverable. isCompletionGated shares this decision with the hub's probe, so a live
    probe cannot re-mask a half-done maps.
  • The module stall watch now kills a genuinely hung runrole (A4): a six-minute
    no-movement backstop beside the existing soft "stalled" hint. The kill drives the
    existing per-module failure path (revert + Retry) -- no new plumbing.

Why: on an unstable network the in-proot download can hang forever, and a half-done
install could not be retried (the retry errored, or the app showed maps falsely
installed).

Verification (device: OnePlus 7T, arm64):

  • A1: full cycle -- fail -> plain-runrole retry -> recover (re-fetched only the missing
    file, PLAY RECAP failed=0).
  • A3: with the completion marker absent and the intent still true, both the module
    detail and the hub show maps Not installed / Install now even while content still
    serves; with the marker present, both show Installed.
  • A4: a runrole made silent (a hung stand-in) triggered the kill at exactly 360s
    ("[Stall] ... killing the runrole"), which drove the failure path to the "Couldn't
    build maps ... retry" card. The kill reaches recovery via onError (it closes the
    output stream), which routes to Retry the same as onProcessExit.
  • Unit tests: InstalledModules (completion rule) and MapsRunroleCommand (runtime mode).

Notes:

  • killProcess SIGKILLs proot and orphans its in-container child (shared with doCancel).
    Left as-is deliberately, grounded in recon: per roles/maps/download_large_file.yml the
    orphan is transient (aria2c self-exits: max-tries=5, timeout=60) or idle (the meta4
    requests.get), and neither firehoses disk. A device test also ruled out a clean fix:
    proot ignores SIGTERM (neither dies nor runs --kill-on-exit), so SIGKILL is the only
    kill and the sole reap path is a /proc sweep. That sweep (reuse EnvironmentProcess) is
    only warranted if a retry-conflict is ever observed in the field.
  • Scope: controller/app -- InstalledModules(+Reader), LocalVarsYamlParser,
    ModuleHubFragment, MapsRunroleCommand, InstallService. Assets (help.db,
    module_sizes.csv, rootfs_sizes.csv) deliberately excluded.

…ot error

MapsRunroleCommand hardcoded 'runrole --reinstall maps'. --reinstall
requires an existing maps_ line in iiab_state.yml to delete; after a
failed install that line is gone, so a retry errors out (exit 1). This
is the retry bug for a half-done maps install.

Choose the mode at runtime by the marker, mirroring runrole's own
grep '^maps_' $IIAB_STATE_FILE gate: marker present (first selection or
a re-selection over a completed/base-seeded install) -> --reinstall;
marker absent (a prior --reinstall run failed after deleting it) ->
plain runrole, which re-enters install.yml and lets creates: re-fetch
only the missing file. The app's decision now always matches runrole's.

Unit test asserts both branches and the runtime gate.
… the intent

Maps is a proot install. An app death mid-install left the local_vars intent
(maps_install: True) claiming maps was installed, with no download done and no
recovery offered. Read maps "installed" from the iiab_state completion marker
(maps_installed: True, written only when the role finishes) instead.

- InstalledModules.isCompleted: pure rule for the completion marker.
- InstalledModulesReader: one readRootfsYaml behind both files (local_vars intent
  and iiab_state completion); installedKeys reads maps from the marker;
  isCompletionGated names the proot-module set (today maps), one source shared
  with the hub.
- ModuleHubFragment.confirmByProbe skips completion-gated keys, so a live probe
  cannot re-mask a half-done maps whose partial content still answers.
- LocalVarsYamlParser also keeps _installed keys (one parser, both files).

Verified on device (OnePlus): with the marker absent and the intent still true,
both the module detail and the hub show maps Not installed / Install now, so the
half-done build is recoverable; with the marker present, both show Installed.
… Retry (A4)

The module stall watch was surface-only: on an unstable network the in-proot
download can hang forever (the aria2c/meta4 fetch blocks with no timeout) and the
install spun with no way out. Add a longer hard threshold that kills the runrole.

The kill reuses the existing failure path: a non-zero exit makes installNextModule's
onProcessExit revert the module and offer Retry, exactly as a self-failed module --
no new recovery plumbing. A generation token drops a kill queued for a module that
has since ended. The soft "stalled" hint is unchanged.

Note: Ansible does not stream a shell task's stdout, so during a long download the
log goes quiet; the write-dir growth is the live movement signal. Safe for maps --
aria2c writes a 60s-summary log into library/downloads/maps.
…te the kill limit

Device test showed the kill drives recovery through onError (the kill closes the
runrole output stream mid-read), not onProcessExit as the comment claimed; both
reach the same revert + Retry path. Also note that killProcess orphans proot's
in-container child rather than reaping it (shared with doCancel) -- recovery still
works; reaping the subtree is a follow-up.
…flags

Deep recon of roles/maps/tasks/download_large_file.yml: the orphaned in-container
child is low impact -- aria2c self-exits (default max-tries=5, timeout=60) and the
meta4 requests.get just idles a socket; neither firehoses disk. So the orphan does
not warrant a bespoke reaper. Reap it (reusing EnvironmentProcess's /proc sweep,
the established idiom) only if a retry-conflict is ever observed in the field.
@luisguzman-adfa
luisguzman-adfa merged commit 4ee85a6 into main Sep 7, 2026
2 checks passed
@luisguzman-adfa
luisguzman-adfa deleted the fix/K2GO-393-maps-recover-half-done branch September 7, 2026 12:22
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