Copilot commented on code in PR #3155:
URL: https://github.com/apache/brpc/pull/3155#discussion_r2564084084


##########
src/brpc/rdma/block_pool.cpp:
##########
@@ -332,66 +331,74 @@ bool InitBlockPool(RegisterCallback cb) {
     return false;
 }
 
+static void MoveExpansionList2EmptyIdleList(int block_type, size_t index) {
+    CHECK(NULL == g_info->idle_list[block_type][index]);
+
+    g_info->idle_list[block_type][index] = 
g_info->expansion_list[block_type][index];
+    g_info->idle_size[block_type][index] += 
g_info->expansion_size[block_type][index];
+    g_info->expansion_list[block_type][index] = NULL;
+    g_info->expansion_size[block_type][index] = 0;
+}
+
 static void* AllocBlockFrom(int block_type) {
     bool locked = false;
     if (BAIDU_UNLIKELY(g_dump_enable)) {
         g_dump_mutex->lock();
         locked = true;
     }
+    BUTIL_SCOPE_EXIT {
+        if (locked) {
+            g_dump_mutex->unlock();
+        }
+    };
+
     void* ptr = NULL;
-    if (block_type == 0 && tls_idle_list != NULL){
+    if (0 == block_type && NULL != tls_idle_list) {
         CHECK(tls_idle_num > 0);
         IdleNode* n = tls_idle_list;
         tls_idle_list = n->next;
         ptr = n->start;
         butil::return_object<IdleNode>(n);
         tls_idle_num--;
-        if (locked) {
-            g_dump_mutex->unlock();
-        }
         return ptr;
     }
 
-    uint64_t index = butil::fast_rand() % g_buckets;
+    size_t index = butil::fast_rand() % g_buckets;
     BAIDU_SCOPED_LOCK(*g_info->lock[block_type][index]);
     IdleNode* node = g_info->idle_list[block_type][index];
-    if (!node) {
+    if (NULL == node) {
         BAIDU_SCOPED_LOCK(g_info->extend_lock);
         node = g_info->idle_list[block_type][index];
-        if (!node) {
-            // There is no block left, extend a new region
-            if (!ExtendBlockPool(FLAGS_rdma_memory_pool_increase_size_mb,
-                                 block_type)) {
+        if (NULL == node && NULL != g_info->expansion_list[block_type][index]) 
{
+            MoveExpansionList2EmptyIdleList(block_type, index);
+            node = g_info->idle_list[block_type][index];
+        }
+        if (NULL == node) {
+            // There is no block left, extend a new region.
+            if (!ExtendBlockPool(FLAGS_rdma_memory_pool_increase_size_mb, 
block_type)) {
                 LOG_EVERY_SECOND(ERROR) << "Fail to extend new region. "
                                         << "You can set the size of memory 
pool larger. "
                                         << "Refer to the help message of these 
flags: "
                                         << "rdma_memory_pool_initial_size_mb, "
                                         << "rdma_memory_pool_increase_size_mb, 
"
                                         << "rdma_memory_pool_max_regions.";
-                if (locked) {
-                    g_dump_mutex->unlock();
-                }
                 return NULL;
             }
+            MoveExpansionList2EmptyIdleList(block_type, index);

Review Comment:
   Missing test coverage for the new dynamic expansion functionality with 
multiple buckets (`FLAGS_rdma_memory_pool_buckets > 1`). The PR description 
states that the main problem being solved is "When 
`FLAGS_rdma_memory_pool_buckets > 1`, the block pool cannot be dynamically 
expanded." However, the existing `extend` test only uses 1 bucket.
   
   Consider adding a test that:
   1. Sets `FLAGS_rdma_memory_pool_buckets` to a value > 1 (e.g., 4)
   2. Verifies that dynamic expansion works correctly with multiple buckets
   3. Ensures that each bucket correctly retrieves memory from its 
corresponding index in `expansion_list`



##########
src/brpc/rdma/block_pool.cpp:
##########
@@ -222,24 +210,27 @@ static void* ExtendBlockPool(size_t region_size, int 
block_type) {
     return ExtendBlockPoolImpl(region_base, region_size, block_type);
 }
 
-void* ExtendBlockPoolByUser(void* region_base, size_t region_size,
-                            int block_type) {
-    if (FLAGS_rdma_memory_pool_user_specified_memory == false) {
+void* ExtendBlockPoolByUser(void* region_base, size_t region_size, int 
block_type) {
+    auto region_base_guard = butil::MakeScopeGuard([region_base]() {
+        free(region_base);
+    });
+
+    if (!FLAGS_rdma_memory_pool_user_specified_memory) {
         LOG_EVERY_SECOND(ERROR) << "User extend memory is disabled";
         return NULL;
     }
     if (reinterpret_cast<uintptr_t>(region_base) % 4096 != 0) {
         LOG_EVERY_SECOND(ERROR) << "region_base must be 4096 aligned";
+        errno = EINVAL;
         return NULL;
     }
 
-    uint64_t index = butil::fast_rand() % g_buckets;
-    BAIDU_SCOPED_LOCK(*g_info->lock[block_type][index]);
-    BAIDU_SCOPED_LOCK(g_info->extend_lock);
     region_size =
         region_size * BYTES_IN_MB / g_block_size[block_type] / g_buckets;
     region_size *= g_block_size[block_type] * g_buckets;
 
+    region_base_guard.dismiss();

Review Comment:
   The scope guard dismissal occurs too early, before input validation for 
`block_type` is performed. If `block_type` is invalid (e.g., < 0 or >= 
BLOCK_SIZE_COUNT), the code could crash in `ExtendBlockPoolImpl` when accessing 
arrays like `g_block_size[block_type]`, `g_info->expansion_list[block_type]`, 
etc.
   
   The `region_base_guard.dismiss()` call should be moved to just before the 
return statement after all validation has passed, or the scope guard should not 
be used at all (see the related issue about calling `free()` on user-provided 
memory).



##########
src/brpc/rdma/block_pool.cpp:
##########
@@ -129,36 +130,20 @@ uint32_t GetRegionId(const void* buf) {
     return r->id;
 }
 
-// When both rdma_memory_pool_max_regions and rdma_memory_pool_buckets are
-// greater than 1, dynamic memory expansion may cause concurrent modification
-// issues in the memory linked list due to lock contention problems. To address
-// this, we increase the region_num count for each block_type. Dynamic memory
-// expansion is only permitted when both of the following conditions are met:
-// rdma_memory_pool_buckets equals 1
-// g_info->region_num[block_type] is less than 1
-static bool CanExtendBlockRuntime(int block_type) {
-    return FLAGS_rdma_memory_pool_buckets == 1 ||
-           g_info->region_num[block_type] < 1;
-}
-
-static void* ExtendBlockPoolImpl(void* region_base, size_t region_size,
-                                 int block_type) {
-    if (CanExtendBlockRuntime(block_type) == false) {
-        LOG(INFO) << "Runtime extend memory only support one bucket or region "
-                     "num is zero for per block_type";
+static void* ExtendBlockPoolImpl(void* region_base, size_t region_size, int 
block_type) {
+    auto region_base_guard = butil::MakeScopeGuard([region_base]() {
         free(region_base);
-        errno = ENOMEM;
-        return NULL;
-    }
+    });

Review Comment:
   Memory management bug: The scope guard in `ExtendBlockPoolImpl` will call 
`free()` on `region_base` if the function fails. This is problematic because 
`ExtendBlockPoolImpl` is called from both:
   1. `ExtendBlockPool` - where memory is allocated with `posix_memalign` (line 
205)
   2. `ExtendBlockPoolByUser` - where memory is provided by the user
   
   When called from `ExtendBlockPoolByUser`, the user-provided memory (e.g., 
from hugepages or custom pools) should not be freed by this function. Calling 
`free()` on non-malloc'd memory can cause crashes.
   
   Suggested fix: Remove the scope guard from `ExtendBlockPoolImpl` and let the 
caller manage memory cleanup. `ExtendBlockPool` should call `free()` if 
`ExtendBlockPoolImpl` fails.



##########
src/brpc/rdma/block_pool.cpp:
##########
@@ -557,7 +561,8 @@ void DumpMemoryPoolInfo(std::ostream& os) {
     for (int i = 0; i < BLOCK_SIZE_COUNT; ++i) {
         os << "\tFor block size " << GetBlockSize(i) << ":\n";
         for (size_t j = 0; j < g_buckets; ++j) {
-            os << "\t\tBucket " << j << ": " << g_info->idle_size[i][j] << 
"\n";
+            os << "\t\tBucket " << j << ": {" << g_info->idle_size[i][j]
+               << ", " << g_info->expansion_size[i][j] << "}\n";

Review Comment:
   [nitpick] Minor race condition in debug code: `DumpMemoryPoolInfo` reads 
`expansion_size[i][j]` without holding `extend_lock`, while 
`ExtendBlockPoolImpl` can modify it concurrently (lines 169-176). This could 
result in inconsistent or partially updated values in the debug output.
   
   While not critical since this is a monitoring function, consider either:
   1. Acquiring `extend_lock` during the dump (may impact performance)
   2. Documenting that the dump values may be inconsistent during concurrent 
modifications
   3. Using atomic operations for size tracking



##########
src/brpc/rdma/block_pool.cpp:
##########
@@ -332,66 +331,74 @@ bool InitBlockPool(RegisterCallback cb) {
     return false;
 }
 
+static void MoveExpansionList2EmptyIdleList(int block_type, size_t index) {
+    CHECK(NULL == g_info->idle_list[block_type][index]);

Review Comment:
   [nitpick] The `CHECK` assertion at line 335 will cause the program to crash 
if `idle_list[block_type][index]` is not NULL. However, this assumption may not 
always hold, especially in concurrent scenarios or if there's a bug in the 
calling logic.
   
   Consider either:
   1. Adding a more descriptive assertion message to aid debugging: `CHECK(NULL 
== g_info->idle_list[block_type][index]) << "Expected empty idle_list at index 
" << index;`
   2. Handling this case gracefully with a LOG and return, rather than crashing
   3. If this is truly an invariant that should never be violated, document why 
this must always be true in a comment above the function
   ```suggestion
       CHECK(NULL == g_info->idle_list[block_type][index])
           << "Expected idle_list[" << block_type << "][" << index << "] to be 
NULL, but got non-NULL. "
           << "This may indicate a logic error or concurrent modification.";
   ```



##########
src/brpc/rdma/block_pool.cpp:
##########
@@ -222,24 +210,27 @@ static void* ExtendBlockPool(size_t region_size, int 
block_type) {
     return ExtendBlockPoolImpl(region_base, region_size, block_type);
 }
 
-void* ExtendBlockPoolByUser(void* region_base, size_t region_size,
-                            int block_type) {
-    if (FLAGS_rdma_memory_pool_user_specified_memory == false) {
+void* ExtendBlockPoolByUser(void* region_base, size_t region_size, int 
block_type) {
+    auto region_base_guard = butil::MakeScopeGuard([region_base]() {
+        free(region_base);
+    });
+
+    if (!FLAGS_rdma_memory_pool_user_specified_memory) {
         LOG_EVERY_SECOND(ERROR) << "User extend memory is disabled";
         return NULL;
     }
     if (reinterpret_cast<uintptr_t>(region_base) % 4096 != 0) {
         LOG_EVERY_SECOND(ERROR) << "region_base must be 4096 aligned";
+        errno = EINVAL;
         return NULL;
     }
 
-    uint64_t index = butil::fast_rand() % g_buckets;
-    BAIDU_SCOPED_LOCK(*g_info->lock[block_type][index]);
-    BAIDU_SCOPED_LOCK(g_info->extend_lock);
     region_size =
         region_size * BYTES_IN_MB / g_block_size[block_type] / g_buckets;
     region_size *= g_block_size[block_type] * g_buckets;
 
+    region_base_guard.dismiss();
+    BAIDU_SCOPED_LOCK(g_info->extend_lock);
     return ExtendBlockPoolImpl(region_base, region_size, block_type);
 }

Review Comment:
   Missing test coverage for `ExtendBlockPoolByUser` function. The PR 
description mentions fixing issues with user-specified memory 
(`FLAGS_rdma_memory_pool_user_specified_memory = true`), but there are no tests 
covering this functionality.
   
   Consider adding tests that:
   1. Test `ExtendBlockPoolByUser` with valid aligned memory
   2. Test the alignment validation (line 222-226)
   3. Test the interaction with multiple buckets
   4. Test error cases (e.g., max regions reached, invalid block_type)



##########
src/brpc/rdma/block_pool.cpp:
##########
@@ -222,24 +210,27 @@ static void* ExtendBlockPool(size_t region_size, int 
block_type) {
     return ExtendBlockPoolImpl(region_base, region_size, block_type);
 }
 
-void* ExtendBlockPoolByUser(void* region_base, size_t region_size,
-                            int block_type) {
-    if (FLAGS_rdma_memory_pool_user_specified_memory == false) {
+void* ExtendBlockPoolByUser(void* region_base, size_t region_size, int 
block_type) {
+    auto region_base_guard = butil::MakeScopeGuard([region_base]() {
+        free(region_base);
+    });

Review Comment:
   [nitpick] The scope guard at lines 214-216 is unnecessary and confusing. 
Since `region_base_guard.dismiss()` is always called at line 232 before any 
early returns, the scope guard will never execute its cleanup function. This 
adds complexity without providing any benefit.
   
   The scope guard should be removed entirely. Memory cleanup is the user's 
responsibility since they provided the memory.



##########
src/brpc/rdma/block_pool.cpp:
##########
@@ -222,24 +210,27 @@ static void* ExtendBlockPool(size_t region_size, int 
block_type) {
     return ExtendBlockPoolImpl(region_base, region_size, block_type);

Review Comment:
   Race condition: `ExtendBlockPool` calls `ExtendBlockPoolImpl` without 
holding `extend_lock`, but `ExtendBlockPoolImpl` modifies `expansion_list` 
(lines 169-176). Meanwhile, `AllocBlockFrom` reads and modifies 
`expansion_list` while holding `extend_lock` (line 370).
   
   This can lead to concurrent unsynchronized access to `expansion_list` and 
`expansion_size`, potentially causing data corruption or crashes.
   
   Fix: Acquire `extend_lock` before calling `ExtendBlockPoolImpl`:
   ```cpp
   BAIDU_SCOPED_LOCK(g_info->extend_lock);
   return ExtendBlockPoolImpl(region_base, region_size, block_type);
   ```



##########
src/brpc/rdma/block_pool.cpp:
##########
@@ -222,24 +210,27 @@ static void* ExtendBlockPool(size_t region_size, int 
block_type) {
     return ExtendBlockPoolImpl(region_base, region_size, block_type);
 }
 
-void* ExtendBlockPoolByUser(void* region_base, size_t region_size,
-                            int block_type) {
-    if (FLAGS_rdma_memory_pool_user_specified_memory == false) {
+void* ExtendBlockPoolByUser(void* region_base, size_t region_size, int 
block_type) {
+    auto region_base_guard = butil::MakeScopeGuard([region_base]() {
+        free(region_base);
+    });
+
+    if (!FLAGS_rdma_memory_pool_user_specified_memory) {
         LOG_EVERY_SECOND(ERROR) << "User extend memory is disabled";
         return NULL;
     }
     if (reinterpret_cast<uintptr_t>(region_base) % 4096 != 0) {
         LOG_EVERY_SECOND(ERROR) << "region_base must be 4096 aligned";
+        errno = EINVAL;
         return NULL;
     }
 
-    uint64_t index = butil::fast_rand() % g_buckets;
-    BAIDU_SCOPED_LOCK(*g_info->lock[block_type][index]);
-    BAIDU_SCOPED_LOCK(g_info->extend_lock);
     region_size =
         region_size * BYTES_IN_MB / g_block_size[block_type] / g_buckets;
     region_size *= g_block_size[block_type] * g_buckets;

Review Comment:
   Missing validation for `region_size` before regularization. If the user 
provides a `region_size` that's too small, the regularization at lines 228-230 
could result in `region_size = 0`, leading to division by zero or allocation of 
useless zero-length nodes.
   
   Add validation similar to `ExtendBlockPool`:
   ```cpp
   if (region_size < 1) {
       LOG_EVERY_SECOND(ERROR) << "region_size must be at least 1 MB";
       errno = EINVAL;
       return NULL;
   }
   ```
   
   Additionally, consider validating after regularization to ensure the result 
is non-zero:
   ```cpp
   region_size = region_size * BYTES_IN_MB / g_block_size[block_type] / 
g_buckets;
   region_size *= g_block_size[block_type] * g_buckets;
   if (region_size == 0) {
       LOG_EVERY_SECOND(ERROR) << "region_size too small after regularization";
       errno = EINVAL;
       return NULL;
   }
   ```



##########
src/brpc/rdma/block_pool.cpp:
##########
@@ -222,24 +210,27 @@ static void* ExtendBlockPool(size_t region_size, int 
block_type) {
     return ExtendBlockPoolImpl(region_base, region_size, block_type);
 }
 
-void* ExtendBlockPoolByUser(void* region_base, size_t region_size,
-                            int block_type) {
-    if (FLAGS_rdma_memory_pool_user_specified_memory == false) {
+void* ExtendBlockPoolByUser(void* region_base, size_t region_size, int 
block_type) {

Review Comment:
   Missing input validation for `block_type`. The parameter should be validated 
to ensure it's within the valid range [0, BLOCK_SIZE_COUNT) before being used 
to index arrays.
   
   Add validation like:
   ```cpp
   if (block_type < 0 || block_type >= BLOCK_SIZE_COUNT) {
       LOG_EVERY_SECOND(ERROR) << "Invalid block_type: " << block_type;
       errno = EINVAL;
       return NULL;
   }
   ```
   ```suggestion
   void* ExtendBlockPoolByUser(void* region_base, size_t region_size, int 
block_type) {
       if (block_type < 0 || block_type >= BLOCK_SIZE_COUNT) {
           LOG_EVERY_SECOND(ERROR) << "Invalid block_type: " << block_type;
           errno = EINVAL;
           return NULL;
       }
   ```



-- 
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]

Reply via email to