Skip to content

Commit 0e03184

Browse files
authored
async_tcp_client: remove callbacks if connection was not closed (envoyproxy#35410)
When the ``AsyncTcpClient`` is being destroyed but it also has an active client connection, there's a crash since during the instance destruction, the ``ClientConnection`` object would also be destroyed, causing ``raiseEvent`` to be called back to ``AsyncTcpClient`` while it is being destroyed Caught with the following stack trace: ``` Caught Segmentation fault, suspect faulting address 0x0 Backtrace (use tools/stack_decode.py to get line numbers): Envoy version: ee8c765a07037033766ea556c032120b497152b3/1.27.0/Clean/RELEASE/BoringSSL #0: __restore_rt [0x7d80ab903420] #1: Envoy::Extensions::AccessLoggers::Fluentd::FluentdAccessLoggerImpl::onEvent() [0x58313528746b] #2: Envoy::Tcp::AsyncTcpClientImpl::onEvent() [0x5831359da00a] #3: Envoy::Network::ConnectionImplBase::raiseConnectionEvent() [0x583135f0521d] envoyproxy#4: Envoy::Network::ConnectionImpl::raiseEvent() [0x583135e9fed9] envoyproxy#5: Envoy::Network::ConnectionImpl::closeSocket() [0x583135e9f90c] envoyproxy#6: Envoy::Network::ConnectionImpl::close() [0x583135e9e54c] envoyproxy#7: Envoy::Network::ConnectionImpl::~ConnectionImpl() [0x583135e9de5c] envoyproxy#8: Envoy::Network::ClientConnectionImpl::~ClientConnectionImpl() [0x5831355fd25e] envoyproxy#9: Envoy::Tcp::AsyncTcpClientImpl::~AsyncTcpClientImpl() [0x5831359da247] envoyproxy#10: Envoy::Extensions::AccessLoggers::Fluentd::FluentdAccessLoggerImpl::~FluentdAccessLoggerImpl() [0x583135289350] envoyproxy#11: Envoy::Extensions::AccessLoggers::Fluentd::FluentdAccessLog::ThreadLocalLogger::~ThreadLocalLogger() [0x58313528adbf] envoyproxy#12: Envoy::ThreadLocal::InstanceImpl::shutdownThread() [0x58313560373a] envoyproxy#13: Envoy::Server::WorkerImpl::threadRoutine() [0x583135630c0a] envoyproxy#14: Envoy::Thread::ThreadImplPosix::ThreadImplPosix()::{lambda()#1}::__invoke() [0x5831364e88d5] envoyproxy#15: start_thread [0x7d80ab8f7609] ``` Risk Level: low Testing: unit tests Docs Changes: none Release Notes: none Platform Specific Features: none --------- Signed-off-by: Ohad Vano <ohadvano@gmail.com>
1 parent 770fa7b commit 0e03184

File tree

6 files changed

+79
-19
lines changed

6 files changed

+79
-19
lines changed

source/common/tcp/async_tcp_client_impl.cc

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ AsyncTcpClientImpl::AsyncTcpClientImpl(Event::Dispatcher& dispatcher,
2424
connect_timer_(dispatcher.createTimer([this]() { onConnectTimeout(); })),
2525
enable_half_close_(enable_half_close) {}
2626

27+
AsyncTcpClientImpl::~AsyncTcpClientImpl() {
28+
if (connection_) {
29+
connection_->removeConnectionCallbacks(*this);
30+
}
31+
32+
close(Network::ConnectionCloseType::NoFlush);
33+
}
34+
2735
bool AsyncTcpClientImpl::connect() {
2836
if (connection_) {
2937
return false;
@@ -69,7 +77,8 @@ void AsyncTcpClientImpl::onConnectTimeout() {
6977
}
7078

7179
void AsyncTcpClientImpl::close(Network::ConnectionCloseType type) {
72-
if (connection_) {
80+
if (connection_ && !closing_) {
81+
closing_ = true;
7382
connection_->close(type);
7483
}
7584
}
@@ -127,6 +136,7 @@ void AsyncTcpClientImpl::onEvent(Network::ConnectionEvent event) {
127136
detected_close_ = connection_->detectedCloseType();
128137
}
129138

139+
closing_ = false;
130140
dispatcher_.deferredDelete(std::move(connection_));
131141
if (callbacks_) {
132142
callbacks_->onEvent(event);

source/common/tcp/async_tcp_client_impl.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ class AsyncTcpClientImpl : public AsyncTcpClient,
2828
AsyncTcpClientImpl(Event::Dispatcher& dispatcher,
2929
Upstream::ThreadLocalCluster& thread_local_cluster,
3030
Upstream::LoadBalancerContext* context, bool enable_half_close);
31+
~AsyncTcpClientImpl();
3132

3233
void close(Network::ConnectionCloseType type) override;
3334

@@ -106,6 +107,7 @@ class AsyncTcpClientImpl : public AsyncTcpClient,
106107
Event::TimerPtr connect_timer_;
107108
AsyncTcpClientCallbacks* callbacks_{};
108109
Network::DetectedCloseType detected_close_{Network::DetectedCloseType::Normal};
110+
bool closing_{false};
109111
bool connected_{false};
110112
bool enable_half_close_{false};
111113
};

test/common/tcp/async_tcp_client_impl_test.cc

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,15 @@ using testing::Return;
1818
namespace Envoy {
1919
namespace Tcp {
2020

21+
class CustomMockClientConnection : public Network::MockClientConnection {
22+
public:
23+
~CustomMockClientConnection() {
24+
if (state_ != Connection::State::Closed) {
25+
raiseEvent(Network::ConnectionEvent::LocalClose);
26+
}
27+
};
28+
};
29+
2130
class AsyncTcpClientImplTest : public Event::TestUsingSimulatedTime, public testing::Test {
2231
public:
2332
AsyncTcpClientImplTest() = default;
@@ -32,7 +41,7 @@ class AsyncTcpClientImplTest : public Event::TestUsingSimulatedTime, public test
3241
}
3342

3443
void expectCreateConnection(bool trigger_connected = true) {
35-
connection_ = new NiceMock<Network::MockClientConnection>();
44+
connection_ = new NiceMock<CustomMockClientConnection>();
3645
Upstream::MockHost::MockCreateConnectionData conn_info;
3746
connection_->streamInfo().setAttemptCount(1);
3847
conn_info.connection_ = connection_;
@@ -59,7 +68,7 @@ class AsyncTcpClientImplTest : public Event::TestUsingSimulatedTime, public test
5968
NiceMock<Event::MockTimer>* connect_timer_;
6069
NiceMock<Event::MockDispatcher> dispatcher_;
6170
NiceMock<Upstream::MockClusterManager> cluster_manager_;
62-
Network::MockClientConnection* connection_{};
71+
CustomMockClientConnection* connection_{};
6372

6473
NiceMock<Tcp::AsyncClient::MockAsyncTcpClientCallbacks> callbacks_;
6574
};

test/integration/filters/test_network_async_tcp_filter.cc

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ class TestNetworkAsyncTcpFilter : public Network::ReadFilter {
4141
const test::integration::filters::TestNetworkAsyncTcpFilterConfig& config,
4242
Stats::Scope& scope, Upstream::ClusterManager& cluster_manager)
4343
: stats_(generateStats("test_network_async_tcp_filter", scope)),
44-
cluster_name_(config.cluster_name()), cluster_manager_(cluster_manager) {
44+
cluster_name_(config.cluster_name()), kill_after_on_data_(config.kill_after_on_data()),
45+
cluster_manager_(cluster_manager) {
4546
const auto thread_local_cluster = cluster_manager_.getThreadLocalCluster(cluster_name_);
4647
options_ = std::make_shared<Tcp::AsyncTcpClientOptions>(true);
4748
if (thread_local_cluster != nullptr) {
@@ -60,6 +61,11 @@ class TestNetworkAsyncTcpFilter : public Network::ReadFilter {
6061
data.length());
6162
client_->write(data, end_stream);
6263

64+
if (kill_after_on_data_) {
65+
Tcp::AsyncTcpClient* c1 = client_.release();
66+
delete c1;
67+
}
68+
6369
return Network::FilterStatus::StopIteration;
6470
}
6571

@@ -166,6 +172,7 @@ class TestNetworkAsyncTcpFilter : public Network::ReadFilter {
166172
TestNetworkAsyncTcpFilterStats stats_;
167173
Tcp::AsyncTcpClientPtr client_;
168174
absl::string_view cluster_name_;
175+
bool kill_after_on_data_;
169176
std::unique_ptr<RequestAsyncCallbacks> request_callbacks_;
170177
std::unique_ptr<DownstreamCallbacks> downstream_callbacks_;
171178
Upstream::ClusterManager& cluster_manager_;

test/integration/filters/test_network_async_tcp_filter.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ package test.integration.filters;
44

55
message TestNetworkAsyncTcpFilterConfig {
66
string cluster_name = 1;
7+
bool kill_after_on_data = 2;
78
}

test/integration/tcp_async_client_integration_test.cc

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#include "test/integration/filters/test_network_async_tcp_filter.pb.h"
12
#include "test/integration/integration.h"
23

34
#include "gtest/gtest.h"
@@ -16,15 +17,37 @@ class TcpAsyncClientIntegrationTest : public testing::TestWithParam<Network::Add
1617
typed_config:
1718
"@type": type.googleapis.com/test.integration.filters.TestNetworkAsyncTcpFilterConfig
1819
cluster_name: cluster_0
19-
)EOF")) {}
20+
)EOF")) {
21+
enableHalfClose(true);
22+
}
23+
24+
void init(bool kill_after_on_data = false) {
25+
const std::string yaml = fmt::format(R"EOF(
26+
cluster_name: cluster_0
27+
kill_after_on_data: {}
28+
)EOF",
29+
kill_after_on_data ? "true" : "false");
30+
31+
config_helper_.addConfigModifier(
32+
[&](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void {
33+
test::integration::filters::TestNetworkAsyncTcpFilterConfig proto_config;
34+
TestUtility::loadFromYaml(yaml, proto_config);
35+
36+
auto* listener = bootstrap.mutable_static_resources()->mutable_listeners(0);
37+
auto* filter_chain = listener->mutable_filter_chains(0);
38+
auto* filter = filter_chain->mutable_filters(0);
39+
filter->mutable_typed_config()->PackFrom(proto_config);
40+
});
41+
42+
BaseIntegrationTest::initialize();
43+
}
2044
};
2145

2246
INSTANTIATE_TEST_SUITE_P(IpVersions, TcpAsyncClientIntegrationTest,
2347
testing::ValuesIn(TestEnvironment::getIpVersionsForTest()));
2448

2549
TEST_P(TcpAsyncClientIntegrationTest, SingleRequest) {
26-
enableHalfClose(true);
27-
initialize();
50+
init();
2851

2952
std::string request("request");
3053
std::string response("response");
@@ -51,8 +74,7 @@ TEST_P(TcpAsyncClientIntegrationTest, SingleRequest) {
5174
}
5275

5376
TEST_P(TcpAsyncClientIntegrationTest, MultipleRequestFrames) {
54-
enableHalfClose(true);
55-
initialize();
77+
init();
5678

5779
std::string data_frame_1("data_frame_1");
5880
std::string data_frame_2("data_frame_2");
@@ -85,8 +107,7 @@ TEST_P(TcpAsyncClientIntegrationTest, MultipleRequestFrames) {
85107
}
86108

87109
TEST_P(TcpAsyncClientIntegrationTest, MultipleResponseFrames) {
88-
enableHalfClose(true);
89-
initialize();
110+
init();
90111

91112
std::string data_frame_1("data_frame_1");
92113
std::string response_1("response_1");
@@ -116,8 +137,7 @@ TEST_P(TcpAsyncClientIntegrationTest, Reconnect) {
116137
return;
117138
}
118139

119-
enableHalfClose(true);
120-
initialize();
140+
init();
121141

122142
IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("listener_0"));
123143
ASSERT_TRUE(tcp_client->write("hello1", false));
@@ -143,11 +163,24 @@ TEST_P(TcpAsyncClientIntegrationTest, Reconnect) {
143163
test_server_->waitForGaugeEq("cluster.cluster_0.upstream_cx_active", 0);
144164
}
145165

166+
TEST_P(TcpAsyncClientIntegrationTest, ClientTearDown) {
167+
init(true);
168+
169+
std::string request("request");
170+
171+
IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("listener_0"));
172+
ASSERT_TRUE(tcp_client->write(request, true));
173+
FakeRawConnectionPtr fake_upstream_connection;
174+
ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection));
175+
ASSERT_TRUE(fake_upstream_connection->waitForData(request.size()));
176+
177+
tcp_client->close();
178+
}
179+
146180
#if ENVOY_PLATFORM_ENABLE_SEND_RST
147181
// Test if RST close can be detected from downstream and upstream is closed by RST.
148182
TEST_P(TcpAsyncClientIntegrationTest, TestClientCloseRST) {
149-
enableHalfClose(true);
150-
initialize();
183+
init();
151184

152185
std::string request("request");
153186
std::string response("response");
@@ -178,8 +211,7 @@ TEST_P(TcpAsyncClientIntegrationTest, TestClientCloseRST) {
178211

179212
// Test if RST close can be detected from upstream.
180213
TEST_P(TcpAsyncClientIntegrationTest, TestUpstreamCloseRST) {
181-
enableHalfClose(true);
182-
initialize();
214+
init();
183215

184216
std::string request("request");
185217
std::string response("response");
@@ -212,8 +244,7 @@ TEST_P(TcpAsyncClientIntegrationTest, TestUpstreamCloseRST) {
212244
// the client. The behavior is different for windows, since RST support is literally supported for
213245
// unix like system, disabled the test for windows.
214246
TEST_P(TcpAsyncClientIntegrationTest, TestDownstremHalfClosedThenRST) {
215-
enableHalfClose(true);
216-
initialize();
247+
init();
217248

218249
std::string request("request");
219250
std::string response("response");

0 commit comments

Comments
 (0)