Detect `fork()` and fail loudly instead of deadlocking
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 80
- Forks
- 8
- Avg merge
- 21h 44m
- Merged PRs (30d)
- 5
Description
Today a ZarrsCodecPipeline used in a forked child hangs, with no error and no timeout (#171). Tokio doesn't support a runtime in child process in their docs and I found a rayon issue related to it.
I also found a way to make it work with rayon but it requires leaking memory. Because when a fork happens everything in memory is copied including the thread handle's and mutexes but not the threads themselves! only the fork-caller thread is copied. When we detect we are a child and rebuild the thread pool we need to drop the pool handler of the parent. But when we try to drop it, it will hang infinitely since it will join on those threads that don't exist. So the only way to do it is to use std::forget i.e. leak memory. Unless you are fine with it I can also open a PR on that. Rayon doesn't explicitly say anything about it in their docs, only a github issue.
rayon
rayon has refused fork support outright. rayon#708 is our exact case: a pool built in the parent, a fork, a child whose work never runs. cuviper closed it the next day:
I don't think we can support Rayon after
forkat all, especially after the
threadpool has started, because POSIX is quite limited in what you can do at
that point ... Very few pthread functions are on that safe list, so we have no
way to start a new pool.
joshtriplett agreed. This is rayon's only statement on fork anywhere; it is not in the source, the README or the FAQ.
tokio
tokio documents it, in the
runtime module docs:
User code that calls
fork(2)without immediately callingexecmust not
reuse Tokio in the child process. ... Creating or using a Tokio runtime in a
child process after the parent has used Tokio is not supported, even if the
runtime in the child is newly created.
Those docs exist because of
tokio#4301; the older
tokio#1541 has the same answer
from carllerche.
Related
Python is moving the same direction.
cpython#84559 changed the
POSIX multiprocessing default from fork to forkserver in 3.14, and
pytorch#169252 tracks the
DataLoader fallout. Users who hit this will increasingly be on a path upstream
already considers deprecated, so an error that names forkserver is an
actionable message rather than a dead end.
Proposal
Register a pthread_atfork child handler that bumps a generation counter, and
check it on entry to the pipeline. If the current generation differs from the
one the state was built under, raise, with a message naming the start method to
switch to.
Here are the Ai assisted written branches if you want to inspect how the rayon fix would be if you are ok with leaking memory:
- https://github.com/selmanozleyen/zarrs-python/compare/main...demo/fork-rebuild-rayon
- https://github.com/selmanozleyen/zarrs-python/compare/main...demo/fork-reject
cc: @flying-sheep
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at the ZarrsCodecPipeline entry point and trace how its state is created and used after a POSIX fork; read the linked Rayon and Tokio fork guidance first. The proposal describes a pthread_atfork generation check. Done means a forked child fails loudly instead of hanging, with an error that names the start method to use.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python, rust
- Domain
- operating-systems
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100