-
Notifications
You must be signed in to change notification settings - Fork 3.6k
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
GH-44846: [C++] Fix thread-unsafe access in ConcurrentQueue::UnsyncFront #44849
Conversation
@github-actions crossbow submit test-ubuntu-24.04-cpp-thread-sanitizer |
|
Revision: 2f92a5b Submitted crossbow builds: ursacomputing/crossbow @ actions-5084f8405a
|
@github-actions crossbow submit -g cpp |
Revision: 2f92a5b Submitted crossbow builds: ursacomputing/crossbow @ actions-7059b221ca |
size_t UnsyncSize() const { return queue_.size(); } | ||
const T& Front() const { | ||
// Need to lock the queue because `front()` may be implemented in terms | ||
// of `begin()`, which isn't safe with concurrent calls to e.g. `push()`. |
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.
is this push
means deque::push_back/front
or ConcurrentQueue::Push()
?
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.
Here it means std::queue::push
:) (which is concretely the same as std::deque::push_back
)
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.
Ah, the original code is too optimistic. The deque might implemented as something like std::vector<block*>
and block*
for elements, the front()
might visit std::vector<block*>
and push_back
might enlarge it, so it really has problem here
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 tackling this. Approve minus one question.
// of `begin()`, which isn't safe with concurrent calls to e.g. `push()`. | ||
// (see GH-44846) | ||
std::unique_lock<std::mutex> lock(mutex_); | ||
return queue_.front(); |
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.
Is it safe to return a reference or does a copy need to be made (the reference will outlive the lock)?
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.
References to deque contents are not invalidated by container mutations, so it's ok.
(unlike iterators, which are invalidated)
🤔 would |
|
Thanks for the reviews @westonpace @mapleFU ! |
After merging your PR, Conbench analyzed the 3 benchmarking runs that have been run so far on merge-commit c4d17fd. There were 132 benchmark results with an error:
There were no benchmark performance regressions. 🎉 The full Conbench report has more details. |
Rationale for this change
The
UnsyncFront
method claims that it can access astd::deque
's front element without synchronizing with another thread that would callstd::deque::push_back
, butstd::deque::front
can actually be implemented in terms ofstd::deque::begin
whilestd::deque::push_back
is specified to invalidate iterators.In the end,
UnsyncFront
is concretely thread-unsafe even though it might ideally be thread-safe. This shows up as occasional Thread Sanitizer failures.What changes are included in this PR?
Replace
UnsyncFront
with a thread-safeFront
method.Are these changes tested?
Yes.
Are there any user-facing changes?
No.