* fix(hopper): shut down the Linux demo helper orderly so its X lock is removed Teardown SIGKILLed the helper's process group, so run_launch's finally never ran stop_process on Xvfb and each session orphaned its /tmp/.X<n>-lock. Later startups count orphans as address_collision. Ask the helper to stop first: closing its stdin trips OwnerLifetime, and its finally stops Hopper and Xvfb with SIGTERM, which removes the lock. Anything still alive after the grace period is reaped by the token-verified group cleanup. Fixes #4921 Co-authored-by: Saim Shafique <128062316+sx4im@users.noreply.github.com> * fix(hopper): notice signal-exited demo helpers during orderly shutdown A helper that already died from a signal keeps exitCode null, so the orderly-shutdown path treated it as still running and sat out the full grace period before the group cleanup. Check signalCode alongside exitCode in both the cleanup guard and the exit wait, and add a regression test for an already signal-exited helper. Also hardens the ignored-shutdown test: it now proves the fixture started (waits for its lock), asserts the helper was reaped by the group SIGKILL, and bounds the wait by the configured 500 ms grace instead of a vacuous 30 s ceiling.
47 lines
1.6 KiB
Python
47 lines
1.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Demo-helper stand-in: shuts down orderly when the owner pipe closes.
|
|
|
|
Mirrors the production helper contract: the launcher owns this process group
|
|
and asks for an orderly shutdown by closing stdin (OwnerLifetime). The
|
|
production run_launch finally block then stops Hopper and Xvfb with SIGTERM;
|
|
here the fixture removes its fake X lock file instead, which is what Xvfb
|
|
does for /tmp/.X<n>-lock on a clean exit.
|
|
"""
|
|
|
|
import importlib.util
|
|
import os
|
|
import signal
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
|
|
spec = importlib.util.spec_from_file_location(
|
|
"hopper_demo_x11", Path(__file__).resolve().parents[2] / "scripts/hopper-demo-x11.py"
|
|
)
|
|
assert spec is not None and spec.loader is not None
|
|
helper = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(helper)
|
|
assert "--owner-stdin" in sys.argv
|
|
|
|
lock_dir = Path(os.environ["REA_FIXTURE_LOCK_DIR"])
|
|
lock = lock_dir / ".X199-lock"
|
|
# Xvfb writes its PID right-justified in a 10-character field.
|
|
lock.write_text(f"{os.getpid():10d}\n")
|
|
|
|
if os.environ.get("REA_FIXTURE_IGNORE_SHUTDOWN") == "1":
|
|
while True:
|
|
time.sleep(60)
|
|
|
|
if os.environ.get("REA_FIXTURE_SIGNAL_DEATH") == "1":
|
|
# Die from a signal before the launcher asks for cleanup: teardown must
|
|
# notice the signal exit instead of waiting out the orderly grace period.
|
|
os.kill(os.getpid(), signal.SIGKILL)
|
|
|
|
owner = helper.OwnerLifetime(True)
|
|
while owner.alive():
|
|
time.sleep(0.05)
|
|
# Orderly shutdown path: remove our own lock, like Xvfb does on SIGTERM.
|
|
lock.unlink(missing_ok=True)
|
|
(lock_dir / "orderly-shutdown").write_text("ok")
|
|
sys.exit(0)
|