"Fix" fork() so it "works" on Python 3.13, and "works" better on older Python versions#1047
"Fix" fork() so it "works" on Python 3.13, and "works" better on older Python versions#1047
Conversation
| for t in threads: | ||
| t.start() | ||
|
|
||
| check(2, threading, 'pre-fork patched') |
There was a problem hiding this comment.
What's going on here is that previously threads created by threading and threads created by eventlet.green.threading were tracked separately, and now they're just tracked in both.
Specifically:
threadingtracked the main thread and the thread it started (see earlier in the test), for a total of 2.eventlet.green.threadingtracked the main thread the 3 threads it started, for a total of 4.
So in total there are 5 threads; main thread is counted by both, so it's 1 + (2 - 1) + (4 - 1) = 5.
There was a problem hiding this comment.
To put another way: this is a semantic change, but seems fine to me.
There was a problem hiding this comment.
I think we have to strongly highlight this behavioural change in documentation and also somewhere in the release note, do you mind adding some words about that to explain to users what they can expect or not.
I suppose this patch will potentially break lot of unit tests outside of Eventlet where people are checking for similar counting for some reasons.
There was a problem hiding this comment.
Apparently, according to comments, Swift and Neutron, at least, seems to rely in some ways on pre-fork and both are Eventlet compliant https://codesearch.opendev.org/?q=pre-fork&i=nope&literal=nope&files=&excludeFiles=&repos=
There was a problem hiding this comment.
Is this based on just the phrasing, or do you know which libraries they're using to start the subprocesses? Since for a long time fork() was the only way to create a subprocess, and so "forking" was synonymous with starting a subprocess.
(After the fork(), you would then execve() or friends to effectively start a new process. So that is fine. The real issue is fork() without execve(), where the memory copied from the parent process is persisted in the child process.)
There was a problem hiding this comment.
I suppose they use oslo.service to achieve that.
| check(4, eventlet.green.threading, 'pre-fork green') | ||
| check(5, eventlet.green.threading, 'pre-fork green') | ||
|
|
||
| if os.fork() == 0: |
There was a problem hiding this comment.
This was asserting that the child lost all its threads. Which would be the case for real threads, but green threads do survive fork() so these assertions were wrong.
There was a problem hiding this comment.
Ack, thanks for the heads up. Make sense.
|
Note that unit tests didn't run! Going to open issue about that if there isn't one already. |
|
Blocked on #1048 |
4383
left a comment
There was a problem hiding this comment.
Hey Itamar, great job as always!
Your changes LGTM, I would just suggest to highlight the asyncio/forking changes into the documentations to explain that to users. See my inline comments.
Feel free to merge it or to amend it with doc changes, or to address the doc change in a follow up PR, as you prefer.
| for t in threads: | ||
| t.start() | ||
|
|
||
| check(2, threading, 'pre-fork patched') |
There was a problem hiding this comment.
I think we have to strongly highlight this behavioural change in documentation and also somewhere in the release note, do you mind adding some words about that to explain to users what they can expect or not.
I suppose this patch will potentially break lot of unit tests outside of Eventlet where people are checking for similar counting for some reasons.
| for t in threads: | ||
| t.start() | ||
|
|
||
| check(2, threading, 'pre-fork patched') |
There was a problem hiding this comment.
Apparently, according to comments, Swift and Neutron, at least, seems to rely in some ways on pre-fork and both are Eventlet compliant https://codesearch.opendev.org/?q=pre-fork&i=nope&literal=nope&files=&excludeFiles=&repos=
| check(4, eventlet.green.threading, 'pre-fork green') | ||
| check(5, eventlet.green.threading, 'pre-fork green') | ||
|
|
||
| if os.fork() == 0: |
There was a problem hiding this comment.
Ack, thanks for the heads up. Make sense.
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #1047 +/- ##
======================================
- Coverage 56% 56% -1%
======================================
Files 89 89
Lines 9847 9852 +5
Branches 1643 1645 +2
======================================
Hits 5560 5560
- Misses 3920 3924 +4
- Partials 367 368 +1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
Note that tests didn't run because of #1048 and now that I reran them they're all red 😀 So more work to be done. |
😭 😂 |
|
I didn't checked properly, I was thinking they were all green because you re-enabled them and because this patch had fixed the situation |
|
Some progress made (the test was too brittle), still slightly more to fix plus addressing review comments. |
Ack, thanks for the update. |
|
What the changelog should say:
|
|
Changelog happens in release PRs? But there's text above. The test failure is the unrelated TLS failure we get on macOS sometimes, looks like. |
|
Concerning the changelog, I'm gonna go to release a new version, so I'll simply copy and paste your sentence into |
Fixes #1030
CAVEAT:
fork()is a terrible idea and should never be used with Python programs.The root cause here is that:
threading.pyso it's actually tracking greenlets, and greenlets do survive forks.threading.py's post-fork handler.In addition, I discovered that the way fork() affected older versions of Python was buggy, e.g.
threading.enumerate()didn't list all threads because of item 1 above. So I added tests and fixed that too.This has a behavior change insofar as
eventlet.green.thread.Threadis now tracked and visible inthreading.pyAPIs, but that seems... fine? I think?