Skip to content

Commit 9a12588

Browse files
committed
Robustify TCPServer implementation for thread safety and windows support
1 parent 3a272a7 commit 9a12588

5 files changed

Lines changed: 168 additions & 32 deletions

File tree

include/ur_client_library/comm/socket_t.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#pragma once
1818

19+
#include <system_error>
1920
#ifdef _WIN32
2021

2122
# define NOMINMAX
@@ -33,6 +34,7 @@
3334

3435
typedef SOCKET socket_t;
3536
typedef SSIZE_T ssize_t;
37+
typedef int socklen_t;
3638

3739
static inline int ur_setsockopt(socket_t s, int level, int optname, const void* optval, unsigned int optlen)
3840
{
@@ -64,3 +66,25 @@ typedef int socket_t;
6466
# define ur_close close
6567

6668
#endif // _WIN32
69+
70+
#ifndef MSG_NOSIGNAL
71+
# define MSG_NOSIGNAL 0
72+
#endif
73+
74+
inline std::system_error makeSocketError(const std::string& message)
75+
{
76+
#ifdef _WIN32
77+
return std::system_error(std::error_code(WSAGetLastError(), std::system_category()), message);
78+
#else
79+
return std::system_error(std::error_code(errno, std::generic_category()), message);
80+
#endif
81+
}
82+
83+
inline int getLastSocketError()
84+
{
85+
#ifdef _WIN32
86+
return WSAGetLastError();
87+
#else
88+
return errno;
89+
#endif
90+
}

include/ur_client_library/comm/tcp_server.h

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include <atomic>
3333
#include <chrono>
3434
#include <functional>
35+
#include <mutex>
3536
#include <thread>
3637
#include <vector>
3738

@@ -77,9 +78,14 @@ class TCPServer
7778
*
7879
* \param func Function handling the event information. The file descriptor created by the
7980
* connection event will be passed to the function.
81+
*
82+
* \note: For thread safety, this will block when there is an active callback around connection
83+
* (connection, disconnection, shutdown). Thus, trying to call setConnectCallback e.g. by a
84+
* handler function registered as disconnectCallback will result in a deadlock.
8085
*/
8186
void setConnectCallback(std::function<void(const socket_t)> func)
8287
{
88+
std::lock_guard<std::mutex> lk(clients_mutex_);
8389
new_connection_callback_ = func;
8490
}
8591

@@ -88,9 +94,14 @@ class TCPServer
8894
*
8995
* \param func Function handling the event information. The file descriptor created by the
9096
* connection event will be passed to the function.
97+
*
98+
* \note: For thread safety, this will block when there is an active callback around connection
99+
* (connection, disconnection, shutdown). Thus, trying to call setDisconnectCallback e.g. by a
100+
* handler function registered as connectCallback will result in a deadlock.
91101
*/
92102
void setDisconnectCallback(std::function<void(const socket_t)> func)
93103
{
104+
std::lock_guard<std::mutex> lk(clients_mutex_);
94105
disconnect_callback_ = func;
95106
}
96107

@@ -99,9 +110,13 @@ class TCPServer
99110
*
100111
* \param func Function handling the event information. The file client's file_descriptor will be
101112
* passed to the function as well as the actual message received from the client.
113+
*
114+
* \note: For thread safety, this will block when there is an active message callback. Thus, trying to call
115+
* setMessageCallback e.g. from a handler function registered as messageCallback will result in a deadlock.
102116
*/
103117
void setMessageCallback(std::function<void(const socket_t, char*, int)> func)
104118
{
119+
std::lock_guard<std::mutex> lk(message_mutex_);
105120
message_callback_ = func;
106121
}
107122

@@ -180,7 +195,7 @@ class TCPServer
180195
void handleDisconnect(const socket_t fd);
181196

182197
//! read data from socket
183-
void readData(const socket_t fd);
198+
bool readData(const socket_t fd);
184199

185200
//! Event handler. Blocks until activity on any client or connection attempt
186201
void spin();
@@ -200,6 +215,8 @@ class TCPServer
200215

201216
uint32_t max_clients_allowed_;
202217
std::vector<socket_t> client_fds_;
218+
std::mutex clients_mutex_;
219+
std::mutex message_mutex_;
203220

204221
static const int INPUT_BUFFER_SIZE = 4096;
205222
char input_buffer_[INPUT_BUFFER_SIZE];

include/ur_client_library/log.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#pragma once
2020
#include <inttypes.h>
2121
#include <memory>
22+
#include <system_error>
2223

2324
#define URCL_LOG_DEBUG(...) urcl::log(__FILE__, __LINE__, urcl::LogLevel::DEBUG, __VA_ARGS__)
2425
#define URCL_LOG_WARN(...) urcl::log(__FILE__, __LINE__, urcl::LogLevel::WARN, __VA_ARGS__)
@@ -89,4 +90,18 @@ void setLogLevel(LogLevel level);
8990
*/
9091
void log(const char* file, int line, LogLevel level, const char* fmt, ...);
9192

93+
/*!
94+
* \brief Cross-platform replacement for strerror.
95+
*
96+
* On MSVC, strerror triggers C4996 (deprecated). This function uses std::error_code instead,
97+
* which is portable and thread-safe.
98+
*
99+
* \param errnum Error number (on POSIX typically errno)
100+
* \returns Human-readable error message
101+
*/
102+
inline std::string strerrorPortable(int errnum)
103+
{
104+
return std::error_code(errnum, std::system_category()).message();
105+
}
106+
92107
} // namespace urcl

0 commit comments

Comments
 (0)