This is an automated email from the ASF dual-hosted git repository.

wwbmmm pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/brpc.git


The following commit(s) were added to refs/heads/master by this push:
     new 6715a9230 Fix semaphore wakeup count to match posted permits (#3673)
6715a9230 is described below

commit 6715a92307d94460de059c060c70755622db6a18
Author: Bright Chen <[email protected]>
AuthorDate: Sat Oct 31 14:46:31 2026 +0800

    Fix semaphore wakeup count to match posted permits (#3673)
    
    * Fix semaphore wakeup count to match posted permits
    
    * Fix semaphore wake-count test race by using bthread-only waiters
---
 src/bthread/semaphore.cpp           |  6 +--
 test/bthread_semaphore_unittest.cpp | 90 +++++++++++++++++++++++++++++++++++++
 2 files changed, 93 insertions(+), 3 deletions(-)

diff --git a/src/bthread/semaphore.cpp b/src/bthread/semaphore.cpp
index 7b9c22d55..6bdf1a8a5 100644
--- a/src/bthread/semaphore.cpp
+++ b/src/bthread/semaphore.cpp
@@ -110,13 +110,13 @@ static int bthread_sem_wait_impl(bthread_sem_t* sem, 
const struct timespec* abst
 
 static inline int bthread_sem_post(bthread_sem_t* sem, size_t num) {
     if (num > 0) {
-        unsigned n = ((butil::atomic<unsigned>*)sem->butex)
+        ((butil::atomic<unsigned>*)sem->butex)
             ->fetch_add(num, butil::memory_order_relaxed);
         const size_t sampling_range = nullptr != bthread::g_cp && 
sem->enable_csite ?
             bvar::is_collectable(&bthread::g_cp_sl) : 
bvar::INVALID_SAMPLING_RANGE;
         const int64_t start_ns = bvar::is_sampling_range_valid(sampling_range) 
?
                                  butil::cpuwide_time_ns() : -1;
-        bthread::butex_wake_n(sem->butex, n);
+        bthread::butex_wake_n(sem->butex, num);
         if (start_ns > 0) {
             const int64_t end_ns = butil::cpuwide_time_ns();
             const bthread_contention_site_t csite{end_ns - start_ns, 
sampling_range};
@@ -170,4 +170,4 @@ int bthread_sem_post_n(bthread_sem_t* sem, size_t n) {
     return bthread::bthread_sem_post(sem, n);
 }
 
-__END_DECLS
\ No newline at end of file
+__END_DECLS
diff --git a/test/bthread_semaphore_unittest.cpp 
b/test/bthread_semaphore_unittest.cpp
index d0391dcfc..4e37985d8 100644
--- a/test/bthread_semaphore_unittest.cpp
+++ b/test/bthread_semaphore_unittest.cpp
@@ -15,8 +15,13 @@
 // specific language governing permissions and limitations
 // under the License.
 
+#include <algorithm>
 #include <gtest/gtest.h>
 #include <bthread/bthread.h>
+#include <bthread/butex.h>
+#include <bthread/countdown_event.h>
+#include <butil/atomicops.h>
+#include <butil/time.h>
 
 namespace {
 
@@ -129,6 +134,91 @@ TEST(SemaphoreTest, trywait) {
     ASSERT_EQ(0, bthread_sem_destroy(&sem));
 }
 
+struct SemButexWaitArgs {
+    bthread_sem_t* sem;
+    int expected_value;
+    bthread::CountdownEvent* ready;
+    butil::atomic<int> nwoken{0};
+};
+
+void* sem_butex_waiter(void* arg) {
+    auto args = static_cast<SemButexWaitArgs*>(arg);
+    args->ready->signal();
+    const timespec deadline = butil::seconds_from_now(10);
+    // Wait only once so that excess wakeups cannot be hidden by sem_wait's
+    // retry loop. Do not consume the posted permits.
+    const int rc = bthread::butex_wait(args->sem->butex, args->expected_value,
+                                      &deadline);
+    if (rc == 0) {
+        args->nwoken.fetch_add(1, butil::memory_order_relaxed);
+    } else {
+        // The post may race with a waiter that has not yet been queued.
+        EXPECT_EQ(EWOULDBLOCK, errno);
+    }
+    return nullptr;
+}
+
+void check_post_wakeup_count(unsigned initial_value, size_t num, bool batch) {
+    SCOPED_TRACE(initial_value);
+    const int N = 16;
+    // Retry if too few waiters were queued before posting. This avoids relying
+    // on a fixed sleep to establish that the waiters have entered butex_wait.
+    for (int attempt = 0; attempt < 10; ++attempt) {
+        bthread_sem_t sem;
+        ASSERT_EQ(0, bthread_sem_init(&sem, initial_value));
+        bthread::CountdownEvent ready(N);
+        SemButexWaitArgs args{&sem, static_cast<int>(initial_value), &ready};
+        // A queued pthread woken before entering futex_wait can still return
+        // EWOULDBLOCK, which is indistinguishable from a late arrival. Use 
only
+        // bthreads so successful waits account for every counted wakeup.
+        bthread_t bthreads[N];
+        for (int i = 0; i < N; ++i) {
+            ASSERT_EQ(0, bthread_start_background(
+                &bthreads[i], nullptr, sem_butex_waiter, &args));
+        }
+        EXPECT_EQ(0, ready.timed_wait(butil::seconds_from_now(10)));
+
+        EXPECT_EQ(0, batch ? bthread_sem_post_n(&sem, num)
+                           : bthread_sem_post(&sem));
+        auto value = reinterpret_cast<butil::atomic<unsigned>*>(sem.butex);
+        EXPECT_EQ(initial_value + num, 
value->load(butil::memory_order_relaxed));
+        // Also change the value for post_n(0), so late arrivals cannot enqueue
+        // after cleanup and block forever.
+        value->fetch_add(1, butil::memory_order_relaxed);
+        const int remaining = bthread::butex_wake_all(sem.butex);
+        for (int i = 0; i < N; ++i) {
+            EXPECT_EQ(0, bthread_join(bthreads[i], nullptr));
+        }
+        const int queued = args.nwoken.load(butil::memory_order_relaxed);
+        // Successful waits include both post and cleanup wakeups; late
+        // arrivals that returned EWOULDBLOCK are excluded.
+        EXPECT_EQ(std::min(num, static_cast<size_t>(queued)),
+                  static_cast<size_t>(queued - remaining));
+        ASSERT_EQ(0, bthread_sem_destroy(&sem));
+        // More waiters than permits are needed to detect excessive wakeups.
+        if (static_cast<size_t>(queued) > num) {
+            return;
+        }
+    }
+    FAIL() << "Too few waiters were queued to check the wakeup limit";
+}
+
+TEST(SemaphoreTest, post_wakes_one_waiter) {
+    check_post_wakeup_count(0, 1, false);
+    check_post_wakeup_count(4, 1, false);
+}
+
+TEST(SemaphoreTest, post_n_wakes_requested_waiters) {
+    check_post_wakeup_count(0, 3, true);
+    check_post_wakeup_count(1, 3, true);
+    check_post_wakeup_count(8, 3, true);
+}
+
+TEST(SemaphoreTest, post_n_zero_does_not_wake_waiters) {
+    check_post_wakeup_count(0, 0, true);
+    check_post_wakeup_count(5, 0, true);
+}
+
 bool g_stopped = false;
 
 void wait_op(bthread_sem_t* sem, int64_t sleep_us) {


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to