Skip to content

Commit 68fe8be

Browse files
authored
[TCPServer] Close all filedescriptors on shutdown (UniversalRobots#432)
This fixes a bug where clients would not have been aware that the server went away, as we just closed the listen filedescriptor, but not the one attached to a client.
1 parent ac7a6a5 commit 68fe8be

2 files changed

Lines changed: 52 additions & 5 deletions

File tree

src/comm/tcp_server.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ TCPServer::~TCPServer()
5858
{
5959
URCL_LOG_DEBUG("Destroying TCPServer object.");
6060
shutdown();
61-
ur_close(listen_fd_);
6261
}
6362

6463
void TCPServer::init()
@@ -115,6 +114,13 @@ void TCPServer::shutdown()
115114
worker_thread_.join();
116115
URCL_LOG_DEBUG("Worker thread joined.");
117116
}
117+
118+
for (const auto& client_fd : client_fds_)
119+
{
120+
ur_close(client_fd);
121+
}
122+
ur_close(shutdown_socket);
123+
ur_close(listen_fd_);
118124
}
119125

120126
void TCPServer::bind(const size_t max_num_tries, const std::chrono::milliseconds reconnection_time)

tests/test_tcp_server.cpp

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,13 @@ class TCPServerTest : public ::testing::Test
6565
size_t read_chars = 99;
6666
while (read_chars > 0)
6767
{
68-
TCPSocket::read((uint8_t*)&character, 1, read_chars);
69-
result << character;
70-
if (character == '\n')
68+
if (TCPSocket::read((uint8_t*)&character, 1, read_chars))
7169
{
72-
break;
70+
result << character;
71+
if (character == '\n')
72+
{
73+
break;
74+
}
7375
}
7476
}
7577
return result.str();
@@ -333,6 +335,45 @@ TEST_F(TCPServerTest, check_address_already_in_use)
333335
EXPECT_THROW(comm::TCPServer test_server(12321, 2, std::chrono::milliseconds(500)), std::system_error);
334336
}
335337

338+
TEST_F(TCPServerTest, check_shutting_down_server_while_listening)
339+
{
340+
auto server = std::make_unique<comm::TCPServer>(port_);
341+
server->start();
342+
343+
// Use a client with a read timeout so we don't hang forever if the server doesn't shut down
344+
// properly.
345+
Client client(port_);
346+
timeval tv;
347+
tv.tv_sec = 10;
348+
tv.tv_usec = 0; // 100 ms
349+
client.setReceiveTimeout(tv);
350+
351+
// Start reading data with the client in a separate thread. This will have a blocking recv() call
352+
// that should be interrupted when the server is shut down. In that case the
353+
bool read_success = true;
354+
std::thread read_data_thread([&client, &read_success]() {
355+
while (read_success)
356+
{
357+
// As we aren't sending any data, this will block until the server is shut down and the
358+
// connection is closed. At that point, recv() should return an empty string, which we
359+
// interpret as a successful shutdown of the server.
360+
read_success = (client.recv() != "");
361+
}
362+
});
363+
364+
std::this_thread::sleep_for(std::chrono::milliseconds(100));
365+
366+
server.reset();
367+
368+
if (read_data_thread.joinable())
369+
{
370+
read_data_thread.join();
371+
}
372+
EXPECT_FALSE(read_success);
373+
// If the read just would have timeouted, the client state would still be connected.
374+
EXPECT_EQ(client.getState(), comm::SocketState::Disconnected);
375+
}
376+
336377
int main(int argc, char* argv[])
337378
{
338379
::testing::InitGoogleTest(&argc, argv);

0 commit comments

Comments
 (0)