Skip to content

Commit 328c6b7

Browse files
committed
fix review
fix review comment add new ut
1 parent 226a6f4 commit 328c6b7

File tree

3 files changed

+209
-113
lines changed

3 files changed

+209
-113
lines changed

src/butil/iobuf.cpp

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -324,33 +324,32 @@ IOBuf::Block* share_tls_block() {
324324
void release_tls_block_chain(IOBuf::Block* b) {
325325
TLSData& tls_data = g_tls_data;
326326
size_t n = 0;
327+
size_t n_hit_threshold = 0;
327328
if (tls_data.num_blocks >= max_blocks_per_thread()) {
328329
do {
329330
++n;
330331
IOBuf::Block* const saved_next = b->u.portal_next;
331-
b->dec_ref();
332+
// If b is already cached in TLS, dec_ref() would drop a reference
333+
// still owned by the TLS list and leave a dangling pointer behind.
334+
if (!is_in_tls_block_chain(tls_data.block_head, b)) {
335+
b->dec_ref();
336+
++n_hit_threshold;
337+
}
332338
b = saved_next;
333339
} while (b);
334-
g_num_hit_tls_threshold.fetch_add(n, butil::memory_order_relaxed);
340+
g_num_hit_tls_threshold.fetch_add(
341+
n_hit_threshold, butil::memory_order_relaxed);
335342
return;
336343
}
337-
IOBuf::Block* const old_head = tls_data.block_head;
338344
IOBuf::Block* first_b = b;
339345
IOBuf::Block* last_b = NULL;
340346
do {
341347
++n;
342348
CHECK(!b->full());
343-
// If any block in the incoming chain is already the TLS block_head,
344-
// linking last_b->portal_next = block_head would create a cycle:
345-
// - Single-block chain [B] where B == block_head:
346-
// last_b->portal_next = B, i.e. B->portal_next = B (self-loop)
347-
// - Multi-block chain [A -> B] where A == block_head:
348-
// last_b(B)->portal_next = A, creating A -> B -> A (cycle)
349-
// Return early before any state is modified so that num_blocks stays
350-
// consistent with the actual list length (remove_tls_block_chain
351-
// verifies this with CHECK_EQ at thread exit).
352-
// See https://github.com/apache/brpc/issues/3243
353-
if (b == old_head) {
349+
// Guard against overlap with any node already cached in TLS, not just
350+
// block_head. Otherwise returning X into H -> X would create a
351+
// 2-node cycle X -> H -> X and hang later list traversal.
352+
if (is_in_tls_block_chain(tls_data.block_head, b)) {
354353
return;
355354
}
356355
if (b->u.portal_next == NULL) {
@@ -359,7 +358,7 @@ void release_tls_block_chain(IOBuf::Block* b) {
359358
}
360359
b = b->u.portal_next;
361360
} while (true);
362-
last_b->u.portal_next = old_head;
361+
last_b->u.portal_next = tls_data.block_head;
363362
tls_data.block_head = first_b;
364363
tls_data.num_blocks += n;
365364
if (!tls_data.registered) {

src/butil/iobuf_inl.h

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -603,26 +603,34 @@ void remove_tls_block_chain();
603603

604604
IOBuf::Block* acquire_tls_block();
605605

606+
inline bool is_in_tls_block_chain(IOBuf::Block* head, IOBuf::Block* b) {
607+
for (IOBuf::Block* p = head; p != NULL; p = p->u.portal_next) {
608+
if (p == b) {
609+
return true;
610+
}
611+
}
612+
return false;
613+
}
614+
606615
// Return one block to TLS.
607616
inline void release_tls_block(IOBuf::Block* b) {
608617
if (!b) {
609618
return;
610619
}
611620
TLSData *tls_data = get_g_tls_data();
621+
// Guard against duplicate return anywhere in the TLS list. Checking only
622+
// block_head misses cases like H -> X where returning X again would create
623+
// a 2-node cycle X -> H -> X. The TLS list is short (soft-limited), so a
624+
// linear scan is cheap here.
625+
if (is_in_tls_block_chain(tls_data->block_head, b)) {
626+
return;
627+
}
612628
if (b->full()) {
613629
b->dec_ref();
614630
} else if (tls_data->num_blocks >= max_blocks_per_thread()) {
615631
b->dec_ref();
616632
// g_num_hit_tls_threshold.fetch_add(1, butil::memory_order_relaxed);
617633
inc_g_num_hit_tls_threshold();
618-
} else if (b == tls_data->block_head) {
619-
// b is already at the head of the TLS free list. Re-inserting it
620-
// would execute `b->portal_next = block_head`, i.e. `b->portal_next = b`,
621-
// creating a single-node self-loop. Any later traversal of the TLS
622-
// chain (remove_tls_block_chain at thread exit, share_tls_block, etc.)
623-
// would spin forever. Skip the duplicate return.
624-
// See https://github.com/apache/brpc/issues/3243
625-
return;
626634
} else {
627635
b->u.portal_next = tls_data->block_head;
628636
tls_data->block_head = b;

0 commit comments

Comments
 (0)