A launchd slot runs sak-runner as an Interactive job, and the runner keeps its host awake while it holds a run or a job #187
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "task/3744-on-a-macos-runner-at-night-timers-stretc"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Tracker: #3744
What it delivers
ProcessTypeInteractive(runner/slot.sx). launchd throttles an agent with noProcessType. While no one uses the Mac, that agent's timers run late, and so do the timers of every process it starts: harnesses, gates and their tests. AnInteractivejob runs with an app's resource limits, which is none. An update's new version writes its own slot's agent (--start-slot), so the first update to this release already runs as an Interactive job./usr/bin/caffeinate -i -w <runner pid>running while a run or a job is active (runner/supervisor.sx). This stops an unattended host from idle-sleeping under the work. The main thread starts it when the first run or job starts and ends it when the last one ends. caffeinate also ends if the runner exits. A sign-in does not hold it.config.macosis the platform rule; agy'sunrunnableuses it as well.The task also names a clock-timed tail heartbeat. That is already on master (
fa0995c7).Why the job type, and not the assertion, fixes the timers
Measured on s32 at 02:10–02:40 local on 2026-10-30, with no user input for 3.3–3.8 h (
HIDIdleTime), using the task's repro (20 ×time.sleep(0.1), mean seconds per sleep):ps -o priProcessTypeProcessTypeStandardProcessTypeBackgroundProcessTypeInteractivecaffeinate -itaskpolicy -l 0 -t 0, or-as32 also holds a system-wide
PreventUserIdleSystemSleepassertion (Amphetamine), and its timers still stretch. So a power assertion does not give timer accuracy. The job type does. The assertion here does a different job: it keeps a host whose energy settings allow idle sleep awake under a run.Through the runner itself, at 02:37 local:
tests/runner_launchd.sxarms whose stub harness ran the repro in itsgatedstep, under the scratch slot's real launchd agent:Regressions, failing before and passing after
Each was run from a
git archivearm of master with this branch's tests:tests/runner_launchd.sx: "slot a's step's harness runs at an app's priority, 31, which launchd gives an Interactive job, not the 20 of an agent it throttles". Master:RUNNER LAUNCHD FAIL (1 checks). Head:RUNNER LAUNCHD OK. The stream-json harness stub now records its pid to./pid.log, and the test reads that process's priority withps. Unlike the timers, the priority does not depend on the time of day.tests/runner_install.sx: "macOS: the agent is an Interactive job, which launchd does not throttle". Master:RUNNER INSTALL FAIL (1 checks). Head: OK.tests/runner_core.sxcheck_awake: a supervisor of capacity 2 runs two chattering runs. While both go, the supervisor's process runs exactly one/usr/bin/caffeinate -i -w <its pid>, andpmset -g assertionslists that process withPreventUserIdleSystemSleep. Once the supervisor is done with the first run, that caffeinate still runs. Once both runs end, no caffeinate child is left. Master fails 3 checks (RUNNER CORE FAIL (3 checks)). A mutation arm that dropsif busy { return; }fromrest, so the caffeinate ends with every run, fails 1: "once the first of the two runs ends, that caffeinate still runs". Head:RUNNER CORE OK.tests/sign_in.sxcheck_pasted: while a sign-in's login is up under the supervisor, the runner's process runs no caffeinate, as a sign-in keeps no host awake. A mutation arm that starts the caffeinate for every ticket, sign-ins included, and keeps it while any slot is active fails exactly that check (SIGN IN FAIL (1 checks)). Head:SIGN IN OK. Master passes it too, since master starts no caffeinate at all. The check pins the sign-in rule, not the bug.Golden set
None: this changes nothing under
web/.Gates at the head
c3e89d7efull: gate run 875, clean, passed. That covers all 69tests/*.sxfiles, release.sh with step9, the deploy contract, the main Playwright config (777 passed, 1 skipped) and the runner config (10 passed).