-
Notifications
You must be signed in to change notification settings - Fork 419
Prevent Ruby thread pools from creating too many threads. #326
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
My thoughts/assumptions:
Since we cannot detect what blocking job blocks which block on the io-thread-pool we have to try to process it immediately since otherwise it could create deadlock (all already blocked treads in the pool can be directly or transitively blocked by the job in queue). Based on that ability to add more threads to the pool is quite important. Therefore I thing we have either figure out what are the limits for given platforms or keep just the graceful handling of We should also log a waning that we could not create another thread and that it may lead to deadlock, which informs the users what is the problem if they indeed hit a deadlock. |
Can you give an example of this? Also, I am somewhat confused as to why someone would ever want to have so many threads as a default to begin with. To me, it seems the overhead of context-switching and accounting will almost always far outweigh any benefits here. I could be confused here about the functionality since I do not know the full implmentation on ThreadPool. |
@ianks I can't think of any good reason, either. But the bigger concern is with the global thread pools. Since we create those thread pools implicitly we have an obligation to prevent those thread pools from crashing the application. In theory this may never be a problem. In my testing I was able to prevent a thread pool from reaching the maximum number of threads simply by adding slight delays and varying intervals. In a real application the execution of the jobs on the pool will cause natural delays. My test script was an extreme example where the tasks immediately return @pitr-ch If I follow your response correctly, you are suggesting that we keep the I like the idea of keeping the |
FWIW I did a quick test on several platforms and all give up after creating ~2000 threads on my MacBook Pro. I'm going to try on my Linux machine when I get home, because now I am curious. Here are the numbers:
Here is my test code: foo = []
1_000_000.times { foo << Thread.new{ sleep(1)} } #=> ThreadError: unable to create new native thread
foo.length |
Not being a systems programmer, the question "how many threads per process will operating system X allow?" is something I never had reason to ask. Apparently, many others have. The TL;DR is "it depends." But the 2000 cap I experienced in my earlier tests seems to be an expected limit:
|
yeah for me: |
51a2034
to
ad62177
Compare
I've done some research and we have an answer. The maximum number of threads that can be created is set by the operating system. There is no way for us to reliably determine this value within out code. Moreover, the cap would either be a maximum number for the process or for all processes. Even if we could determine a number we wouldn't be able to set a reliable max for any given thread pool because the max would have to, at best, need to be set for all thread pools we create, plus any threads spawned by other code in the application. This is impossible. Next I dug into the Java source code for java.util.concurrent.ThreadPoolExecutor. It turns out that--quite coincidentally--my solution is exactly what Java does. Internally, Java's ThreadPoolExecutor uses java.util.concurrent.ThreadFactory to create new threads. When the operating systems refuses to create a new thread the factory returns With respect to the maximum number of threads that can be set on a given thread pool, Java's With respect to logging, I am choosing not to log when there is a thread creation error. The reason is that we cannot log thread creation error on JRuby. Java's |
Prevent Ruby thread pools from creating too many threads.
@jdantonio thanks for the detailed information. This is what I was thinking and suggesting 👍. Just a note: There may be valid use-cases when |
In testing I've discovered that MRI will raise a
ThreadError
if threads are created too fast. I believe I've been able to get more than 2046 threads in the past (see e3bb86f) but I cannot verify. In my current testing on 2.2.2 I've not been able to get more than 2046.I have mixed feelings about this PR. With this change a pure-Ruby thread pool will gracefully handle
ThreadError
exceptions when adding threads to the pool. In such cases the:fallback_policy
will be triggered. This seems like a reasonable response. Unfortunately, suppressing this exception may mask other problems that we haven't discovered yet.Thought, anyone?