Skip to content

Commit a64580b

Browse files
iOS inner loop: use per-call unique logarchive path to eliminate sudo rm hang
The previous code shared a single `ioshelper_startup.logarchive` path across all iterations and bracketed the `sudo log collect` call with `sudo rm -rf` calls to purge any stale archive (since `log collect` refuses to overwrite an existing --output path with exit 74). That works when sudo credentials are cached, but hangs indefinitely in non-TTY background contexts (e.g., campaigns started via detached caffeinate, nohup, or any agent runner) because the sudo password prompt has no terminal to read from. NOPASSWD is only practical to scope to `/usr/bin/log collect` (per the existing /etc/sudoers.d/log-collect convention); scoping it to /bin/rm is broader than most users want. Switch to a per-call unique logarchive path `ioshelper_startup_{pid}_{ms}.logarchive`. macOS's tmp reaper cleans /tmp on its own schedule, so per-iteration archives don't accumulate harmfully. `log collect` succeeds on the first try every time since the path never pre-exists. No sudo rm needed. Validated by a 4-side M365 campaign overnight: 11 cold-startup events per side across all 4 configs, zero hangs, with the orchestrator running fully detached from any user TTY.
1 parent 8a3b644 commit a64580b

1 file changed

Lines changed: 15 additions & 10 deletions

File tree

src/scenarios/shared/ioshelper.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -611,19 +611,22 @@ def _measure_device_startup_via_watchdog(self, bundle_id):
611611
launch_proc.wait()
612612

613613
# Collect device logs covering the launch window.
614-
# Both rm -rf calls use sudo because `sudo log collect` writes the
615-
# logarchive owned by root; an unprivileged rm cannot recurse into it
616-
# and `log collect` refuses to overwrite an existing --output path
617-
# (exit 74), so a stale archive from iteration N would block iteration
618-
# N+1 from collecting any logs.
619-
logarchive = os.path.join(tempfile.gettempdir(), 'ioshelper_startup.logarchive')
620-
self._run_quiet(['sudo', 'rm', '-rf', logarchive])
614+
# Use a unique per-call logarchive path so we don't need to delete a
615+
# prior root-owned archive (the upstream code uses `sudo rm -rf` to
616+
# purge a stale shared path; that requires interactive sudo when
617+
# NOPASSWD is scoped only to `log collect`, which hangs background
618+
# campaigns). Per-call uniqueness eliminates the need for cleanup
619+
# entirely; macOS's tmp cleaner reaps leaked archives.
620+
import time as _time
621+
logarchive = os.path.join(
622+
tempfile.gettempdir(),
623+
f'ioshelper_startup_{os.getpid()}_{int(_time.time() * 1000)}.logarchive')
621624
collect_cmd = ['sudo', 'log', 'collect', '--device',
622625
'--start', start_ts, '--output', logarchive]
623626
RunCommand(collect_cmd, verbose=True).run()
624627

625628
# Parse SpringBoard watchdog events for this bundle ID.
626-
# sudo because the logarchive is root-owned (see comment above).
629+
# sudo because the logarchive is root-owned (created by sudo log collect).
627630
show_cmd = ['sudo', 'log', 'show',
628631
'--predicate', '(process == "SpringBoard") && (category == "Watchdog")',
629632
'--info', '--style', 'ndjson', logarchive]
@@ -673,8 +676,10 @@ def parse_ts(evt):
673676
getLogger().info("Cold startup: %d ms (Time to Main: %d ms, Time to First Draw: %d ms)",
674677
total_ms, time_to_main_ms, time_to_draw_ms)
675678

676-
# Clean up logarchive (sudo: log collect ran as root, so the tree is root-owned)
677-
self._run_quiet(['sudo', 'rm', '-rf', logarchive])
679+
# Skip post-iter cleanup: the logarchive uses a unique per-call name so
680+
# accumulation isn't a correctness problem. macOS reaps /tmp on a
681+
# schedule. Leaving it avoids needing sudo (which hangs background
682+
# campaigns when NOPASSWD is scoped only to `log collect`).
678683

679684
return total_ms
680685

0 commit comments

Comments
 (0)