Fix use-after-free crash in getBrokerConsumerStatsAsync when connection is closed#537
Fix use-after-free crash in getBrokerConsumerStatsAsync when connection is closed#537dragonls wants to merge 1 commit intoapache:mainfrom
getBrokerConsumerStatsAsync when connection is closed#537Conversation
| }); | ||
| auto indexPtr = std::make_shared<std::atomic<size_t>>(0); | ||
| auto weakSelf = weak_from_this(); | ||
| consumers_.forEachValue([weakSelf, latchPtr, statsPtr, indexPtr, callback](const ConsumerImplPtr& consumer) { |
There was a problem hiding this comment.
The callback of forEachValue is immediately executed in the current method.
pulsar-client-cpp/lib/SynchronizedHashMap.h
Lines 95 to 99 in 0b893a1
so I believe this patch does not fix the actual issue
There was a problem hiding this comment.
Yes, but in consumers_.forEachValue, it also calls consumer->getBrokerConsumerStatsAsync, which passed the weakSelf into the callback. The callback is executed later.
There was a problem hiding this comment.
The lambda of getBrokerConsumerStatsAsync captures all variables by value:
consumer->getBrokerConsumerStatsAsync([this, weakSelf, latchPtr, statsPtr, index, callback](|
I believe I've figured out the cause, let me create a new fix |
Nice, looking forward to it. |
|
Replace this PR by #538 |
Fixes #536
Motivation
A use-after-free crash occurs in
MultiTopicsConsumerImpl::getBrokerConsumerStatsAsync()when the underlying connection is closed while an asynchronous consumer stats request is pending.The root cause has two aspects:
Reference capture of stack variables: The lambda captures local variables (
latchPtr,statsPtr,i) by reference. If the callback is invoked after the function returns, these references become dangling.Direct
thiscapture in lambda: Both the outer and inner lambdas capturethisdirectly. Although there's aweakSelf.lock()check, the actual function callhandleGetConsumerStats(...)is invoked through the capturedthispointer (implicitthis->handleGetConsumerStats(...)), not throughself->. WhenClientConnection::close()callssetFailed()on pending promises, it synchronously triggers the registered callbacks, potentially causing use-after-free if theMultiTopicsConsumerImplhas been destroyed.Modifications
Use
shared_ptr<atomic>for index: Replace stack variablesize_t iwithstd::shared_ptr<std::atomic<size_t>>for thread-safe indexing and proper lifetime management.Create
weakSelfoutside lambdas: Moveweak_from_this()call outside to avoid capturingthisin the outer lambda.Remove
thiscapture from all lambdas: Neither the outer nor inner lambda capturesthisanymore.Call member function through
self->: ChangehandleGetConsumerStats(...)toself->handleGetConsumerStats(...)to ensure the call goes through theshared_ptr, not the rawthispointer.Verifying this change
(Please pick either of the following options)
This change is a trivial rework / code cleanup without any test coverage.
The crash scenario is difficult to reproduce in unit tests as it requires precise timing of connection closure during async operations. However, the fix follows the standard C++ weak_ptr/shared_ptr pattern for preventing use-after-free in asynchronous callbacks.
Documentation
doc-required(Your PR needs to update docs and you will update later)
doc-not-needed(Please explain why)
doc(Your PR contains doc changes)
doc-complete(Docs have been already added)