WIP: fix(control): keep command-line sessions alive on macOS #516
No reviewers
Labels
No labels
area/accessibility
area/audio
area/input
area/install
area/menu
area/renderer
area/saves
area/upscaling
good first issue
gpu/amd
gpu/igpu
gpu/intel
gpu/nvidia
help wanted
kind
bug
kind
chore
kind
docs
kind
feature
kind
performance
kind
question
needs measurements
platform/linux
platform/macos
platform/steam-deck
platform/windows
preview
priority
critical
priority
high
priority
low
status
blocked
status
confirmed
status
duplicate
status
invalid
status
needs-info
status
wontfix
testing wanted
ci: approved
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!516
Loading…
Reference in a new issue
No description provided.
Delete branch "mooki/Longshot:mac/longshot-ctl-darwin"
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?
Why
On macOS,
scripts/longshot_ctl.pythinks every durable session (#47) is dead.process_start()reads/proc/<pid>/stat, which macOS does not have. The game, its owner and its launcher all look gone, so sessions cannot beattached, stopped or finished.
tests/test_longshot_ctl.pyfails on a Mac with 2 failures and 26 errors.What changed
process_start()inscripts/longshot_ctl.pygets a Darwin branch that readssysctlkern.proc.pidthrough
ctypes, standard library only.kp_proc.p_starttime, astruct timevalthat the kernel sets at fork andnever changes. It has microsecond resolution and is kept as
{"clock": "darwin-start-us", "ticks": …}.kern.bootsessionuuid, as Linux addsboot_id.p_stat == SZOMB) or a pid that is gone (the call answers with no bytes) counts as dead, as Linux'sZ/Xdoes. The answer's own
p_pidmust match the pid asked for.struct kinfo_proclayout was checked with the SDK (offsetof). On arm64 and x86_64 alike it is 648 bytes, withthe start time at 0,
p_statat 36 andp_pidat 40.test_darwin_process_identity(Darwin only) checks four things:sysctl -n kern.bootsessionuuid.offsets.
os.kill(pid, 0)) reads as dead.test_copy_savecompares launch's data folder withos.path.realpath(run).launchresolves--run(
Path.resolve()), and on macOS/tmpis a link to/private/tmp. There is no platform case: on Linux theresolved path is the same.
Why sysctl and not
ps -o lstart=:lstarthas one-second resolution, so a pid reused within the same second wouldpass as the same process, which is weaker than Linux's start ticks. It is also locale-formatted text from a subprocess,
about 7 ms per call on this Mac, and the session code polls
same_process()every 20 ms. sysctl is one system call,about 14 µs, with microsecond resolution and the zombie state in the same answer. libproc's
PROC_PIDTBSDINFOwould givethe same start time. sysctl needs no second library, and
psreads the samekinfo_proc[inference: from howpsworks on BSD; its source was not read here].
Pid reuse: a reused pid belongs to a process forked later, so its
p_starttimediffers. The comparison is the wholeidentity, as on Linux and Windows. The existing
pid was reusedcheck inSessionsstill passes. It is strictly finerthan Linux's 10 ms ticks, and the boot UUID covers a reboot, as
boot_iddoes.Flags and settings
None.
Measurements
main:
9c9d69ed09(2026-11-14)this PR: mac/longshot-ctl-darwin @
526953848fHardware: Apple M4 Max (16 CPUs), 64 GB, macOS 26.7.1
Scene: none (game-free checks; no GPU, no game)
Method:
tests/run_all.py test_longshot_ctl, run main, this PR, main, this PR. The results are pass or fail and the samein both rounds, so there is no timing median. Also the full game-free suite, compared by test name with main's run on
this Mac.
How to reproduce:
python -I -B tests/test_longshot_ctl.pyin each checkout. Add--source-root <checkout> Sessions.test_darwin_process_identityto point one test at another checkout'slongshot_ctl.py.9c9d69ed52695384tests/test_longshot_ctl.py, rounds 1 and 2test_darwin_process_identitypointed at main'slongshot_ctl.py'NoneType' object is not subscriptable(no identity on Darwin)tests/run_all.py --jobs 6)The new check catches real faults. In a scratch copy of this PR's
longshot_ctl.pywith the zombie check(
p_stat == 5) removed, it fails at the zombie ({'clock': 'darwin-start-us', …} is not None). This PR's copy passes.Testing
Tested:
test_longshot_ctlfails on main and passes here, in two interleaved rounds.offsetof.(
reports/MacMainFixes-files/probes/ps_probe.py).tests/test_longshot_ctl.py, FAIL on main,passes here. The other 19 failures are main's on this Mac, by name. Trees were clean before and after, and the run
took 179 s.
Not tested:
process_start()are unchanged.test_copy_save's expectation resolves to the samepath where
/tmpis not a link [inference: not run there].Needs other testers: None to merge. Linux and Windows CI would confirm that nothing changed there.
Screenshots
Nothing on screen.
Risk and rollback
macOS only. If sysctl fails or answers with the wrong size, the process reads as dead. That is the same outcome as
before this PR, so it fails safe. Reverting the two commits restores main.
Reviewer notes
Base: this PR is on main
9c9d69ed. Main has since moved to543846eb(#385, #465), and neither commit touchesthese files.
git merge-tree --write-tree 543846eb mac/longshot-ctl-darwinmerges cleanly.Where to look: the Darwin block in
process_start(). Sessions store the identity as JSON, so it is plain ints and astring.
ctypes.CDLL(None)findssysctlandsysctlbynamein libSystem through the interpreter's own image.Issues
See #47 (and #420, macOS tracking).
Checklist
For everyone
type(scope): description).main. Branched from9c9d69ed; main has moved to543846eb, which merges cleanly (neither new commit touches these files). It is rebased in the sweep before ready.python -I -B tests/run_all.pyrun and passing (any failure that also fails onmainis named above).third_party/untouched (a new folder with aPROVENANCE.mdinstead).If applicable
Assisted-by:/Co-Authored-By:trailers.mainand passing here.View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.