mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[CI, android-plugin] (P6): CI starts a second MobileGL process on a device
The Android retrace matrix gains a third lane, spawn-acceptance, beside
monolith-control and inproc-acceptance. Until now nothing in CI had ever
started a second MobileGL process on a device at all.
The apk.yml retrace job already carried a `transport` matrix dimension and
already branched on it, so the lane itself is three edits: a row in
github_transport_matrix, an elif in the retrace step, and a summary to render.
What needed thought was the gate.
THE SPAWN LANE IS APK-ONLY, and that is a statement about where evidence is
missing rather than about what spawn can do. Linux already has
test.yml's `retrace-split`, which is {backend, case, transport} over inproc AND
spawn with the two negative controls that make it mean anything. A spawn row in
the Linux transport matrix would pay for a second full sweep proving nothing -
and test.yml's `retrace` job stages no server image, so the lane would not
merely be redundant, it would be red. self_test_transport_matrices now asserts
the absence in both directions, because an accidental removal of the `if apk`
has to be caught in the self-test rather than in CI.
--require-spawn REQUIRES TWO SENTENCES, and the second is the one that matters.
ConfigLoader's marker proves the VALUE RESOLVED - it exists only in the Spawn
arm, which exists only under MOBILEGL_BUILD_DISAGGREGATED - but that is a
statement about a parser. `spawn ARMED - the server role runs in pid N` is
written only after the launch, the connect and the handshake have all
succeeded, and the pid in it is the SERVER's. It is the only line in the log a
same-process run cannot produce.
Both halves red once, measured:
* a monolith run demanded to prove spawn: REFUSED, transport_resolution_marker
false, server_pid null - while the retrace itself scored ssim=1.000000,
which is exactly the trap. OpenRA matches its golden under monolith too, so
the picture has never been the evidence.
* the same run's real spawn log with the one ARMED line deleted: REFUSED,
transport_resolution_marker TRUE and server_pid null. That is the
"resolved the transport and then never left the process" case, isolated.
THE PROOF USES `any` WHERE THE inproc GATE USES `all`, and the difference is
load-bearing rather than a relaxation. Under spawn two processes append to one
MOBILEGL_LOG_FILE_PATH, and the launcher deliberately scrubs every
MOBILEGL_IPC_* from the child's environment (anti-recursion catch (b)) - so the
server's own `Config: IPC` line reads strict=0 role-split-state=0 BY
CONSTRUCTION. Demanding every line match would red every spawn run for the one
thing the design requires.
--use-pbuffer is not optional on this lane: until P12 a spawned server cannot be
handed a window, because an ANativeWindow* is a pointer into the CLIENT's
process (Rule H). MOBILEGL_IPC_SERVER_PATH is deliberately NOT passed -
nativeLibraryDir carries an install-time hash that nothing off the device can
spell, and the Activity resolves it.
The local runner now passes the same knobs and the same --require-* flag CI
does, so "it works locally" can no longer mean "it ran monolith locally". That
change immediately caught its own omission: the first local run with the gate
armed failed on client_ipc_line_present=false because the runner had been
sending MOBILEGL_TRANSPORT alone.
Verified on device 2f7cbe2e: OpenRA.DirectGLES spawn, ssim=1.000000,
mismatchPixels=0, "real spawn transport, server role in pid 9885, and zero
Fatal records confirmed".
This commit is contained in:
@@ -34,6 +34,7 @@ Usage:
|
||||
[--dump-texture-2d CALL,TEXTURE,LEVEL,DIR] \
|
||||
[--env "K=V;K=V"] \
|
||||
[--require-inproc] \
|
||||
[--require-spawn] \
|
||||
[--benchmark] \
|
||||
[--benchmark-tail-frames N] \
|
||||
[--benchmark-finish 0|1] \
|
||||
@@ -67,6 +68,8 @@ variables to the replay process. They are applied last, immediately before
|
||||
libMobileGL.so is loaded, so they override every flag above; an entry with no "="
|
||||
unsets the variable instead. This is the generic passthrough: a MOBILEGL_* knob
|
||||
that has no flag of its own needs no plumbing to be forwarded.
|
||||
Pass --require-spawn to require the same proof for transport=spawn, plus the one
|
||||
sentence a monolith fallback can never write: the pid of the server process.
|
||||
Pass --require-inproc to require the library's real transport-resolution log,
|
||||
strict errors and separate role state, plus zero Fatal records. This checks the
|
||||
APK process after replay; a host environment echo or an SSIM-only pass is not proof.
|
||||
@@ -131,6 +134,7 @@ coherent_as_flush=0
|
||||
texture_2d_dumps=""
|
||||
env_overrides="${MOBILEGL_TRACE_ENV:-}"
|
||||
require_inproc=0
|
||||
require_spawn=0
|
||||
benchmark=0
|
||||
benchmark_tail_frames=200
|
||||
benchmark_finish=1
|
||||
@@ -178,6 +182,7 @@ while [ "$#" -gt 0 ]; do
|
||||
--dump-texture-2d) texture_2d_dumps="$(next_arg "$@")"; shift 2 ;;
|
||||
--env) env_overrides="$(next_arg "$@")"; shift 2 ;;
|
||||
--require-inproc) require_inproc=1; shift 1 ;;
|
||||
--require-spawn) require_spawn=1; shift 1 ;;
|
||||
--benchmark) benchmark=1; shift 1 ;;
|
||||
--benchmark-tail-frames) benchmark_tail_frames="$(next_arg "$@")"; shift 2 ;;
|
||||
--benchmark-finish) benchmark_finish="$(next_arg "$@")"; shift 2 ;;
|
||||
@@ -559,6 +564,59 @@ proof_path.write_text(json.dumps(proof, indent=2), encoding="utf-8")
|
||||
if not passed:
|
||||
sys.exit("trace replay failed actual inproc/strict/role/zero-Fatal proof: " + json.dumps(proof))
|
||||
print("Android retrace: real inproc transport, strict role state and zero Fatal records confirmed")
|
||||
PY
|
||||
fi
|
||||
if [ "${require_spawn}" -eq 1 ]; then
|
||||
"${PYTHON}" - "${result_dir}/mobilegl.log" "${result_dir}/transport-proof.json" <<'PY'
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
import sys
|
||||
|
||||
log_path, proof_path = map(Path, sys.argv[1:])
|
||||
text = log_path.read_text(encoding="utf-8", errors="replace") if log_path.is_file() else ""
|
||||
|
||||
# TWO SENTENCES, AND THE SECOND IS THE ONE THAT MATTERS.
|
||||
#
|
||||
# ConfigLoader's marker proves the VALUE RESOLVED - it exists only in ConfigLoader's Spawn arm,
|
||||
# which exists only under MOBILEGL_BUILD_DISAGGREGATED, so a pull library cannot write it. That
|
||||
# is a statement about a parser.
|
||||
#
|
||||
# `spawn ARMED - the server role runs in pid N` is written by ClientSession::StartSpawned only
|
||||
# after the launch, the connect and the handshake have ALL succeeded, and the pid in it is the
|
||||
# SERVER's. It is the only line in this log a same-process run cannot produce: inproc resolves
|
||||
# its own marker while running the server role on a thread HERE. Requiring only the first would
|
||||
# accept a session that resolved spawn and then never reached another process.
|
||||
marker = "Config: MOBILEGL_TRANSPORT=spawn - the MGPipe record stream"
|
||||
armed = re.search(r"spawn ARMED - the server role runs in pid (\d+)", text)
|
||||
|
||||
# `any`, NOT the `all` the inproc gate uses, and the difference is load-bearing.
|
||||
#
|
||||
# Under spawn TWO PROCESSES append to one MOBILEGL_LOG_FILE_PATH, and the launcher deliberately
|
||||
# scrubs every MOBILEGL_IPC_* out of the child's environment (anti-recursion catch (b)) - so the
|
||||
# server's own `Config: IPC` line reads strict=0 role-split-state=0 BY CONSTRUCTION. Demanding
|
||||
# that every line match would red every spawn run for the one thing the design requires.
|
||||
configs = re.findall(r"Config: IPC[^\r\n]*", text)
|
||||
fatals = re.findall(r"^.*Fatal\{.*$", text, re.MULTILINE)
|
||||
required = {"strict": "1", "role-split-state": "1", "run-ahead": "1"}
|
||||
def matches(line):
|
||||
return all(re.search(r"\b" + re.escape(key) + "=" + value + r"\b", line)
|
||||
for key, value in required.items())
|
||||
client_configs = [line for line in configs if matches(line)]
|
||||
config_ok = bool(client_configs)
|
||||
|
||||
passed = marker in text and armed is not None and config_ok and not fatals
|
||||
proof = {"required_transport": "spawn", "library_log": str(log_path),
|
||||
"transport_resolution_marker": marker in text,
|
||||
"server_pid": int(armed.group(1)) if armed else None,
|
||||
"ipc_config_lines": configs, "required_ipc_settings": required,
|
||||
"client_ipc_line_present": config_ok,
|
||||
"fatal_count": len(fatals), "fatal_lines": fatals, "passed": passed}
|
||||
proof_path.write_text(json.dumps(proof, indent=2), encoding="utf-8")
|
||||
if not passed:
|
||||
sys.exit("trace replay failed actual spawn/second-process/zero-Fatal proof: " + json.dumps(proof))
|
||||
print("Android retrace: real spawn transport, server role in pid "
|
||||
f"{proof['server_pid']}, and zero Fatal records confirmed")
|
||||
PY
|
||||
fi
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user