-
Notifications
You must be signed in to change notification settings - Fork 25
Address FileThreadPool race condition #69
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
853d256
to
e4a14bb
Compare
yield item | ||
until queue.empty? | ||
item = lock.synchronize { queue.pop(true) unless queue.empty? } | ||
yield item if item |
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.
WDYT of a small utility method:
# yield item while (item = next_item(queue))
def next_item(queue)
lock = Mutex.new
lock.synchronize { !queue.empty? && queue.pop(true) }
end
Another option would be rescue the non-block-empty-queue-popped exception (assuming it's sufficiently specific), and replace it with a nil
.
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.
WDYT of a small utility method:
That sounds good to me, but I think the lock would need to be a member var, yeah? Getting a new lock on every method call doesn't seem real helpful.
Another option would be rescue the non-block-empty-queue-popped exception (assuming it's sufficiently specific)
It's a ThreadError
, and I felt that was too broad to rescue: previously we were rescuing them in the entire thread block. I could wrap just the pop with a rescue, but I'm not confident enough about JRuby threading internals to be sure nothing else could raise that exception class at any given time.
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 think the lock would need to be a member var, yeah? Getting a new lock on every method call doesn't seem real helpful.
Can you expand on that for my own education? I'm not familiar with this and assumed it didn't matter from where you instantiated the Mutex, it would always work the same.
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.
Oh I see. It's not a global lock, you have to use the same instance in both (all) threads for it to actually prevent the concurrency. Sorry about that.
Instance var seems fine. The FP purist in me would probably pass it into #next_item
, but this is rubby so, eh.
e9526d4
to
8df5299
Compare
I saw a couple ThreadErrors around popping from an empty queue in bugsnag: this got introduced when I moved away from catching all ThreadErrors. There's a race condition between checking that the queue is empty & attempting to pop from it. This change locks the pop operation with a mutex to prevent the race.
8df5299
to
625a2d6
Compare
@pbrisbin Thanks for that, updated. (FWIW the single line version of the loop failed to parse: I think it's because |
I think it's just the paren-less method call. Anyway, I like the multi-line way. LGTM. |
Address FileThreadPool race condition
I saw a couple ThreadErrors around popping from an empty queue in
bugsnag: this got introduced when I moved away from catching all
ThreadErrors. There's a race condition between checking that the queue
is empty & attempting to pop from it.
This change locks the pop operation with a mutex to prevent the race.
👀 @codeclimate/review