testsAndMisc/python_pkg/phone_lease/_cleanup.py
Krzysztof kuhy Rudnicki c0c51036fc phone_lease: per-app leases, a self-cleaning whole-phone lease, one serial
Several sessions can now share one phone: a lease covers one package (the
real screen stays the default scope, so existing callers keep their
meaning), and only phone-wide actions take `--whole-phone`, which is capped
at 300 s and runs its `--cleanup` commands exactly once on release or, via a
detached reaper, on expiry. The serial is always resolved, never the
"default" placeholder that let android_ui and phone_deploy lease different
keys for the same phone (2026-09-25). Adds status --json, holds and whoami
for phone_vd.sh and the raw-adb hook.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Ajzz98xksbfak8CaunZWC
2026-09-25 15:05:57 +02:00

70 lines
2.0 KiB
Python

"""Run a whole-phone lease's cleanup commands, even if nobody releases it.
A forced Doze left behind by a crashed session breaks every other session's
network and alarms until someone notices, so cleanup cannot depend on the
holder remembering: a detached reaper watches each lease that has cleanup
commands and runs them the moment it expires.
"""
from __future__ import annotations
import os
from pathlib import Path
import shlex
import subprocess
import sys
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from collections.abc import Iterable
# One cleanup command may not hang the reaper or a caller's release.
CLEANUP_TIMEOUT = 30.0
# The repo root, so the detached reaper can import python_pkg from anywhere.
_REPO_ROOT = Path(__file__).resolve().parents[2]
def run(commands: Iterable[str]) -> list[int]:
"""Run each command (no shell), returning exit codes; never raises.
A failing or missing command is reported as a non-zero code rather than
raised, because the remaining cleanup steps must still run.
"""
codes: list[int] = []
for command in commands:
try:
done = subprocess.run(
shlex.split(command),
check=False,
timeout=CLEANUP_TIMEOUT,
capture_output=True,
)
except OSError, ValueError, subprocess.TimeoutExpired:
codes.append(127)
continue
codes.append(done.returncode)
return codes
def spawn_reaper(serial: str, owner: str, scope: str) -> None:
"""Start a detached ``phone_lease reap`` for one freshly taken lease."""
env = {**os.environ, "PYTHONPATH": str(_REPO_ROOT)}
subprocess.Popen(
[
sys.executable,
"-m",
"python_pkg.phone_lease",
"reap",
serial,
"--owner",
owner,
"--scope",
scope,
],
env=env,
start_new_session=True,
stdin=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)