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
Fix a possible race where Thread.interrupted was not properly cleared #131
Conversation
ee752a6
to
43a01d0
Compare
43a01d0
to
3edfa23
Compare
rescue java.lang.InterruptedException => e | ||
# NOOP, we don't expect these, but maybe some interruptible thing could be | ||
# added to grok besides regexps | ||
@logger.debug("Unexpected interruptible caught during grok. This isn't a problem most likely") |
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.
since the grok has likely been interrupted, shouldn't we either log at a higher, warn or error or bubble up the exception?
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.
Hmmm, good point, yes, it should be a warn.
@@ -72,31 +68,16 @@ def start_thread_groking(thread) | |||
@threads_to_start_time.put(thread, java.lang.System.nanoTime) |
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.
one thing confusing here and in the grok_till_timeout
method is that the current thread is passed as the thread
parameter but the current thread is also referenced implicitly using the java.lang.Thread.xxx
.
AFAIU both should be the same thread so I wonder for clarity sake if we should not use one thread reference notation?
java.lang.Thread.interrupted
@threads_to_start_time.put(java.lang.Thread.currentThread(), java.lang.System.nanoTime)
or
thread.interrupted
@threads_to_start_time.put(thread, java.lang.System.nanoTime)
?
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.
+1 on standardizing on the instance
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.
OK, this is now done
Left some minor notes, the thread interruption handling logic seems good. |
end | ||
# If the regexp finished, but interrupt was called after, we'll want to | ||
# clear the interrupted status anyway | ||
@threads_to_start_time.remove(thread) |
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.
even though this is class-internal, this change introduces something of a mixed abstraction to this method -- we register this thread via start_thread_groking
and unregister it by reaching directly into an ivar; if we can keep this method at a single level of abstraction and continue to use the stop_thread_groking
that this change also removes to unregister, I believe it will provide greater long-term clarity.
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.
I've opted to remove start_thread_groking
and just inline its one call. stop_thread_grokking
would only be called once from one spot since there isn't a single way to stop it. In one spot we must compute
in another remove
.
WDYT of how it looks now?
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.
eh, in an ideal world we'd have better encapsulation (e.g., both grok_till_timeout
and cancel_time_out!
reach into the @threads_to_start_time
ivar directly to muck with its internals), but at least this method isn't mixing abstractions.
EDIT: what better encapsulation? this is literally in a class that only encapsulates the timeout enforcement logic. Clearly I need more coffee.
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.
☕️
# If the regexp finished, but interrupt was called after, we'll want to | ||
# clear the interrupted status anyway | ||
@threads_to_start_time.remove(thread) | ||
java.lang.Thread.interrupted |
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.
should we be sending java.lang.Thread#isInterrupted()
to our thread
? It's odd that we both use a reference for the current thread and also rely on static methods that target the current thread.
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.
isInterrupted()
does not clear the interrupted state, interrupted()
does. In other words, if the thread is in an interrupted state, calling twice interrupted()
will return true
then false
.
I agree we should use thread.interrupted()
clarity/consistency ... but practically this has no impact since thread
is the current thread too.
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.
I'm +1 on using the local thread object for clarity
@threads_to_start_time.compute(thread) do |thread, start_time| | ||
if start_time && start_time < now && now - start_time > @timeout_nanos | ||
thread.interrupt | ||
nil # Delete the key |
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.
a little unsure about the jruby/java boundary here -- nil
is not null
, and while I can find documentation that the coersion happens from Java to Ruby, I'm not finding anything explicit about the other direction.
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.
$ irb
irb(main):001:0> require "java"
=> false
irb(main):002:0> h = java.util.concurrent.ConcurrentHashMap.new
=> {}
irb(main):003:0> h.inspect
=> "{}"
irb(main):004:0> h.put("a", 1)
=> nil
irb(main):005:0> h.inspect
=> "{\"a\"=>1}"
irb(main):006:0> h.compute("a") {|v| nil}
=> nil
irb(main):007:0> h.inspect
=> "{}"
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.
Thanks for confirming this behavior colin!
nit: I noticed that |
@andrewvc @jordansissel following up on our conversation about @threads_to_start_time.forEach do |thread, start_time|
@threads_to_start_time.compute(thread) do |thread, start_time|
...
end
end From what I can read in https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/package-summary.html#Weakly
So I am not sure about the behaviour of |
@colinsurprenant makes sense to move to |
2b5cae9
to
eb88860
Compare
I changed this in a few ways:
|
@andrewvc nit: just noticed that
|
eb88860
to
d213869
Compare
@colinsurprenant just removed that extra line, good catch! |
Another observation: this is not part of the change set but might be a good idea to modify: the
|
@andrewvc I leave it to you to decide for |
This should keep perf even
d213869
to
f57ed2a
Compare
@colinsurprenant moved |
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.
LGTM
end | ||
# If the regexp finished, but interrupt was called after, we'll want to | ||
# clear the interrupted status anyway | ||
@threads_to_start_time.remove(thread) |
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.
eh, in an ideal world we'd have better encapsulation (e.g., both grok_till_timeout
and cancel_time_out!
reach into the @threads_to_start_time
ivar directly to muck with its internals), but at least this method isn't mixing abstractions.
EDIT: what better encapsulation? this is literally in a class that only encapsulates the timeout enforcement logic. Clearly I need more coffee.
Andrew Cholakian merged this into the following branches!
|
@andrewvc , Is this fix available in LS 5.5.2? Seems like I have only 3.4.4, and If I understand correctly, this fix is in 4.0.1 ?
|
Just confirmed I was able to upgrade in LS 5.6.3
|
This should keep perf close enough. It's a little slower, but worth it for the consistency.
Old perf as measured with
time
:54.57 real 216.58 user 3.51 sys
New perf as measured with
time
:59.58 real 238.70 user 4.21 sys
Test config: