Skip to content

Commit 9910a75

Browse files
gophergogogophergogo
authored andcommitted
Stop capturing stack-local PendingConnection in pool timeout timer (#222)
ConnectionPoolImpl::createNewConnection constructed PendingConnection on the caller's stack, handed its address to the timeout-timer lambda, and only then std::move-pushed the entry onto pending_connections_. The captured pointer dangled as soon as createNewConnection returned, so the timer callback touched freed memory when it fired. Emplace an empty slot on pending_connections_ first and bind the timer capture to that list element. std::list guarantees per-element address stability across push/erase of other elements, which is the invariant the capture relies on. onConnectionTimeout previously compacted the list via std::remove_if plus list::erase. The non-member remove_if moves elements to shuffle the erased one to the end — which would move the entry whose timer lambda is currently executing on top of itself. Switch to std::list::remove_if (member) so the node is unlinked in place, and identify it by address rather than unique_ptr equality to avoid accidentally matching an unrelated entry.
1 parent dce6d40 commit 9910a75

1 file changed

Lines changed: 18 additions & 12 deletions

File tree

src/network/connection_manager_impl.cc

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -396,12 +396,20 @@ void ConnectionPoolImpl::createNewConnection(Callbacks& callbacks) {
396396
return;
397397
}
398398

399-
// Create pending connection entry
400-
PendingConnection pending;
399+
// Emplace an empty slot in the list first so we have a stable address to
400+
// bind the timeout-timer callback to. std::list keeps per-element
401+
// addresses stable across push_back/erase of *other* elements, which is
402+
// the invariant the timer's capture depends on. Taking the address of a
403+
// stack-local PendingConnection and capturing it by reference would
404+
// dangle as soon as createNewConnection() returned, since the original
405+
// would be destroyed even after std::move into the list.
406+
pending_connections_.emplace_back();
407+
PendingConnection& pending = pending_connections_.back();
401408
pending.connection = std::move(connection);
402409
pending.callbacks = &callbacks;
403410

404-
// Set connection timeout
411+
// Set connection timeout. The lambda now captures the list element's
412+
// stable address, not a stack local.
405413
if (config_.connection_timeout.has_value()) {
406414
pending.timeout_timer = dispatcher_.createTimer(
407415
[this, &pending]() { onConnectionTimeout(pending); });
@@ -413,8 +421,6 @@ void ConnectionPoolImpl::createNewConnection(Callbacks& callbacks) {
413421

414422
// Start connection
415423
pending.connection->connect();
416-
417-
pending_connections_.push_back(std::move(pending));
418424
}
419425

420426
void ConnectionPoolImpl::assignConnection(ClientConnection& connection,
@@ -435,13 +441,13 @@ void ConnectionPoolImpl::onConnectionTimeout(PendingConnection& pending) {
435441
pending.callbacks->onPoolFailure(PoolFailureReason::Timeout,
436442
"Connection timeout");
437443

438-
// Remove from pending
439-
pending_connections_.erase(
440-
std::remove_if(pending_connections_.begin(), pending_connections_.end(),
441-
[&pending](const PendingConnection& p) {
442-
return p.connection == pending.connection;
443-
}),
444-
pending_connections_.end());
444+
// Identify the list node by address, not by unique_ptr equality, and use
445+
// std::list::remove_if (not the non-member std::remove_if) so the node is
446+
// unlinked in-place. The non-member overload moves elements around to
447+
// compact them — which would move the entry whose timer lambda is
448+
// currently executing on top of us.
449+
pending_connections_.remove_if(
450+
[&pending](const PendingConnection& p) { return &p == &pending; });
445451
}
446452

447453
void ConnectionPoolImpl::checkIdleCallbacks() {

0 commit comments

Comments
 (0)