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

wasphin 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 b009adb42 Fix semaphore publication with release ordering (#3697)
b009adb42 is described below

commit b009adb42af89dd6a620c3fa41239fc2e2522e79
Author: Bright Chen <[email protected]>
AuthorDate: Sun Nov 1 22:01:12 2026 +0800

    Fix semaphore publication with release ordering (#3697)
---
 src/bthread/semaphore.cpp           | 14 +++---
 test/bthread_semaphore_unittest.cpp | 98 +++++++++++++++++++++++++++++++++++++
 2 files changed, 106 insertions(+), 6 deletions(-)

diff --git a/src/bthread/semaphore.cpp b/src/bthread/semaphore.cpp
index 6bdf1a8a5..eaf3d69c4 100644
--- a/src/bthread/semaphore.cpp
+++ b/src/bthread/semaphore.cpp
@@ -110,16 +110,18 @@ 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) {
+        // Publish preceding writes to successful acquire-CAS waiters, 
including
+        // those that consume permits without entering butex_wait().
         ((butil::atomic<unsigned>*)sem->butex)
-            ->fetch_add(num, butil::memory_order_relaxed);
-        const size_t sampling_range = nullptr != bthread::g_cp && 
sem->enable_csite ?
+            ->fetch_add(num, butil::memory_order_release);
+        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;
+        int64_t start_ns = bvar::is_sampling_range_valid(sampling_range) ?
+                           butil::cpuwide_time_ns() : -1;
         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};
+            int64_t end_ns = butil::cpuwide_time_ns();
+            bthread_contention_site_t csite{end_ns - start_ns, sampling_range};
             bthread::submit_contention(csite, end_ns);
         }
     }
diff --git a/test/bthread_semaphore_unittest.cpp 
b/test/bthread_semaphore_unittest.cpp
index 4e37985d8..88856dc14 100644
--- a/test/bthread_semaphore_unittest.cpp
+++ b/test/bthread_semaphore_unittest.cpp
@@ -16,6 +16,7 @@
 // under the License.
 
 #include <algorithm>
+#include <sched.h>
 #include <gtest/gtest.h>
 #include <bthread/bthread.h>
 #include <bthread/butex.h>
@@ -134,6 +135,103 @@ TEST(SemaphoreTest, trywait) {
     ASSERT_EQ(0, bthread_sem_destroy(&sem));
 }
 
+typedef int (*SemWaitOp)(bthread_sem_t*);
+
+int sem_timedwait_fast(bthread_sem_t* sem) {
+    // An available permit must be consumed even with an expired deadline.
+    const timespec deadline = { -2, 0 };
+    return bthread_sem_timedwait(sem, &deadline);
+}
+
+struct SemPublicationArgs {
+    bthread_sem_t* sem;
+    SemWaitOp wait;
+    const int* payload;
+    int observed[16];
+    int rc;
+};
+
+void* sem_publication_waiter(void* arg) {
+    auto args = static_cast<SemPublicationArgs*>(arg);
+    auto value = reinterpret_cast<butil::atomic<unsigned>*>(args->sem->butex);
+    const int64_t deadline = butil::gettimeofday_us() + 10 * 1000 * 1000;
+    // Observe available permits without acquiring the payload. Each thread
+    // consumes only once, and the post supplies enough permits for all of 
them.
+    // This keeps consumers off the blocking path and its additional locks.
+    while (value->load(butil::memory_order_relaxed) == 0) {
+        if (butil::gettimeofday_us() >= deadline) {
+            args->rc = ETIMEDOUT;
+            return nullptr;
+        }
+        sched_yield();
+    }
+    args->rc = args->wait(args->sem);
+    if (args->rc == 0) {
+        // Snapshot non-atomic data before join or any assertion can introduce
+        // synchronization. Only the successful semaphore wait may acquire it.
+        std::copy(args->payload, args->payload + ARRAY_SIZE(args->observed),
+                  args->observed);
+    }
+    return nullptr;
+}
+
+void check_post_publication(bool batch) {
+    const SemWaitOp wait_ops[] = {
+        bthread_sem_wait, sem_timedwait_fast, bthread_sem_trywait
+    };
+    const size_t nwaiters = batch ? ARRAY_SIZE(wait_ops) : 1;
+    for (int round = 0; round < 1000; ++round) {
+        SCOPED_TRACE(round);
+        bthread_sem_t sem;
+        ASSERT_EQ(0, bthread_sem_init(&sem, 0));
+        int payload[16] = {};
+        SemPublicationArgs args[ARRAY_SIZE(wait_ops)] = {};
+        pthread_t threads[ARRAY_SIZE(wait_ops)];
+        size_t started = 0;
+        int create_rc = 0;
+        // Start every consumer before writing the payload, so pthread_create
+        // cannot publish it. Use pthreads to avoid bthread scheduler handoffs.
+        for (; started < nwaiters; ++started) {
+            args[started].sem = &sem;
+            args[started].wait = wait_ops[(round + started) % 
ARRAY_SIZE(wait_ops)];
+            args[started].payload = payload;
+            create_rc = pthread_create(&threads[started], nullptr,
+                                       sem_publication_waiter, &args[started]);
+            if (create_rc != 0) {
+                break;
+            }
+        }
+        for (size_t i = 0; i < ARRAY_SIZE(payload); ++i) {
+            payload[i] = round + 1 + static_cast<int>(i);
+        }
+        // For a batch, later consumers must acquire the original publication
+        // through the release sequence extended by earlier consumers' CASes.
+        const int post_rc = batch ? bthread_sem_post_n(&sem, nwaiters)
+                                  : bthread_sem_post(&sem);
+        for (size_t i = 0; i < started; ++i) {
+            EXPECT_EQ(0, pthread_join(threads[i], nullptr));
+        }
+        EXPECT_EQ(0, bthread_sem_destroy(&sem));
+        ASSERT_EQ(0, create_rc);
+        ASSERT_EQ(0, post_rc);
+        for (size_t i = 0; i < nwaiters; ++i) {
+            ASSERT_EQ(0, args[i].rc) << "waiter=" << i;
+            for (size_t j = 0; j < ARRAY_SIZE(payload); ++j) {
+                ASSERT_EQ(payload[j], args[i].observed[j])
+                    << "waiter=" << i << " index=" << j;
+            }
+        }
+    }
+}
+
+TEST(SemaphoreTest, post_publishes_data_to_fast_waiters) {
+    check_post_publication(false);
+}
+
+TEST(SemaphoreTest, post_n_publishes_data_to_fast_waiters) {
+    check_post_publication(true);
+}
+
 struct SemButexWaitArgs {
     bthread_sem_t* sem;
     int expected_value;


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

Reply via email to