-
Notifications
You must be signed in to change notification settings - Fork 419
Reset ThreadPoolExecutor on fork. #501
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
This latest update actually clears the pool if it detects a fork. I've updated the test script to perform a fork using the example given here. The new implementation survives the fork and posts the new job. I ran the test script on my work computer (2.8 GHz Intel Core i7, 16 GB 1600 MHz DDR3) and the difference in performance is still minimal. |
FWIW, my imagined But yeah.. my results with that script match yours: they're so close I can't even get the original, clearly-less-code, version to consistently bench as the fastest... and when they do, $$ seems to be faster than the thread check anyway. |
@queue.clear | ||
@ready.clear | ||
@pool.clear | ||
@ruby_pid == $$ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
=
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ty. Fixed.
@matthewd @pitr-ch @chrisseaton Please review. |
|
||
Some Ruby versions allow the Ruby process to be [forked](http://ruby-doc.org/core-2.3.0/Process.html#method-c-fork). Generally, mixing threading and forking is an [anti-pattern](https://en.wikipedia.org/wiki/Anti-pattern). Threading and forking are both concurrency techniques and mixing the two rarely provides a benefit. Moreover, Ruby does not copy any threads execept the main thread when forking. Thus threads created before the fork become unusable ("dead") in the forked process. This aspect of forking is a significant issue for any application or library which spawns threads, including but not limited to Concurrent Ruby. It is strongly advised that applications using `ThreadPoolExecutor` either directly or indirectly (via high-level concurrency abstractions like `Future` and `Actor`) do **not** also fork. Since Concurrent Ruby is a foundational library often used by gems which are in turn used by other applications it is impossible to prevent forking upstream. Concurrent Ruby therefore makes a few guaratnees about the behavior of `ThreadPoolExecutor` after forking. | ||
|
||
*Concurrent Ruby guarantees that any thread pools created on by the forking process before the fork remain functional on the forked process after the fork. It also guarantees that jobs post to a thread pool before a fork remain on the thread pool in the forking process after the fork.* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"created on by"
Maybe "remain on the thread pool in (only) the forking process"?
104a2c7
to
72a43cd
Compare
|
||
Some Ruby versions allow the Ruby process to be [forked](http://ruby-doc.org/core-2.3.0/Process.html#method-c-fork). Generally, mixing threading and forking is an [anti-pattern](https://en.wikipedia.org/wiki/Anti-pattern). Threading and forking are both concurrency techniques and mixing the two is rarely beneficial. Moreover, Ruby does not copy any threads execept the main thread when forking. Thus threads created before the fork become unusable ("dead") in the forked process. This aspect of forking is a significant issue for any application or library which spawns threads. It is strongly advised that applications using `ThreadPoolExecutor`, either directly or indirectly (via high-level concurrency abstractions like `Future` and `Actor`), do **not** also fork. Since Concurrent Ruby is a foundational library often used by gems which are in turn used by other applications, it is impossible to predict or prevent upstream forking. Concurrent Ruby therefore makes a few guaratnees about the behavior of `ThreadPoolExecutor` after forking. | ||
|
||
*Concurrent Ruby guarantees that all threads created by thread pools in the forking process before the fork remain only in the forking process after the fork. It also guarantees that jobs post to a thread pool before a fork remain on the thread pool in the forking process after the fork. Finally, it guarantees that thread pools copied to a forked process will continue to function normally on the forked process.* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, but the terminology here confused me a couple times.
Maybe parent
/child
would already help to figure which process we are talking about.
Also, the first guarantee is not a Concurrent Ruby one but one of the semantics of fork(2)
at the OS level, right?
Maybe something simpler along the lines of
"Current jobs will be processed by the parent process. The child process does not inherit any job." would help.
Another angle of attack is to ask the the executor if it has a thread that is alive with |
|
||
## Forking | ||
|
||
Some Ruby versions allow the Ruby process to be [forked](http://ruby-doc.org/core-2.3.0/Process.html#method-c-fork). Generally, mixing threading and forking is an [anti-pattern](https://en.wikipedia.org/wiki/Anti-pattern). Threading and forking are both concurrency techniques and mixing the two is rarely beneficial. Moreover, Ruby does not copy any threads execept the main thread when forking. Thus threads created before the fork become unusable ("dead") in the forked process. This aspect of forking is a significant issue for any application or library which spawns threads. It is strongly advised that applications using `ThreadPoolExecutor`, either directly or indirectly (via high-level concurrency abstractions like `Future` and `Actor`), do **not** also fork. Since Concurrent Ruby is a foundational library often used by gems which are in turn used by other applications, it is impossible to predict or prevent upstream forking. Concurrent Ruby therefore makes a few guaratnees about the behavior of `ThreadPoolExecutor` after forking. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Moreover, Ruby does not copy any threads execept the main thread when forking."
s/Ruby/the Operating System/ I believe.
Also, it's not necessarily the main thread:
ruby -e 'p Thread.main; Thread.new { fork {p Thread.main; p Thread.current}}.join'
Maybe use the example of locks taken by threads (other than the one forking) as why it's an anti-pattern (the lock would remained locked forever and unlock-able in the child process).
6b66cca
to
ab2825e
Compare
ab2825e
to
d53cf37
Compare
Another update to the docs. |
Reset ThreadPoolExecutor on fork.
Can we get a release cut with this fix? Any major issues to date? |
I'll cut a release this week. I was just waiting to see if other bugs were reported. |
Great, thanks! |
@schneems v1.0.1 has been released. With this update you should now also be able to safely use |
Awesome! Thanks ❤️ we need to get sprockets-rails back in the green. The threads dying on fork was causing failures |
When a
ThreadPoolExecutor
in the forked process detects that a fork has occurred it immediately takes the following actions: