parity: a host's exclude list also holds on its own CI runner

playhorizon's runner fails remote_fireball and remote_fireworks even alone and
at bare main (3b19c386c: 2 of 2 runs and the retry), while the Mac mini passes
them; every fork PR whose parity lands there stopped on them. The host's
exclude list only kept harnesses off workers sent there, not off the runner's
own jobs: platform_skips now adds the local host's exclude, skipped with its
reason unless named. The two harnesses join playhorizon's list; the Mac
mini's runner and the nightly audit (macOS) keep running them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Ludovic Marie
2026-10-27 15:37:04 +01:00
parent 27af1879d7
commit 0879f3dbd2
4 changed files with 19 additions and 8 deletions
+1 -1
View File
@@ -42,7 +42,7 @@ The game logs into `HORIZON_HOME` and falls back to the player's own game folder
Harnesses can also run on the Mac mini, the desktop and playhorizon (`tools/parity_hosts.json` describes them; `desktop` and `playhorizon` use their CI runner's environment and vanilla data, and the desktop refuses work, or stops it, while someone uses it: docs/release-flow.md, "The desktop's runner"; its `default` list is empty, so pass `--hosts macmini,playhorizon` or set `PARITY_HOSTS`, after copying `shared-local/` to `~/code/horizon-parity/shared-local` on each host). The runner mirrors the worktree there with rsync (not `.git`, `.local`, `target` or ignored files) into `~/code/horizon-parity/remote/<host><path>`, whose `.local` links to the vanilla data in `~/code/horizon-parity/shared-local`, and starts `tools/parity.py --worker` over ssh. The worker builds the probes (about 80 s the first time) and runs what it is sent under the same tracer, admitting harnesses against its own free memory. One queue, longest first, feeds every machine, remote ones first: the Mac takes up to 14 harnesses, the desktop 8, playhorizon 3 (on CPUs 0-3: its CI runner has 4-7). Each pass comes back with the content hash of every file it depended on, so it is keyed on exactly what ran, and logs come back to `.local/parity-runner/logs/`; state, sharing and `gate.py` work as before. If a host can't be reached, or drops out, its harnesses run here.
Harnesses a host can't run are listed in the file: `local_only` (`save_roundtrip` runs the game on a virtual screen) and each host's `exclude`. The Mac excludes nothing: vanilla's `Math.sin`, `pow`, `log` and the like don't round the same on an Apple silicon Mac as on Linux (HotSpot uses fdlibm there, Intel's stubs on x86-64), and Rust's float functions round like neither (they call the platform libm), so code that mirrors a vanilla `Math` call uses `horizon_core::jmath`, which computes what HotSpot computes on the host architecture. The ThinkPad no longer runs harnesses (2026-10-26): playhorizon is the Linux host that is always on.
Harnesses a host can't run are listed in the file: `local_only` (`save_roundtrip` runs the game on a virtual screen) and each host's `exclude`. A host's `exclude` also holds when `parity.py` runs on that host itself, as its CI runner does: the harness is skipped there with its reason (playhorizon skips `remote_fireball` and `remote_fireworks`, which its CPU-starved CI slice fails even on bare main), unless named on the command line. The Mac mini's runner and the nightly audit still run them. The Mac excludes nothing: vanilla's `Math.sin`, `pow`, `log` and the like don't round the same on an Apple silicon Mac as on Linux (HotSpot uses fdlibm there, Intel's stubs on x86-64), and Rust's float functions round like neither (they call the platform libm), so code that mirrors a vanilla `Math` call uses `horizon_core::jmath`, which computes what HotSpot computes on the host architecture. The ThinkPad no longer runs harnesses (2026-10-26): playhorizon is the Linux host that is always on.
The file's `solo` list names harnesses that drive game windows (`remote_fireworks`, `server_player_sculk`, `world_reopen`, ...). On a machine where `parity.py` has 4 CPUs or fewer, such as playhorizon's CI runner, each of them runs alone, after the others: there frames are drawn on the CPU, and beside other harnesses a screenshot or a control request took more ticks than the check allows. `PARITY_SOLO=1` or `0` forces this on or off.
+7
View File
@@ -145,6 +145,13 @@ class ParityRunnerTests(unittest.TestCase):
with mock.patch.object(parity.sys, "platform", "darwin"):
self.assertEqual(parity.platform_skips(config), {"options": "libm"})
def test_a_hosts_excludes_are_skipped_when_it_runs_parity_itself(self):
config = {"hosts": {"playhorizon": {"exclude": {"remote_fireworks": "starved"}}, "macmini": {"exclude": {}}}}
with mock.patch.object(parity.sys, "platform", "linux"):
self.assertEqual(parity.platform_skips(config, "playhorizon"), {"remote_fireworks": "starved"})
self.assertEqual(parity.platform_skips(config, "macmini"), {})
self.assertEqual(parity.platform_skips(config, "elsewhere"), {})
def test_named_only_harnesses_are_listed_from_the_host_config(self):
self.assertEqual(parity.named_only({"named_only": {"lan_integration": "slow"}}), {"lan_integration": "slow"})
self.assertEqual(parity.named_only({}), {})
+8 -6
View File
@@ -1454,11 +1454,13 @@ def affected_harnesses(found, records, changed, reach=True, base=None):
return affected
def platform_skips(config):
"""Harnesses tools/parity_hosts.json marks as giving different results on this platform: {name: reason}."""
if sys.platform == "linux":
return {}
return dict(config.get("linux_only", {}))
def platform_skips(config, host=None):
"""Harnesses tools/parity_hosts.json marks as giving different results on this platform, and those this host's
entry excludes (playhorizon's CI runner runs its own pull request jobs here, not only workers sent to it):
{name: reason}."""
skips = {} if sys.platform == "linux" else dict(config.get("linux_only", {}))
skips.update(config.get("hosts", {}).get(HOST if host is None else host, {}).get("exclude", {}))
return skips
def known_flaky(config):
@@ -1593,7 +1595,7 @@ def main():
chosen = [name for name in found if stale(name, state, hasher, environment)]
skips = platform_skips(host_config())
for name in [name for name in chosen if base_name(name) in skips and not args.names]:
print(f"skipped on {sys.platform}: {name}: {skips[base_name(name)]}", flush=True)
print(f"skipped on {HOST} ({sys.platform}): {name}: {skips[base_name(name)]}", flush=True)
chosen.remove(name)
manual = {} if args.names or args.all else named_only(host_config())
for name in [name for name in chosen if base_name(name) in manual]:
+3 -1
View File
@@ -31,7 +31,9 @@
"exclude": {
"mob_ai": "the vanilla oracle runs out of its 3 GB heap here (OutOfMemoryError in IO workers; passes on the desktop and the Mac) - 2026-10-26 trial",
"lan_integration": "two clients rendered on the CPU (lavapipe): different timing subtests fail on each run (elytra, consume-release, barter, critical-hits) - 2026-10-26 trial",
"world_reopen": "failed twice here (no Save and Quit button after reopening; the minecart-count flake), passes on the desktop - 2026-10-26 trial"
"world_reopen": "failed twice here (no Save and Quit button after reopening; the minecart-count flake), passes on the desktop - 2026-10-26 trial",
"remote_fireball": "fails here even alone, at bare main 3b19c386c (2 of 2 runs and the retry), and passes on the Mac mini: the guest's egg is seen in 2 of 6 samples. CPU-starved: the whole horizon-ci slice (runner on 4-7, workers on 0-3) shares a 700% quota and was throttled in 27% of periods, so lavapipe frames and screenshots outlast the projectile's flight - 2026-10-27",
"remote_fireworks": "fails here even alone, at bare main 3b19c386c (2 of 2 runs and the retry), and passes on the Mac mini: the guest's sparks are gone by its trail screenshot (same CPU starvation as remote_fireball; a private Xvfb on CPUs 0-3 did not help) - 2026-10-27"
},
"note": "a partner's server (Ubuntu, 8 EPYC threads, 30 GB, Detroit; tailnet only), its CI runner has CPUs 4-7, so workers get 0-3 (as tools/remote.sh runs do) and 3 jobs, in the CI runner's environment, slice and Xvfb screen (tools/ci/linux-runner.sh); the always-on Linux host since the ThinkPad stopped running harnesses (2026-10-26)"
}