Skip to content

fix(py): kill a self-stopped worker on macOS with Python 3.14.10 - #460

Open
jat255 wants to merge 3 commits into
mainfrom
jat255/macos-314-stopped-worker
Open

jat255 wants to merge 3 commits into
mainfrom
jat255/macos-314-stopped-worker

Conversation

@jat255

@jat255 jat255 commented Oct 30, 2026

Copy link
Copy Markdown
Collaborator

This PR makes the stop watcher read a macOS worker's state from the process table, so a worker that stops itself is still killed at once on Python 3.14.10. In that release, asyncio takes the stopped child's wait report itself (python/cpython#158287), so the watcher never woke and the call waited out its timeout. This is the check (macos-latest, 3.14) failure on posit-dev/commons#443.

Agent-written detail
  • The new runner image (20261026) has CPython 3.14.10. I reproduced the failure on 3.14.8 by patching in 3.14.10's child-watcher change. Before this fix the test failed 1 run in 3. After it, 20 of 20 runs passed.
  • macOS before Python 3.13 has no os.waitid. There, a stopped worker used to fail at the call timeout. With this fix it is killed at once, so the driver test now runs on those versions too. Linux keeps the waitid wait.
  • The worker's start time is read at spawn, before asyncio can reap it. This stops the watcher from killing a later process that reuses the PID.
  • Full suite passes locally on 3.11 and 3.14.
  • kata: jtjx

CPython 3.14.10 (gh-158287) makes asyncio's child watcher on macOS take a stopped child's wait report with waitid(WSTOPPED | WNOHANG). The stop watcher waited for that same report with WNOWAIT, so when asyncio's thread got there first the watcher never woke, and the call ran out its timeout instead of reporting a crash.

On macOS the watcher now polls proc_pidinfo() for the worker's state, which shows SSTOP regardless of who took the report. This also covers macOS before Python 3.13, which has no os.waitid, so the driver test now runs there too. Other platforms keep the waitid wait.
The stop watcher took the worker's start time on its first poll, so a worker reaped before the thread first ran could be confused with a later process that reused its PID. LocalSession now reads it on the event loop's thread right after the spawn, before asyncio can reap the worker.
@github-actions

Copy link
Copy Markdown
Contributor

Preview root: https://posit-dev.github.io/commons/pr-460/

Python site preview: https://posit-dev.github.io/commons/pr-460/py/

Built from the latest commit on this branch. The R links in it point at the published R site, which no pull request rebuilds.

pyrefly evaluates sys.platform checks for the platform it runs on, so on Linux the early return left the rest of _process_start typed as unreachable. The caller now decides whether to read it.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant