| 668 | } |
| 669 | |
| 670 | int butex_wait(void* arg, int expected_value, const timespec* abstime, bool prepend) { |
| 671 | Butex* b = container_of(static_cast<butil::atomic<int>*>(arg), Butex, value); |
| 672 | if (b->value.load(butil::memory_order_relaxed) != expected_value) { |
| 673 | errno = EWOULDBLOCK; |
| 674 | // Sometimes we may take actions immediately after unmatched butex, |
| 675 | // this fence makes sure that we see changes before changing butex. |
| 676 | butil::atomic_thread_fence(butil::memory_order_acquire); |
| 677 | return -1; |
| 678 | } |
| 679 | TaskGroup* g = tls_task_group; |
| 680 | if (NULL == g || g->is_current_pthread_task()) { |
| 681 | return butex_wait_from_pthread(g, b, expected_value, abstime, prepend); |
| 682 | } |
| 683 | ButexBthreadWaiter bbw; |
| 684 | // tid is 0 iff the thread is non-bthread |
| 685 | bbw.tid = g->current_tid(); |
| 686 | bbw.container.store(NULL, butil::memory_order_relaxed); |
| 687 | bbw.task_meta = g->current_task(); |
| 688 | bbw.sleep_id = 0; |
| 689 | bbw.waiter_state = WAITER_STATE_READY; |
| 690 | bbw.expected_value = expected_value; |
| 691 | bbw.initial_butex = b; |
| 692 | bbw.control = g->control(); |
| 693 | bbw.abstime = abstime; |
| 694 | bbw.tag = g->tag(); |
| 695 | |
| 696 | if (abstime != NULL) { |
| 697 | // Schedule timer before queueing. If the timer is triggered before |
| 698 | // queueing, cancel queueing. This is a kind of optimistic locking. |
| 699 | if (butil::timespec_to_microseconds(*abstime) < |
| 700 | (butil::gettimeofday_us() + MIN_SLEEP_US)) { |
| 701 | // Already timed out. |
| 702 | errno = ETIMEDOUT; |
| 703 | return -1; |
| 704 | } |
| 705 | } |
| 706 | #ifdef SHOW_BTHREAD_BUTEX_WAITER_COUNT_IN_VARS |
| 707 | bvar::Adder<int64_t>& num_waiters = butex_waiter_count(); |
| 708 | num_waiters << 1; |
| 709 | #endif |
| 710 | |
| 711 | // release fence matches with acquire fence in interrupt_and_consume_waiters |
| 712 | // in task_group.cpp to guarantee visibility of `interrupted'. |
| 713 | bbw.task_meta->current_waiter.store(&bbw, butil::memory_order_release); |
| 714 | WaitForButexArgs args{ &bbw, prepend }; |
| 715 | g->set_remained(wait_for_butex, &args); |
| 716 | TaskGroup::sched(&g); |
| 717 | |
| 718 | // erase_from_butex_and_wakeup (called by TimerThread) is possibly still |
| 719 | // running and using bbw. The chance is small, just spin until it's done. |
| 720 | BT_LOOP_WHEN(unsleep_if_necessary(&bbw, get_global_timer_thread()) < 0, |
| 721 | 30/*nops before sched_yield*/); |
| 722 | |
| 723 | // If current_waiter is NULL, TaskGroup::interrupt() is running and using bbw. |
| 724 | // Spin until current_waiter != NULL. |
| 725 | BT_LOOP_WHEN(bbw.task_meta->current_waiter.exchange( |
| 726 | NULL, butil::memory_order_acquire) == NULL, |
| 727 | 30/*nops before sched_yield*/); |