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

chenBright 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 2857208cc Fix synchronization of object pool free chunks (#3615)
2857208cc is described below

commit 2857208cc032d6578743b48c670e842b810ebfff
Author: nkysg <[email protected]>
AuthorDate: Mon Oct 26 10:41:08 2026 +0800

    Fix synchronization of object pool free chunks (#3615)
    
    * Fix synchronization of object pool free chunks
    
    * Add free chunk count transition tests
    
    * rm unnecessary comments
    
    * update unit tests
---
 src/butil/object_pool_inl.h     | 11 ++++++++---
 src/butil/resource_pool_inl.h   | 11 ++++++++---
 test/BUILD.bazel                |  1 +
 test/CMakeLists.txt             |  1 +
 test/Makefile                   |  1 +
 test/object_pool_unittest.cpp   | 42 +++++++++++++++++++++++++++++++++++++++++
 test/resource_pool_unittest.cpp | 42 +++++++++++++++++++++++++++++++++++++++++
 7 files changed, 103 insertions(+), 6 deletions(-)

diff --git a/src/butil/object_pool_inl.h b/src/butil/object_pool_inl.h
index caabca9b0..ea78ee5a0 100644
--- a/src/butil/object_pool_inl.h
+++ b/src/butil/object_pool_inl.h
@@ -357,6 +357,7 @@ public:
 private:
     ObjectPool() {
         _free_chunks.reserve(OP_INITIAL_FREE_LIST_SIZE);
+        _nfree_chunks.store(0, butil::memory_order_relaxed);
         pthread_mutex_init(&_free_chunks_mutex, nullptr);
 #if defined(BUTIL_USE_ASAN) && \
     !defined(BAIDU_CLEAR_OBJECT_POOL_AFTER_ALL_THREADS_QUIT)
@@ -536,9 +537,10 @@ private:
 
 private:
     bool pop_free_chunk(FreeChunk& c) {
-        // Critical for the case that most return_object are called in
-        // different threads of get_object.
-        if (_free_chunks.empty()) {
+        // This relaxed load is only a hint that avoids contending on the
+        // mutex when the shared free list is empty. The list itself remains
+        // protected by _free_chunks_mutex below.
+        if (_nfree_chunks.load(butil::memory_order_relaxed) == 0) {
             return false;
         }
         pthread_mutex_lock(&_free_chunks_mutex);
@@ -548,6 +550,7 @@ private:
         }
         DynamicFreeChunk* p = _free_chunks.back();
         _free_chunks.pop_back();
+        _nfree_chunks.fetch_sub(1, butil::memory_order_relaxed);
         pthread_mutex_unlock(&_free_chunks_mutex);
         c.nfree = p->nfree;
         memcpy(c.ptrs, p->ptrs, sizeof(*p->ptrs) * p->nfree);
@@ -565,6 +568,7 @@ private:
         memcpy(p->ptrs, c.ptrs, sizeof(*c.ptrs) * c.nfree);
         pthread_mutex_lock(&_free_chunks_mutex);
         _free_chunks.push_back(p);
+        _nfree_chunks.fetch_add(1, butil::memory_order_relaxed);
         pthread_mutex_unlock(&_free_chunks_mutex);
         return true;
     }
@@ -580,6 +584,7 @@ private:
 
     std::vector<DynamicFreeChunk*> _free_chunks;
     pthread_mutex_t _free_chunks_mutex;
+    butil::static_atomic<size_t> _nfree_chunks;
 
 #ifdef BUTIL_OBJECT_POOL_NEED_FREE_ITEM_NUM
     static butil::static_atomic<size_t> _global_nfree;
diff --git a/src/butil/resource_pool_inl.h b/src/butil/resource_pool_inl.h
index 57134c901..1c75a0867 100644
--- a/src/butil/resource_pool_inl.h
+++ b/src/butil/resource_pool_inl.h
@@ -388,6 +388,7 @@ public:
 private:
     ResourcePool() {
         _free_chunks.reserve(RP_INITIAL_FREE_LIST_SIZE);
+        _nfree_chunks.store(0, butil::memory_order_relaxed);
         pthread_mutex_init(&_free_chunks_mutex, nullptr);
     }
 
@@ -519,9 +520,10 @@ private:
 
 private:
     bool pop_free_chunk(FreeChunk& c) {
-        // Critical for the case that most return_object are called in
-        // different threads of get_object.
-        if (_free_chunks.empty()) {
+        // This relaxed load is only a hint that avoids contending on the
+        // mutex when the shared free list is empty. The list itself remains
+        // protected by _free_chunks_mutex below.
+        if (_nfree_chunks.load(butil::memory_order_relaxed) == 0) {
             return false;
         }
         pthread_mutex_lock(&_free_chunks_mutex);
@@ -531,6 +533,7 @@ private:
         }
         DynamicFreeChunk* p = _free_chunks.back();
         _free_chunks.pop_back();
+        _nfree_chunks.fetch_sub(1, butil::memory_order_relaxed);
         pthread_mutex_unlock(&_free_chunks_mutex);
         c.nfree = p->nfree;
         memcpy(c.ids, p->ids, sizeof(*p->ids) * p->nfree);
@@ -548,6 +551,7 @@ private:
         memcpy(p->ids, c.ids, sizeof(*c.ids) * c.nfree);
         pthread_mutex_lock(&_free_chunks_mutex);
         _free_chunks.push_back(p);
+        _nfree_chunks.fetch_add(1, butil::memory_order_relaxed);
         pthread_mutex_unlock(&_free_chunks_mutex);
         return true;
     }
@@ -563,6 +567,7 @@ private:
 
     std::vector<DynamicFreeChunk*> _free_chunks;
     pthread_mutex_t _free_chunks_mutex;
+    butil::static_atomic<size_t> _nfree_chunks;
 
 #ifdef BUTIL_RESOURCE_POOL_NEED_FREE_ITEM_NUM
     static butil::static_atomic<size_t> _global_nfree;
diff --git a/test/BUILD.bazel b/test/BUILD.bazel
index a2c291eb7..2d331018b 100644
--- a/test/BUILD.bazel
+++ b/test/BUILD.bazel
@@ -120,6 +120,7 @@ TEST_BUTIL_SOURCES = [
     "crc32c_unittest.cc",
     "iobuf_unittest.cpp",
     "object_pool_unittest.cpp",
+    "resource_pool_unittest.cpp",
     "test_switches.cc",
     "scoped_locale.cc",
     "recordio_unittest.cpp",
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 4c3a09f52..d4348027a 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -132,6 +132,7 @@ SET(TEST_BUTIL_SOURCES
     ${CMAKE_CURRENT_SOURCE_DIR}/non_thread_safe_unittest.cc
     ${CMAKE_CURRENT_SOURCE_DIR}/nullable_string16_unittest.cc
     ${CMAKE_CURRENT_SOURCE_DIR}/object_pool_unittest.cpp
+    ${CMAKE_CURRENT_SOURCE_DIR}/resource_pool_unittest.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/observer_list_unittest.cc
     ${CMAKE_CURRENT_SOURCE_DIR}/optional_unittest.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/platform_thread_unittest.cc
diff --git a/test/Makefile b/test/Makefile
index f4d5462e6..4d007f266 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -145,6 +145,7 @@ TEST_BUTIL_SOURCES = \
     crc32c_unittest.cc \
     iobuf_unittest.cpp \
     object_pool_unittest.cpp \
+    resource_pool_unittest.cpp \
     recordio_unittest.cpp \
     test_switches.cc \
     scoped_locale.cc \
diff --git a/test/object_pool_unittest.cpp b/test/object_pool_unittest.cpp
index 366b05224..10ad931ab 100644
--- a/test/object_pool_unittest.cpp
+++ b/test/object_pool_unittest.cpp
@@ -25,6 +25,7 @@
 
 namespace {
 struct MyObject {};
+struct FreeChunkCountObject {};
 
 int nfoo_dtor = 0;
 struct Foo {
@@ -51,6 +52,10 @@ template <> struct ObjectPoolFreeChunkMaxItem<MyObject> {
     static size_t value() { return 5; }
 };
 
+template <> struct ObjectPoolFreeChunkMaxItem<FreeChunkCountObject> {
+    static size_t value() { return 1; }
+};
+
 template <> struct ObjectPoolValidator<Foo> {
     static bool validate(const Foo* foo) {
         return foo->x != 0;
@@ -243,6 +248,43 @@ TEST_F(ObjectPoolTest, get_perf) {
     std::cout << describe_objects<SilentObj>() << std::endl;
 }
 
+struct FreeChunkCountArgs {
+    FreeChunkCountObject* first;
+    FreeChunkCountObject* second;
+};
+
+void* add_two_free_chunks(void* arg) {
+    FreeChunkCountArgs* args = static_cast<FreeChunkCountArgs*>(arg);
+    args->first = get_object<FreeChunkCountObject>();
+    args->second = get_object<FreeChunkCountObject>();
+    return_object(args->first);
+    return_object(args->second);
+    return nullptr;
+}
+
+TEST_F(ObjectPoolTest, free_chunk_count_tracks_push_and_pop) {
+    // Keep the main LocalPool alive so worker teardown does not clear chunks.
+    FreeChunkCountObject* guard = get_object<FreeChunkCountObject>();
+    ASSERT_NE(nullptr, guard);
+    FreeChunkCountArgs args = { nullptr, nullptr };
+    pthread_t tid;
+    ASSERT_EQ(0, pthread_create(&tid, nullptr, add_two_free_chunks, &args));
+    ASSERT_EQ(0, pthread_join(tid, nullptr));
+
+    FreeChunkCountObject* first = get_object<FreeChunkCountObject>();
+    FreeChunkCountObject* second = get_object<FreeChunkCountObject>();
+    ASSERT_NE(nullptr, first);
+    ASSERT_NE(nullptr, second);
+    ASSERT_TRUE(first == args.first || first == args.second);
+    ASSERT_TRUE(second == args.first || second == args.second);
+    ASSERT_NE(first, second);
+
+    return_object(first);
+    return_object(second);
+    return_object(guard);
+    clear_objects<FreeChunkCountObject>();
+}
+
 struct D { int val[1]; };
 
 void* get_and_return_int(void*) {
diff --git a/test/resource_pool_unittest.cpp b/test/resource_pool_unittest.cpp
index 9bc27f134..d3f6fbf79 100644
--- a/test/resource_pool_unittest.cpp
+++ b/test/resource_pool_unittest.cpp
@@ -25,6 +25,7 @@
 
 namespace {
 struct MyObject {};
+struct FreeChunkCountObject {};
 
 int nfoo_dtor = 0;
 struct Foo {
@@ -51,6 +52,10 @@ template <> struct ResourcePoolFreeChunkMaxItem<MyObject> {
     static size_t value() { return 5; }
 };
 
+template <> struct ResourcePoolFreeChunkMaxItem<FreeChunkCountObject> {
+    static size_t value() { return 1; }
+};
+
 template <> struct ResourcePoolValidator<Foo> {
     static bool validate(const Foo* foo) {
         return foo->x != 0;
@@ -277,6 +282,43 @@ TEST_F(ResourcePoolTest, get_perf) {
     std::cout << describe_resources<SilentObj>() << std::endl;
 }
 
+struct FreeChunkCountArgs {
+    ResourceId<FreeChunkCountObject> first;
+    ResourceId<FreeChunkCountObject> second;
+};
+
+void* add_two_free_chunks(void* arg) {
+    FreeChunkCountArgs* args = static_cast<FreeChunkCountArgs*>(arg);
+    get_resource(&args->first);
+    get_resource(&args->second);
+    return_resource(args->first);
+    return_resource(args->second);
+    return nullptr;
+}
+
+TEST_F(ResourcePoolTest, free_chunk_count_tracks_push_and_pop) {
+    // Keep the main LocalPool alive so worker teardown does not clear chunks.
+    ResourceId<FreeChunkCountObject> guard = { 0 };
+    ASSERT_NE(nullptr, get_resource(&guard));
+    FreeChunkCountArgs args = { { 0 }, { 0 } };
+    pthread_t tid;
+    ASSERT_EQ(0, pthread_create(&tid, nullptr, add_two_free_chunks, &args));
+    ASSERT_EQ(0, pthread_join(tid, nullptr));
+
+    ResourceId<FreeChunkCountObject> first = { 0 };
+    ResourceId<FreeChunkCountObject> second = { 0 };
+    ASSERT_NE(nullptr, get_resource(&first));
+    ASSERT_NE(nullptr, get_resource(&second));
+    ASSERT_TRUE(first == args.first || first == args.second);
+    ASSERT_TRUE(second == args.first || second == args.second);
+    ASSERT_NE(first, second);
+
+    return_resource(first);
+    return_resource(second);
+    return_resource(guard);
+    clear_resources<FreeChunkCountObject>();
+}
+
 struct D { int val[1]; };
 
 void* get_and_return_int(void*) {


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

Reply via email to