Skip to content

Commit bf00638

Browse files
committed
Remove stale tcp channels (#6421)
* Refs #24549: Test for removing stale channel_resources_ of finished clients Signed-off-by: Carlos Ferreira González <carlosferreira@eprosima.com> * Refs #24549: Improve TCP clean up Signed-off-by: Carlos Ferreira González <carlosferreira@eprosima.com> * Refs #24549: Ensure proper destruction in test Signed-off-by: Carlos Ferreira González <carlosferreira@eprosima.com> * Refs #24549: Protect smart wait from potential data race Signed-off-by: Carlos Ferreira González <carlosferreira@eprosima.com> * Refs #24549: Fix test Signed-off-by: Carlos Ferreira González <carlosferreira@eprosima.com> --------- Signed-off-by: Carlos Ferreira González <carlosferreira@eprosima.com> (cherry picked from commit 8188b41)
1 parent eb29b8a commit bf00638

3 files changed

Lines changed: 115 additions & 0 deletions

File tree

src/cpp/rtps/transport/TCPTransportInterface.cpp

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1221,6 +1221,55 @@ void TCPTransportInterface::perform_listen_operation(
12211221
}
12221222

12231223
EPROSIMA_LOG_INFO(RTCP, "End PerformListenOperation " << channel->locator());
1224+
1225+
// If we get here, the channel has been disconnected. We might need to clean it up if
1226+
// the remote endpoint is the one that initiated the disconnection.
1227+
// We only delete acceptor channels, as connect channels need to be kept in channel_resources_ to restart the connection
1228+
if (channel && channel->tcp_connection_type() == TCPChannelResource::TCPConnectionType::TCP_ACCEPT_TYPE)
1229+
{
1230+
// Defer the erase to io_context_ so the TCPChannelResource destructor runs off the listener thread that is about to exit.
1231+
// Weak_ptr is used to avoid keeping the channel alive if it has already been removed from the maps by another thread.
1232+
asio::post(io_context_, [this, channel_weak]()
1233+
{
1234+
auto ch = channel_weak.lock();
1235+
if (!ch)
1236+
{
1237+
return;
1238+
}
1239+
{
1240+
// Channel resources map case
1241+
std::unique_lock<std::mutex> scoped_lock(sockets_map_mutex_);
1242+
bool erased = false;
1243+
// There might be multiple entries with the same channel. Delete them all
1244+
for (auto it = channel_resources_.begin(); it != channel_resources_.end(); )
1245+
{
1246+
if (it->second == ch)
1247+
{
1248+
it = channel_resources_.erase(it);
1249+
erased = true;
1250+
}
1251+
else
1252+
{
1253+
++it;
1254+
}
1255+
}
1256+
if (erased)
1257+
{
1258+
return;
1259+
}
1260+
}
1261+
// Unbound channel resources map case
1262+
std::unique_lock<std::mutex> unbound_lock(unbound_map_mutex_);
1263+
auto it = std::find(unbound_channel_resources_.begin(),
1264+
unbound_channel_resources_.end(), ch);
1265+
if (it != unbound_channel_resources_.end())
1266+
{
1267+
unbound_channel_resources_.erase(it);
1268+
}
1269+
});
1270+
// Drop the listener's reference so the destructor cannot run on this thread
1271+
channel.reset();
1272+
}
12241273
}
12251274

12261275
bool TCPTransportInterface::read_body(

test/unittest/transport/TCPv4Tests.cpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2429,6 +2429,60 @@ TEST_F(TCPv4Tests, add_logical_port_on_send_resource_creation)
24292429
}
24302430
}
24312431

2432+
// This test verifies that TCP channels of type ACCEPT are correctly removed from the channel resources map when
2433+
// the channel is disabled by asio. This is the case when a client disconnects from the server. There is no need
2434+
// maintain the channel resource of a disconnected client because new connections will generate new channel resources
2435+
// and no unbind operation is needed at destruction time for a removed participant (eDisconnected channel).
2436+
TEST_F(TCPv4Tests, remove_stale_channel_resources_of_server)
2437+
{
2438+
// Server
2439+
TCPv4TransportDescriptor serverDescriptor;
2440+
serverDescriptor.add_listener_port(g_default_port);
2441+
MockTCPv4Transport server(serverDescriptor);
2442+
ASSERT_TRUE(server.init());
2443+
2444+
// Client
2445+
{
2446+
TCPv4TransportDescriptor clientDescriptor;
2447+
auto client = std::unique_ptr<TCPv4Transport>(new TCPv4Transport(clientDescriptor));
2448+
ASSERT_TRUE(client->init());
2449+
2450+
Locator_t outputLocator;
2451+
outputLocator.kind = LOCATOR_KIND_TCPv4;
2452+
IPLocator::setIPv4(outputLocator, 127, 0, 0, 1);
2453+
IPLocator::setPhysicalPort(outputLocator, g_default_port);
2454+
IPLocator::setLogicalPort(outputLocator, 7410);
2455+
2456+
SendResourceList send_resource_list;
2457+
ASSERT_TRUE(client->OpenOutputChannel(send_resource_list, outputLocator));
2458+
2459+
// Wait for the server to finish the BindConnectionRequest handshake
2460+
auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5);
2461+
while (server.get_channel_resources_size() == 0 &&
2462+
std::chrono::steady_clock::now() < deadline)
2463+
{
2464+
std::this_thread::sleep_for(std::chrono::milliseconds(20));
2465+
}
2466+
// Ensure there are channel resources in the server. Bind socket adds an entry per interface available, so there could be more than one.
2467+
ASSERT_GT(server.get_channel_resources_size(), 0u);
2468+
2469+
// Tear down the client: clean send_resource_list and then close the TCP socket.
2470+
send_resource_list.clear();
2471+
client.reset();
2472+
}
2473+
2474+
// Check that the server correctly removes the channel resource of type ACCEPT after the client disconnection
2475+
auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(10);
2476+
while (server.get_channel_resources_size() != 0 &&
2477+
std::chrono::steady_clock::now() < deadline)
2478+
{
2479+
std::this_thread::sleep_for(std::chrono::milliseconds(100));
2480+
}
2481+
EXPECT_EQ(server.get_channel_resources_size(), 0u);
2482+
EXPECT_EQ(server.get_unbound_channel_resources_size(), 0u);
2483+
}
2484+
2485+
24322486
void TCPv4Tests::HELPER_SetDescriptorDefaults()
24332487
{
24342488
descriptor.add_listener_port(g_default_port);

test/unittest/transport/mock/MockTCPv4Transport.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,23 @@ class MockTCPv4Transport : public TCPv4Transport
4040
return channel_resources_;
4141
}
4242

43+
size_t get_channel_resources_size() const
44+
{
45+
std::lock_guard<std::mutex> lock(sockets_map_mutex_);
46+
return channel_resources_.size();
47+
}
48+
4349
const std::vector<std::shared_ptr<TCPChannelResource>> get_unbound_channel_resources() const
4450
{
4551
return unbound_channel_resources_;
4652
}
4753

54+
size_t get_unbound_channel_resources_size() const
55+
{
56+
std::lock_guard<std::mutex> lock(unbound_map_mutex_);
57+
return unbound_channel_resources_.size();
58+
}
59+
4860
const std::vector<asio::ip::address_v4>& get_interface_whitelist() const
4961
{
5062
return interface_whitelist_;

0 commit comments

Comments
 (0)