Re-derive the API 37 emulator failure, and harden the local sweep #47

Merged
JMR-dev merged 7 commits from tools/api-37-emulator into main 2026-08-23 05:21:26 +00:00
JMR-dev commented 2026-08-23 04:20:40 +00:00 (Migrated from github.com)

Five commits. The first two re-derive the API 37 failure; the last three close nine findings from the overnight review.

The correction

docs/api-37-emulator-crash.md claimed "Both swiftshader_indirect and host crash… the crash is in the gralloc mapper, below the renderer." The renderer decides whether API 37 boots, 7 runs for 7 — it boots iff the emulator log says something other than gles_mode_selected:host.

-gpu image boots?
host 37.0 rev6 and 37.1 rev8 no — 71 aborts, looping
swangle_indirect / angle_indirect 37.0 yes — 85 s, 1 abort

This is the exact inverse of API 33–36, where host works and SwiftShader segfaults the host. The harness now chooses per level (gpu_for_api).

Why the old claim looked right: the local swiftshader_indirect sample was void — every SwiftShader-GLES launch segfaults this host via the execheap bug, which was not understood when that file was written. So "both GPU modes" was one CI sample plus one local -gpu host sample. ANGLE had never been tried.

Mechanism: SystemUI nav-bar luma sampling → SF RegionSamplingThread locks a GraphicBuffer → Gralloc5 → GoldfishMapper::readFromHost asserts → init SIGKILLs zygote, restarting the framework mid-test.

CI should still stop at API 36 — CI runs swiftshader on a GPU-less runner, the working config needs SystemUI disabled, and 37 is permanently 47/49 (two CodecExceptions on c2.goldfish.h264.decoder; API 35 under the same renderer is green, so it is the image).

Review findings closed

R17 #26 cleanup now runs on EXIT/INT/TERM — an interrupted sweep no longer leaks the emulator, AVD and port
R31 #40 kill -9 "${EMU_PID:-0}" signalled the whole process group when empty. Narrowed.
R33 #42 avd_config_path searches every directory avdmanager honours; an unwritable config.ini fails the level instead of running at default RAM
R34 #43 Deleted a property loop that never waited; named the service check as the real wait
R4 #13 · R20 #29 Suite size is now derived, not written down — the total equals the checkout's @Test count, and a grep is given so a mismatch is the signal
R16 #25 The default sweep is red by design; now documented, with the note printed only when 37.x is the sole red level
R27 #36 "sole trigger" → "dominant trigger", residue named as undecided
R28 #37 The negotiation claim now rests on the aborts under ANGLE, not on an SDK string search

Two false claims fell out of R4 rather than being renumbered: "47 of 49" is not a ratio when 2 of the 49 are skips (45+2+2), and "matches the Pixel baseline of 49/0/0/2 exactly" describes a run that never happened — the Pixel measured 40/0/0/2 at edd6385.

Validation

bash -n clean throughout. shellcheck is not installed here, so two stub harnesses were built instead: a lifecycle test (13 checks) that demonstrates the empty-pid kill really does hit the sender's process group and that the fix signals nothing; and a smoke test driving the real script end to end on the boot-failure path (22 checks). All pass. No emulator was booted and no sweep was run — every finding here was fixable by reading.

