-
Notifications
You must be signed in to change notification settings - Fork 25
Add and test JRuby implementation #14
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 implementation is based on the JDK's ScheduledThreadPoolExecutor class, which provides an efficient way to schedule many tasks in the future without spinning up a thread for each. This logic is ported from a Java implementation of timeout that JRuby has shipped for many years, and passes all but one of the timeout tests.
It would be great if we can get a pure-Ruby version in this gem, and then that would be used by all Ruby implementations. I think the one part of https://github.com/oracle/truffleruby/blob/master/lib/truffle/timeout.rb which is not just standard Ruby is Maybe we can emulate WEBrick also has its own timeout implementation in https://github.com/ruby/webrick/blob/e4182968b1ae34f38b71247fae05acf22a8f258e/lib/webrick/utils.rb#L182-L184. |
Performance is now roughly the same as the Java version.
I have refactored the implementation and it is now about the same speed as the Java version:
|
@eregon I agree, and the implementation by @evanphx is probably efficient enough for everyone. The lack of a queue pop timeout is the main problem, since we could not support that across all impls until 3.2. I don't see a way around that in the short term, since emulating pop timeout with a separate thread would mean we are basically implementing a timeout thread for Queue#pop in order to implement Timeout using Queue#pop. |
I am marking this ready for review even though there's still the one failure in the weird rescue-skipping test. That test depends on |
I'll check how we could emulate Queue#pop with timeout. |
One concern I have with the threaded impl ls the nasty races that might come from having two threads fighting over the state of the timeout job, but perhaps the as-yet-unwritten implementation can avoid serious problems. I also considered an IO.pipe but I think that would mean serializing jobs or some other inefficient hand-off mechanism. |
I think it'd be good to unify behavior of all Ruby implementations for that, i.e., either change so CRuby no longer uses The test is Lines 38 to 50 in 9b18d10
MRI issue: https://bugs.ruby-lang.org/issues/8730 And the code implementing it: Lines 29 to 50 in 9b18d10
I also remember some bug, maybe in Rails that due to using |
I found that reference about Rails + transactions + Timeout using throw: https://bugs.ruby-lang.org/issues/15567#note-20 |
https://spin.atomicobject.com/2014/07/07/ruby-queue-pop-timeout/ actually shows a way to implement it without an extra thread or file descriptor, just using a ConditionVariable, that seems a good way to emulate it, and then we could use the new Queue#pop with :timeout where available: https://bugs.ruby-lang.org/issues/18774 |
Currently, p Timeout::Error.ancestors
# => [Timeout::Error, RuntimeError, StandardError, Exception, ...] So maybe this is a reason why So probably it should either be another exception class which < Exception, or Exception itself to interrupt the thread timing out, and then convert to |
Since #15 is faster on the given benchmark, more portable and avoids duplication, I think there is no reason to merge this. |
@eregon it would be nice to have a root |
@ioquatix That's out of scope, #15 is fully compatible on purpose, let's discuss that on https://bugs.ruby-lang.org/issues/17363 or in a separate issue. |
The implementation in #15 from Rubinius, modified by @eregon for compatibiity and performance, appears to be the fastest on JRuby, so this PR is no longer needed. https://gist.github.com/headius/71e6721577872b03125f4ad2c0c024fa |
This PR incorporates a pure-Ruby port of JRuby's native
timeout
logic into this library.The existing implementation spins up a new native thread per timeout request, which is inefficient for several reasons:
This last point is perhaps the most worrisome: nearly all operations that pass through this code will never time out and do not need the timeout thread, but it is still created and joined every time.
Instead, we use the JDK
ScheduledThreadPoolExecutor
, which spins up a single thread and schedules timeout requests into it. No more than one thread will ever be started, and operations that do not time out incur only the cost of enqueueing the timeout request.Performance is naturally much faster than spinning up a thread each time (see benchmark and performance below).
This PR starts out as a draft for a few reasons:
timeout
library could be improved and used by all implemenations by using logic similar to JDK'sScheduledThreadPoolExecutor
. This simplest way to do this would be to use the version oftimeout
that @evanphx wrote for Rubinius (now incorporated into TruffleRuby): https://github.com/oracle/truffleruby/blob/master/lib/truffle/timeout.rbJRuby.runtime
(which requires us torequire 'jruby'
) andorg.jruby.RubyTime#convert_time_interval
This implementation currently passes all but one test, but I am unsure how important this test is or what exactly the expected behavior is supposed to be (
test_skip_rescue
):Benchmark and results are below (on JRuby master/9.4):
Before:
After:
And using our Java implementation, as an example of the lost performance from this naive port (which is about 4x slower):
Fixes #11.