chenBright commented on code in PR #3551:
URL: https://github.com/apache/brpc/pull/3551#discussion_r4060835131
##########
src/bthread/butex.cpp:
##########
@@ -573,26 +573,37 @@ void wait_for_butex(void* arg) {
BAIDU_SCOPED_LOCK(b->waiter_lock);
if (b->value.load(butil::memory_order_relaxed) != bw->expected_value) {
bw->waiter_state = WAITER_STATE_UNMATCHEDVALUE;
- } else if (bw->waiter_state == WAITER_STATE_READY/*1*/ &&
- !bw->task_meta->interrupted) {
- if (args->prepend) {
- b->waiters.Prepend(bw);
- } else {
- b->waiters.Append(bw);
- }
- bw->container.store(b, butil::memory_order_relaxed);
+ } else {
+ // Checking `interrupted` and publishing `bw->container` must be
+ // atomic with respect to TaskGroup::interrupt(), which sets
+ // `interrupted` and consumes `current_waiter' under the same
+ // `version_lock`. Otherwise interrupt() may consume `bw` in
between
+ // and its erase_from_butex() does nothing because `container` is
+ // still nullptr, leaving this bthread queued but never woken up.
+ // `container` cannot be published upfront: it must stay nullptr
until
+ // the bthread is off its stack, see the comment after this block.
+ BAIDU_SCOPED_LOCK(bw->task_meta->version_lock);
+ if (bw->waiter_state == WAITER_STATE_READY/*1*/ &&
+ !bw->task_meta->interrupted) {
+ if (args->prepend) {
+ b->waiters.Prepend(bw);
+ } else {
+ b->waiters.Append(bw);
+ }
+ bw->container.store(b, butil::memory_order_relaxed);
#ifdef BRPC_BTHREAD_TRACER
- bw->control->_task_tracer.set_status(TASK_STATUS_SUSPENDED,
bw->task_meta);
+ TaskTracer::set_status_unsafe(TASK_STATUS_SUSPENDED,
bw->task_meta);
#endif // BRPC_BTHREAD_TRACER
- if (bw->abstime != nullptr) {
- bw->sleep_id = get_global_timer_thread()->schedule(
- erase_from_butex_and_wakeup, bw, *bw->abstime);
- if (!bw->sleep_id) { // TimerThread stopped.
- errno = ESTOP;
- erase_from_butex_and_wakeup(bw);
+ if (bw->abstime != nullptr) {
+ bw->sleep_id = get_global_timer_thread()->schedule(
+ erase_from_butex_and_wakeup, bw, *bw->abstime);
+ if (!bw->sleep_id) { // TimerThread stopped.
+ errno = ESTOP;
+ erase_from_butex_and_wakeup(bw);
Review Comment:
Confirmed and fixed in 62f8e5dd8eb823250198f838b6e6b70602e6b5b9. It was
actually two levels of self-deadlock on that path, since Butex::waiter_lock
(FastPthreadMutex) is non-recursive as well: erase_from_butex_and_wakeup() →
erase_from_butex() re-takes waiter_lock before ever reaching the tracer.
Fixed by narrowing the version_lock scope to the interrupted check + enqueue
+ container store, as suggested. Two deliberate deviations from the suggestion:
- The tracer update stays inside version_lock. It uses the new lock-free
set_status_unsafe(), so it cannot deadlock, and keeping it there makes the
transition to SUSPENDED atomic w.r.t. TraceImpl(). Moving it out would let a
concurrent butex_wake() set READY first, which we would then overwrite back to
SUSPENDED — TraceImpl() would then ContextTrace() a running bthread and return
a bogus stack.
- TimerThread::schedule() stays inside waiter_lock (but outside
version_lock). bw lives on butex_wait()'s stack: if scheduling happened after
releasing waiter_lock, a concurrent butex_wake() could remove bw and resume the
bthread, whose stack frame is then gone while the timer task still refers to it
(UAF), and unsleep_if_necessary() would miss sleep_id too. schedule() only
takes the timer's own locks, so it cannot deadlock on waiter_lock.
On schedule() failure, bw is now unlinked directly under the locks already
held (RemoveFromList() + container = nullptr + waiter_state = TIMEDOUT, errno =
ESTOP) and control falls through to the existing epilogue, which re-schedules
the bthread via ready_to_run(). That is safe.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]