Held, not done

  • R32 (#41) is not PLAUSIBLE — it was reproduced. yes | sdkmanager under pipefail returns 141 even on success, so a successful install reports "FAILED". It should bite nearly every install, and this branch makes the install path the normal path. Untouched: fixing it means handling sdkmanager's licence prompt, which needs a machine with the image uninstalled to validate. Ticket relabelled CONFIRMED.
  • R19 (#28) — status_check.yml's API-37 comment is falsified by this branch. .github/ deliberately not opened.
  • The exit-code semantics were documented, not changed. Subtracting 37's contribution reverses a recorded decision — the repo owner's call.

🤖 Generated with Claude Code

Five commits. The first two re-derive the API 37 failure; the last three close nine findings from the overnight review. ## The correction `docs/api-37-emulator-crash.md` claimed *"Both `swiftshader_indirect` and `host` crash… the crash is in the gralloc mapper, below the renderer."* **The renderer decides whether API 37 boots**, 7 runs for 7 — it boots iff the emulator log says something other than `gles_mode_selected:host`. | `-gpu` | image | boots? | |---|---|---| | `host` | 37.0 rev6 **and** 37.1 rev8 | **no** — 71 aborts, looping | | `swangle_indirect` / `angle_indirect` | 37.0 | **yes** — 85 s, 1 abort | **This is the exact inverse of API 33–36**, where `host` works and SwiftShader segfaults the host. The harness now chooses per level (`gpu_for_api`). **Why the old claim looked right:** the local `swiftshader_indirect` sample was *void* — every SwiftShader-GLES launch segfaults this host via the `execheap` bug, which was not understood when that file was written. So "both GPU modes" was one CI sample plus one local `-gpu host` sample. **ANGLE had never been tried.** Mechanism: SystemUI nav-bar luma sampling → SF `RegionSamplingThread` locks a GraphicBuffer → Gralloc5 → `GoldfishMapper::readFromHost` asserts → **init SIGKILLs zygote**, restarting the framework mid-test. **CI should still stop at API 36** — CI runs swiftshader on a GPU-less runner, the working config needs SystemUI disabled, and 37 is permanently 47/49 (two `CodecException`s on `c2.goldfish.h264.decoder`; API 35 under the same renderer is green, so it is the image). ## Review findings closed | | | |---|---| | R17 #26 | `cleanup` now runs on EXIT/INT/TERM — an interrupted sweep no longer leaks the emulator, AVD and port | | R31 #40 | `kill -9 "${EMU_PID:-0}"` **signalled the whole process group** when empty. Narrowed. | | R33 #42 | `avd_config_path` searches every directory avdmanager honours; an unwritable `config.ini` fails the level instead of running at default RAM | | R34 #43 | Deleted a property loop that never waited; named the service check as the real wait | | R4 #13 · R20 #29 | Suite size is now **derived, not written down** — the total equals the checkout's `@Test` count, and a grep is given so a *mismatch* is the signal | | R16 #25 | The default sweep is red **by design**; now documented, with the note printed only when 37.x is the sole red level | | R27 #36 | "sole trigger" → "dominant trigger", residue named as undecided | | R28 #37 | The negotiation claim now rests on the aborts under ANGLE, not on an SDK string search | **Two false claims fell out of R4** rather than being renumbered: "47 of 49" is not a ratio when 2 of the 49 are skips (45+2+2), and *"matches the Pixel baseline of 49/0/0/2 exactly"* **describes a run that never happened** — the Pixel measured 40/0/0/2 at `edd6385`. ## Validation `bash -n` clean throughout. **`shellcheck` is not installed here**, so two stub harnesses were built instead: a lifecycle test (13 checks) that *demonstrates* the empty-pid kill really does hit the sender's process group and that the fix signals nothing; and a smoke test driving the **real script** end to end on the boot-failure path (22 checks). All pass. **No emulator was booted and no sweep was run** — every finding here was fixable by reading. ## Held, not done - **R32 (#41) is not PLAUSIBLE — it was reproduced.** `yes | sdkmanager` under `pipefail` returns 141 even on success, so a successful install reports "FAILED". It should bite nearly every install, and this branch makes the install path the normal path. Untouched: fixing it means handling sdkmanager's licence prompt, which needs a machine with the image uninstalled to validate. Ticket relabelled CONFIRMED. - **R19 (#28)** — `status_check.yml`'s API-37 comment is falsified by this branch. `.github/` deliberately not opened. - **The exit-code semantics** were documented, not changed. Subtracting 37's contribution reverses a recorded decision — the repo owner's call. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.