From d9bb439f63b74ef8bf1935a12574d710e3f91381 Mon Sep 17 00:00:00 2001 From: cogu Date: Thu, 17 Sep 2026 00:39:15 +0200 Subject: [PATCH 1/6] Allow custom destructors in socket server --- source/msocket_server.c | 2 +- tests/testsuite_msocket_server.c | 60 +++++++++++++++++++++++++++++++- 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/source/msocket_server.c b/source/msocket_server.c index e719c53..946c617 100644 --- a/source/msocket_server.c +++ b/source/msocket_server.c @@ -250,7 +250,7 @@ static void accept_task(void *arg) if (child == NULL) { break; } - if (self->destructor != NULL) { + if (self->destructor == msocket_vdelete) { msocket_set_server(child, self); } if (self->handler_table.stream_accept != NULL) { diff --git a/tests/testsuite_msocket_server.c b/tests/testsuite_msocket_server.c index 2798ee7..67a5a5f 100644 --- a/tests/testsuite_msocket_server.c +++ b/tests/testsuite_msocket_server.c @@ -95,9 +95,9 @@ static void test_msocket_server_cleanup(CuTest *tc) static void server_on_accept_auto(void *arg, msocket_server_t *srv, void *socket) { (void)arg; - (void)srv; msocket_t *child = (msocket_t *)socket; if (child != NULL) { + msocket_set_server(child, srv); msocket_handler_t child_handler; memset(&child_handler, 0, sizeof(child_handler)); msocket_set_handler(child, &child_handler, NULL); @@ -159,10 +159,68 @@ static void test_msocket_server_auto_reap(CuTest *tc) msocket_sem_delete(g_sem_cleaned_up); } +static void server_on_accept_default(void *arg, msocket_server_t *srv, void *socket) +{ + (void)arg; + (void)srv; + msocket_t *child = (msocket_t *)socket; + if (child != NULL) { + msocket_handler_t child_handler; + memset(&child_handler, 0, sizeof(child_handler)); + msocket_set_handler(child, &child_handler, NULL); + msocket_start_io(child); + msocket_sem_post(g_sem_accepted); + } +} + +static void test_msocket_server_default_auto_reap(CuTest *tc) +{ + g_sem_accepted = msocket_sem_new(0u); + + /* Server with default destructor (msocket_vdelete) automatically attaches accepted sockets */ + msocket_server_t *srv = msocket_server_new(MSOCKET_ADDR_INET, NULL); + CuAssertPtrNotNull(tc, srv); + + msocket_handler_t handler; + memset(&handler, 0, sizeof(handler)); + handler.stream_accept = server_on_accept_default; + msocket_server_set_handler(srv, &handler, NULL); + + msocket_server_start(srv, NULL, 0u, SERVER_TEST_PORT + 2u); + + /* Connect client */ + msocket_t *cli = msocket_new(MSOCKET_ADDR_INET); + CuAssertPtrNotNull(tc, cli); + + msocket_handler_t cli_handler; + memset(&cli_handler, 0, sizeof(cli_handler)); + msocket_set_handler(cli, &cli_handler, NULL); + + msocket_error_t rc = msocket_connect(cli, "127.0.0.1", SERVER_TEST_PORT + 2u); + CuAssertIntEquals(tc, MSOCKET_NO_ERROR, rc); + msocket_start_io(cli); + + /* Wait for server accept */ + int attempts = 0; + while (msocket_sem_test(g_sem_accepted) <= 0 && attempts++ < 50) { + msocket_sleep_ms(20u); + } + CuAssertTrue(tc, attempts < 50); + + /* Disconnect client - auto-reap should handle child socket without error */ + msocket_close(cli); + msocket_delete(cli); + + msocket_sleep_ms(50u); + msocket_server_delete(srv); + msocket_sem_delete(g_sem_accepted); +} + CuSuite *testsuite_msocket_server(void) { CuSuite *suite = CuSuiteNew(); SUITE_ADD_TEST(suite, test_msocket_server_cleanup); SUITE_ADD_TEST(suite, test_msocket_server_auto_reap); + SUITE_ADD_TEST(suite, test_msocket_server_default_auto_reap); return suite; } From 8d507f458ca018f3d9b1c29f4bb4c7aa079ae2ba Mon Sep 17 00:00:00 2001 From: cogu Date: Sat, 19 Sep 2026 15:47:45 +0200 Subject: [PATCH 2/6] Change version to 2.0.1 --- CMakeLists.txt | 2 +- docs/Doxyfile | 2 +- docs/conf.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0bfeab2..b27bce9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) -project(msocket LANGUAGES C CXX VERSION 2.0.0) +project(msocket LANGUAGES C CXX VERSION 2.0.1) set(CMAKE_DISABLE_IN_SOURCE_BUILD ON) set(CMAKE_DISABLE_SOURCE_CHANGES ON) diff --git a/docs/Doxyfile b/docs/Doxyfile index caec118..683b26c 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -1,6 +1,6 @@ # Doxyfile for Sphinx + Breathe integration PROJECT_NAME = "msocket" -PROJECT_NUMBER = "2.0.0" +PROJECT_NUMBER = "2.0.1" PROJECT_BRIEF = "Event-driven socket library for Linux and Windows" # Input configuration diff --git a/docs/conf.py b/docs/conf.py index bae3ad7..bfa55e3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -12,7 +12,7 @@ project = 'msocket' copyright = '2026, Conny Gustafsson' author = 'Conny Gustafsson' -release = '2.0.0' +release = '2.0.1' # -- General configuration --------------------------------------------------- From 811bc17ea2dfc1678fc2238034e985c3d38aaea1 Mon Sep 17 00:00:00 2001 From: cogu Date: Sat, 19 Sep 2026 16:00:47 +0200 Subject: [PATCH 3/6] Update unit tests and docs for custom wrapper destructors --- docs/msocket_server.rst | 36 +++- docs/socket_handler.rst | 8 +- examples/echo/echo_server.c | 12 +- include/msocket.h | 4 + include/msocket_server.h | 27 ++- source/msocket_adapter.cpp | 6 +- tests/testsuite_cpp_socket.cpp | 53 ++++++ tests/testsuite_msocket_server.c | 314 +++++++++++++++++++++++++++---- 8 files changed, 404 insertions(+), 56 deletions(-) diff --git a/docs/msocket_server.rst b/docs/msocket_server.rst index a0c46af..5ad9e2b 100644 --- a/docs/msocket_server.rst +++ b/docs/msocket_server.rst @@ -6,15 +6,41 @@ The ``msocket_server`` module manages listening sockets and handles accepted cli Architecture & Acceptance Workflow ----------------------------------- -1. Initialize the server using :cpp:func:`msocket_server_create` or :cpp:func:`msocket_server_new`. +The server operates in one of two modes depending on the destructor passed to :cpp:func:`msocket_server_create` or :cpp:func:`msocket_server_new`: + +Default Auto-Reap Mode +~~~~~~~~~~~~~~~~~~~~~~ + +When initialized with ``NULL`` or :cpp:func:`msocket_vdelete` as the destructor, accepted sockets are managed automatically: + +1. Initialize the server using :cpp:func:`msocket_server_create` (e.g. ``msocket_server_create(srv, family, NULL)``) or :cpp:func:`msocket_server_new` (e.g. ``msocket_server_new(family, NULL)``). 2. Register an accept handler callback table with :cpp:func:`msocket_server_set_handler`. Inside the ``stream_accept`` callback (see :doc:`Socket Event Handler `): - - Configure per-connection event handlers on the child socket using :cpp:func:`msocket_set_handler`. - - Attach the child socket to the server using :cpp:func:`msocket_set_server(child_socket, srv) ` for automatic cleanup upon disconnect. + - Attach per-connection event handlers to the child socket using :cpp:func:`msocket_set_handler`. - Start background I/O on the child connection by calling :cpp:func:`msocket_start_io(child_socket) `. -3. Start the server on the desired TCP/UDP port using :cpp:func:`msocket_server_start` or on a UNIX domain socket using :cpp:func:`msocket_server_unix_start`. -4. When stopping, destroy the server with :cpp:func:`msocket_server_destroy` or :cpp:func:`msocket_server_delete`, which stops all listening threads and cleans up reaped client resources. + - *Note:* Accepted child sockets are automatically cleaned up by the server upon disconnect. When the remote client disconnects, the socket's background thread automatically enqueues the raw socket to the cleanup thread for asynchronous deletion. + +3. Start listening with :cpp:func:`msocket_server_start` (TCP/UDP) or :cpp:func:`msocket_server_unix_start` (UNIX domain). +4. When stopping, destroy the server with :cpp:func:`msocket_server_destroy` or :cpp:func:`msocket_server_delete`. + +Custom Wrapper Mode +~~~~~~~~~~~~~~~~~~~ + +When wrapping client sockets inside application-level connection or session structures (as in APX): + +1. Initialize the server with a custom destructor function: :cpp:func:`msocket_server_new` (e.g. ``msocket_server_new(family, on_custom_conn_delete)``). +2. Inside ``stream_accept``: + + - Allocate your wrapper structure and wrap the accepted ``child_socket``. + - Configure per-connection event handlers on ``child_socket``, passing the wrapper as callback context. + - Start background I/O on ``child_socket`` using :cpp:func:`msocket_start_io`. + - *Note:* Accepted child sockets are not automatically cleaned up by the server in this mode, preventing raw socket pointers from being passed to the custom destructor. + +3. Inside the ``stream_disconnected`` handler of your wrapper: + + - Enqueue the wrapper structure for deletion by calling :cpp:func:`msocket_server_cleanup_connection` (e.g. ``msocket_server_cleanup_connection(srv, wrapper)``). + - The server cleanup thread invokes your custom destructor, safely deleting both the wrapper and the inner socket. API Reference ------------- diff --git a/docs/socket_handler.rst b/docs/socket_handler.rst index 55fd17f..cd0f79c 100644 --- a/docs/socket_handler.rst +++ b/docs/socket_handler.rst @@ -68,8 +68,12 @@ Invoked on the server when a remote client initiates a new connection. **Implementation Contract:** Configure the child socket before starting I/O: 1. Attach per-client callbacks and context: ``msocket_set_handler(child, &client_handler, client_ctx);`` -2. Bind to server for automatic lifecycle cleanup: ``msocket_set_server(child, srv);`` -3. Start background I/O: ``msocket_start_io(child);`` +2. Start background I/O: ``msocket_start_io(child);`` + +*Connection Lifecycle Cleanup:* + +* Under **Default Auto-Reap Mode** (server created with ``NULL`` or :cpp:func:`msocket_vdelete`), the server accept task automatically associates the child socket for background cleanup upon disconnect. Manual binding is not required. +* Under **Custom Wrapper Mode** (server created with a custom destructor), wrap the socket in your application connection object, listen for ``stream_disconnected``, and call :cpp:func:`msocket_server_cleanup_connection` (e.g. ``msocket_server_cleanup_connection(srv, wrapper)``). stream_connected ~~~~~~~~~~~~~~~~ diff --git a/examples/echo/echo_server.c b/examples/echo/echo_server.c index 4b53928..7de9c3f 100644 --- a/examples/echo/echo_server.c +++ b/examples/echo/echo_server.c @@ -65,6 +65,11 @@ static void on_client_disconnected(void *arg, void *socket) { (void)arg; (void)socket; + /* + * Note: In default auto-reap mode, the disconnected child socket is automatically + * reaped and deleted by the server's background cleanup thread. Do not call + * msocket_delete here. + */ printf("[SERVER] Client disconnected\n"); } @@ -121,7 +126,12 @@ int main(int argc, char *argv[]) printf("[SERVER] Starting Echo Server on port %u (press Ctrl+C to exit)...\n", port); - msocket_server_t *server = msocket_server_new(MSOCKET_ADDR_INET, msocket_vdelete); + /* + * Initialize server with default auto-reap mode (NULL defaults to msocket_vdelete). + * In this mode, accepted child sockets are automatically bound to the server and + * reaped by the server cleanup thread upon disconnection. + */ + msocket_server_t *server = msocket_server_new(MSOCKET_ADDR_INET, NULL); if (server == NULL) { fprintf(stderr, "[SERVER] Failed to allocate msocket_server\n"); return 1; diff --git a/include/msocket.h b/include/msocket.h index 9c987f5..6382783 100644 --- a/include/msocket.h +++ b/include/msocket.h @@ -242,6 +242,10 @@ void msocket_set_handler(msocket_t *self, const msocket_handler_t *handler_table /** * Associates a parent msocket_server instance with this socket for automatic reaping upon disconnection. * + * NOTE: For servers created with default destructor (NULL or msocket_vdelete), accepted child + * sockets are associated with the server automatically by the accept task. Manual association + * is only needed when explicitly opting into automatic raw socket reaping under custom setups. + * * @param self Pointer to msocket_t instance. * @param server Pointer to parent msocket_server instance, or NULL to detach. */ diff --git a/include/msocket_server.h b/include/msocket_server.h index 86abbb1..55d6fb8 100644 --- a/include/msocket_server.h +++ b/include/msocket_server.h @@ -49,6 +49,14 @@ typedef struct msocket_server_tag { /** * Initializes an existing msocket_server instance. * + * The server supports two connection cleanup modes: + * - Default Auto-Reap Mode (destructor == NULL or msocket_vdelete): Accepted child sockets + * are automatically cleaned up by the server. When disconnected, the socket's background I/O + * thread automatically queues the raw socket for deletion via msocket_vdelete. + * - Custom Wrapper Mode (destructor != msocket_vdelete): Accepted child sockets are NOT + * automatically cleaned up by the server. The application should wrap the child socket in a custom structure + * and invoke msocket_server_cleanup_connection(self, wrapper) from the stream_disconnected callback. + * * @param self Pointer to msocket_server_t instance. * @param address_family Address family (MSOCKET_ADDR_INET, MSOCKET_ADDR_INET6, or MSOCKET_ADDR_UNIX). * @param destructor Destructor function for cleanup items, or NULL to use default msocket_vdelete. @@ -65,6 +73,9 @@ void msocket_server_destroy(msocket_server_t *self); /** * Dynamically allocates and initializes a new msocket_server instance. * + * Supports default auto-reap mode (destructor == NULL or msocket_vdelete) or custom + * wrapper mode (custom destructor function). + * * @param address_family Address family (MSOCKET_ADDR_INET, MSOCKET_ADDR_INET6, or MSOCKET_ADDR_UNIX). * @param destructor Destructor function for cleanup items, or NULL to use default msocket_vdelete. * @return Pointer to newly allocated msocket_server_t, or NULL on failure. @@ -81,13 +92,13 @@ void msocket_server_delete(msocket_server_t *self); /** * Registers the server handler table. * - * The `handler->tcp_accept` callback is called whenever a new client connection is accepted: - * void on_accept(void *arg, msocket_server_t *srv, msocket_t *child_socket) + * The `handler->stream_accept` callback is called whenever a new client connection is accepted: + * void on_accept(void *arg, msocket_server_t *srv, void *socket) * Inside that callback, the application should attach per-connection handlers to `child_socket` * via msocket_set_handler(), and then call msocket_start_io(child_socket). * * @param self Pointer to msocket_server_t instance. - * @param handler Pointer to handler table containing tcp_accept callback. + * @param handler Pointer to handler table containing stream_accept callback. * @param handler_arg User context pointer passed to callback functions. */ void msocket_server_set_handler(msocket_server_t *self, const msocket_handler_t *handler, void *handler_arg); @@ -118,20 +129,22 @@ void msocket_server_unix_start(msocket_server_t *self, const char *socket_path); void msocket_server_disable_cleanup(msocket_server_t *self); /** - * Enqueues a disconnected connection socket for asynchronous deletion by the cleanup thread. + * Enqueues an item directly for asynchronous deletion by the cleanup thread. * * @param self Pointer to msocket_server_t instance. - * @param arg Pointer to connection object (passed to destructor). + * @param arg Pointer to object passed to destructor. */ void msocket_server_reap_connection(msocket_server_t *self, void *arg); /** - * Enqueues a closed connection socket or wrapper item for asynchronous deletion by the cleanup thread. + * Safely enqueues a closed connection socket or wrapper item for asynchronous deletion. * * Safe to call from within client connection callbacks (e.g. `stream_disconnected`). + * In default auto-reap mode (destructor == msocket_vdelete), this safely detaches the socket + * from the server before enqueuing to prevent double-reaping upon I/O thread termination. * * @param self Pointer to msocket_server_t instance. - * @param arg Pointer to connection object (passed to destructor). + * @param arg Pointer to connection object or wrapper (passed to destructor). */ void msocket_server_cleanup_connection(msocket_server_t *self, void *arg); diff --git a/source/msocket_adapter.cpp b/source/msocket_adapter.cpp index 767cad5..606bbb0 100644 --- a/source/msocket_adapter.cpp +++ b/source/msocket_adapter.cpp @@ -67,6 +67,7 @@ namespace auto child_socket = reinterpret_cast(socket); if ((listener != nullptr) && (child_socket != nullptr)) { + msocket_set_server(child_socket, nullptr); auto accepted = std::make_unique(child_socket, true); listener->on_connection_accepted(std::move(accepted)); } @@ -181,15 +182,12 @@ namespace msocket { if (m_socket != nullptr) { + msocket_close(m_socket); TcpSocket::set_listener(nullptr); if (m_owns_socket) { msocket_delete(m_socket); } - else - { - msocket_close(m_socket); - } m_socket = nullptr; } m_listener = nullptr; diff --git a/tests/testsuite_cpp_socket.cpp b/tests/testsuite_cpp_socket.cpp index 6022396..57bdfdb 100644 --- a/tests/testsuite_cpp_socket.cpp +++ b/tests/testsuite_cpp_socket.cpp @@ -17,6 +17,10 @@ extern "C" { #include "msocket_adapter.h" #include #include +#include +#include +#include +#include namespace { @@ -80,6 +84,23 @@ namespace } }; + class MockServerListener : public msocket::ServerListener + { + public: + std::mutex m_mutex; + std::condition_variable m_cv; + int accept_calls{ 0 }; + std::unique_ptr accepted_socket; + + void on_connection_accepted(std::unique_ptr socket) override + { + std::lock_guard lock(m_mutex); + accept_calls++; + accepted_socket = std::move(socket); + m_cv.notify_one(); + } + }; + void helper_send_via_base(msocket::Socket& sock, std::string_view message) { sock.send(message); @@ -206,6 +227,37 @@ static void test_cpp_tcp_socket_lifecycle(CuTest* tc) // Destructor of sock_moved will safely call msocket_delete via RAII } +static void test_cpp_tcp_server_lifecycle(CuTest* tc) +{ + MockServerListener srv_listener; + msocket::TcpServer server(MSOCKET_ADDR_INET); + server.set_listener(&srv_listener); + int res = server.start(19890); + CuAssertIntEquals(tc, 0, res); + + MockSocketListener cli_listener; + msocket::TcpSocket client(MSOCKET_ADDR_INET); + client.set_listener(&cli_listener); + res = client.connect("127.0.0.1", 19890); + CuAssertIntEquals(tc, 0, res); + + std::unique_lock lock(srv_listener.m_mutex); + bool accepted = srv_listener.m_cv.wait_for(lock, std::chrono::milliseconds(1000), [&]() { + return srv_listener.accept_calls > 0; + }); + CuAssertTrue(tc, accepted); + CuAssertIntEquals(tc, 1, srv_listener.accept_calls); + CuAssertPtrNotNull(tc, srv_listener.accepted_socket.get()); + auto accepted_sock = std::move(srv_listener.accepted_socket); + lock.unlock(); + + client.close(); + accepted_sock->close(); + accepted_sock.reset(); + + server.stop(); +} + extern "C" CuSuite* testsuite_cpp_socket(void) { CuSuite* suite = CuSuiteNew(); @@ -214,5 +266,6 @@ extern "C" CuSuite* testsuite_cpp_socket(void) SUITE_ADD_TEST(suite, test_cpp_test_socket_move); SUITE_ADD_TEST(suite, test_cpp_legacy_handler_compat); SUITE_ADD_TEST(suite, test_cpp_tcp_socket_lifecycle); + SUITE_ADD_TEST(suite, test_cpp_tcp_server_lifecycle); return suite; } diff --git a/tests/testsuite_msocket_server.c b/tests/testsuite_msocket_server.c index 67a5a5f..dc24911 100644 --- a/tests/testsuite_msocket_server.c +++ b/tests/testsuite_msocket_server.c @@ -16,27 +16,34 @@ #define SERVER_TEST_PORT 19878u -static msocket_sem_t *g_sem_accepted = NULL; -static msocket_sem_t *g_sem_cleaned_up = NULL; -static int g_cleanup_count = 0; - -static void custom_destructor(void *arg) +static msocket_sem_t *m_sem_accepted = NULL; +static msocket_sem_t *m_sem_cleaned_up = NULL; +static int m_cleanup_count = 0; +static msocket_t *m_accepted_socket = NULL; + +typedef struct custom_conn_tag { + msocket_t *sock; + msocket_server_t *srv; + int custom_id; +} custom_conn_t; + +static void on_custom_destructor(void *arg) { msocket_t *sock = (msocket_t *)arg; if (sock != NULL) { msocket_delete(sock); - g_cleanup_count++; - msocket_sem_post(g_sem_cleaned_up); + m_cleanup_count++; + msocket_sem_post(m_sem_cleaned_up); } } -static void server_on_accept(void *arg, msocket_server_t *srv, void *socket) +static void on_server_accept_cleanup(void *arg, msocket_server_t *srv, void *socket) { (void)arg; (void)srv; msocket_t *child = (msocket_t *)socket; if (child != NULL) { - msocket_sem_post(g_sem_accepted); + msocket_sem_post(m_sem_accepted); /* Queue for asynchronous destruction */ msocket_server_cleanup_connection(srv, child); } @@ -44,16 +51,16 @@ static void server_on_accept(void *arg, msocket_server_t *srv, void *socket) static void test_msocket_server_cleanup(CuTest *tc) { - g_sem_accepted = msocket_sem_new(0u); - g_sem_cleaned_up = msocket_sem_new(0u); - g_cleanup_count = 0; + m_sem_accepted = msocket_sem_new(0u); + m_sem_cleaned_up = msocket_sem_new(0u); + m_cleanup_count = 0; - msocket_server_t *srv = msocket_server_new(MSOCKET_ADDR_INET, custom_destructor); + msocket_server_t *srv = msocket_server_new(MSOCKET_ADDR_INET, on_custom_destructor); CuAssertPtrNotNull(tc, srv); msocket_handler_t handler; memset(&handler, 0, sizeof(handler)); - handler.stream_accept = server_on_accept; + handler.stream_accept = on_server_accept_cleanup; msocket_server_set_handler(srv, &handler, NULL); msocket_server_start(srv, NULL, 0u, SERVER_TEST_PORT); @@ -71,28 +78,28 @@ static void test_msocket_server_cleanup(CuTest *tc) /* Wait for server accept */ int attempts = 0; - while (msocket_sem_test(g_sem_accepted) <= 0 && attempts++ < 50) { + while (msocket_sem_test(m_sem_accepted) <= 0 && attempts++ < 50) { msocket_sleep_ms(20u); } CuAssertTrue(tc, attempts < 50); /* Wait for asynchronous cleanup queue to run destructor */ attempts = 0; - while (msocket_sem_test(g_sem_cleaned_up) <= 0 && attempts++ < 50) { + while (msocket_sem_test(m_sem_cleaned_up) <= 0 && attempts++ < 50) { msocket_sleep_ms(20u); } CuAssertTrue(tc, attempts < 50); - CuAssertIntEquals(tc, 1, g_cleanup_count); + CuAssertIntEquals(tc, 1, m_cleanup_count); msocket_close(cli); msocket_delete(cli); msocket_server_delete(srv); - msocket_sem_delete(g_sem_accepted); - msocket_sem_delete(g_sem_cleaned_up); + msocket_sem_delete(m_sem_accepted); + msocket_sem_delete(m_sem_cleaned_up); } -static void server_on_accept_auto(void *arg, msocket_server_t *srv, void *socket) +static void on_server_accept_auto(void *arg, msocket_server_t *srv, void *socket) { (void)arg; msocket_t *child = (msocket_t *)socket; @@ -102,22 +109,22 @@ static void server_on_accept_auto(void *arg, msocket_server_t *srv, void *socket memset(&child_handler, 0, sizeof(child_handler)); msocket_set_handler(child, &child_handler, NULL); msocket_start_io(child); - msocket_sem_post(g_sem_accepted); + msocket_sem_post(m_sem_accepted); } } static void test_msocket_server_auto_reap(CuTest *tc) { - g_sem_accepted = msocket_sem_new(0u); - g_sem_cleaned_up = msocket_sem_new(0u); - g_cleanup_count = 0; + m_sem_accepted = msocket_sem_new(0u); + m_sem_cleaned_up = msocket_sem_new(0u); + m_cleanup_count = 0; - msocket_server_t *srv = msocket_server_new(MSOCKET_ADDR_INET, custom_destructor); + msocket_server_t *srv = msocket_server_new(MSOCKET_ADDR_INET, on_custom_destructor); CuAssertPtrNotNull(tc, srv); msocket_handler_t handler; memset(&handler, 0, sizeof(handler)); - handler.stream_accept = server_on_accept_auto; + handler.stream_accept = on_server_accept_auto; msocket_server_set_handler(srv, &handler, NULL); msocket_server_start(srv, NULL, 0u, SERVER_TEST_PORT + 1u); @@ -136,7 +143,7 @@ static void test_msocket_server_auto_reap(CuTest *tc) /* Wait for server accept */ int attempts = 0; - while (msocket_sem_test(g_sem_accepted) <= 0 && attempts++ < 50) { + while (msocket_sem_test(m_sem_accepted) <= 0 && attempts++ < 50) { msocket_sleep_ms(20u); } CuAssertTrue(tc, attempts < 50); @@ -147,19 +154,19 @@ static void test_msocket_server_auto_reap(CuTest *tc) /* Wait for automatic cleanup queue to run destructor */ attempts = 0; - while (msocket_sem_test(g_sem_cleaned_up) <= 0 && attempts++ < 50) { + while (msocket_sem_test(m_sem_cleaned_up) <= 0 && attempts++ < 50) { msocket_sleep_ms(20u); } CuAssertTrue(tc, attempts < 50); - CuAssertIntEquals(tc, 1, g_cleanup_count); + CuAssertIntEquals(tc, 1, m_cleanup_count); msocket_server_delete(srv); - msocket_sem_delete(g_sem_accepted); - msocket_sem_delete(g_sem_cleaned_up); + msocket_sem_delete(m_sem_accepted); + msocket_sem_delete(m_sem_cleaned_up); } -static void server_on_accept_default(void *arg, msocket_server_t *srv, void *socket) +static void on_server_accept_default(void *arg, msocket_server_t *srv, void *socket) { (void)arg; (void)srv; @@ -169,13 +176,13 @@ static void server_on_accept_default(void *arg, msocket_server_t *srv, void *soc memset(&child_handler, 0, sizeof(child_handler)); msocket_set_handler(child, &child_handler, NULL); msocket_start_io(child); - msocket_sem_post(g_sem_accepted); + msocket_sem_post(m_sem_accepted); } } static void test_msocket_server_default_auto_reap(CuTest *tc) { - g_sem_accepted = msocket_sem_new(0u); + m_sem_accepted = msocket_sem_new(0u); /* Server with default destructor (msocket_vdelete) automatically attaches accepted sockets */ msocket_server_t *srv = msocket_server_new(MSOCKET_ADDR_INET, NULL); @@ -183,7 +190,7 @@ static void test_msocket_server_default_auto_reap(CuTest *tc) msocket_handler_t handler; memset(&handler, 0, sizeof(handler)); - handler.stream_accept = server_on_accept_default; + handler.stream_accept = on_server_accept_default; msocket_server_set_handler(srv, &handler, NULL); msocket_server_start(srv, NULL, 0u, SERVER_TEST_PORT + 2u); @@ -202,7 +209,7 @@ static void test_msocket_server_default_auto_reap(CuTest *tc) /* Wait for server accept */ int attempts = 0; - while (msocket_sem_test(g_sem_accepted) <= 0 && attempts++ < 50) { + while (msocket_sem_test(m_sem_accepted) <= 0 && attempts++ < 50) { msocket_sleep_ms(20u); } CuAssertTrue(tc, attempts < 50); @@ -213,7 +220,237 @@ static void test_msocket_server_default_auto_reap(CuTest *tc) msocket_sleep_ms(50u); msocket_server_delete(srv); - msocket_sem_delete(g_sem_accepted); + msocket_sem_delete(m_sem_accepted); +} + +static void on_custom_conn_delete(void *arg) +{ + custom_conn_t *conn = (custom_conn_t *)arg; + if (conn != NULL) { + if (conn->sock != NULL) { + msocket_delete(conn->sock); + conn->sock = NULL; + } + free(conn); + m_cleanup_count++; + msocket_sem_post(m_sem_cleaned_up); + } +} + +static void on_wrapper_client_disconnected(void *arg, void *socket) +{ + (void)socket; + custom_conn_t *conn = (custom_conn_t *)arg; + if (conn != NULL && conn->srv != NULL) { + msocket_server_cleanup_connection(conn->srv, conn); + } +} + +static void on_server_accept_custom_wrapper(void *arg, msocket_server_t *srv, void *socket) +{ + (void)arg; + msocket_t *child = (msocket_t *)socket; + if (child != NULL) { + custom_conn_t *conn = (custom_conn_t *)malloc(sizeof(custom_conn_t)); + if (conn != NULL) { + conn->sock = child; + conn->srv = srv; + conn->custom_id = 42; + msocket_handler_t child_handler; + memset(&child_handler, 0, sizeof(child_handler)); + child_handler.stream_disconnected = on_wrapper_client_disconnected; + msocket_set_handler(child, &child_handler, (void *)conn); + msocket_start_io(child); + msocket_sem_post(m_sem_accepted); + } + } +} + +static void test_msocket_server_custom_wrapper(CuTest *tc) +{ + m_sem_accepted = msocket_sem_new(0u); + m_sem_cleaned_up = msocket_sem_new(0u); + m_cleanup_count = 0; + + /* Server with custom wrapper destructor (e.g. c-apx json_server_connection_vdelete pattern) */ + msocket_server_t *srv = msocket_server_new(MSOCKET_ADDR_INET, on_custom_conn_delete); + CuAssertPtrNotNull(tc, srv); + + msocket_handler_t handler; + memset(&handler, 0, sizeof(handler)); + handler.stream_accept = on_server_accept_custom_wrapper; + msocket_server_set_handler(srv, &handler, NULL); + + msocket_server_start(srv, NULL, 0u, SERVER_TEST_PORT + 3u); + + /* Connect client */ + msocket_t *cli = msocket_new(MSOCKET_ADDR_INET); + CuAssertPtrNotNull(tc, cli); + + msocket_handler_t cli_handler; + memset(&cli_handler, 0, sizeof(cli_handler)); + msocket_set_handler(cli, &cli_handler, NULL); + + msocket_error_t rc = msocket_connect(cli, "127.0.0.1", SERVER_TEST_PORT + 3u); + CuAssertIntEquals(tc, MSOCKET_NO_ERROR, rc); + msocket_start_io(cli); + + /* Wait for server accept */ + int attempts = 0; + while (msocket_sem_test(m_sem_accepted) <= 0 && attempts++ < 50) { + msocket_sleep_ms(20u); + } + CuAssertTrue(tc, attempts < 50); + + /* Disconnect client - wrapper disconnect callback invokes msocket_server_cleanup_connection */ + msocket_close(cli); + msocket_delete(cli); + + /* Wait for custom destructor to execute */ + attempts = 0; + while (msocket_sem_test(m_sem_cleaned_up) <= 0 && attempts++ < 50) { + msocket_sleep_ms(20u); + } + CuAssertTrue(tc, attempts < 50); + CuAssertIntEquals(tc, 1, m_cleanup_count); + + msocket_server_delete(srv); + msocket_sem_delete(m_sem_accepted); + msocket_sem_delete(m_sem_cleaned_up); +} + +static void on_child_disconnected_cleanup(void *arg, void *socket) +{ + msocket_server_t *srv = (msocket_server_t *)arg; + msocket_t *child = (msocket_t *)socket; + if (srv != NULL && child != NULL) { + msocket_server_cleanup_connection(srv, child); + } +} + +static void on_server_accept_manual_default(void *arg, msocket_server_t *srv, void *socket) +{ + (void)arg; + msocket_t *child = (msocket_t *)socket; + if (child != NULL) { + msocket_handler_t child_handler; + memset(&child_handler, 0, sizeof(child_handler)); + child_handler.stream_disconnected = on_child_disconnected_cleanup; + msocket_set_handler(child, &child_handler, (void *)srv); + msocket_start_io(child); + msocket_sem_post(m_sem_accepted); + } +} + +static void test_msocket_server_manual_cleanup_default(CuTest *tc) +{ + m_sem_accepted = msocket_sem_new(0u); + + /* Server with default destructor */ + msocket_server_t *srv = msocket_server_new(MSOCKET_ADDR_INET, NULL); + CuAssertPtrNotNull(tc, srv); + + msocket_handler_t handler; + memset(&handler, 0, sizeof(handler)); + handler.stream_accept = on_server_accept_manual_default; + msocket_server_set_handler(srv, &handler, NULL); + + msocket_server_start(srv, NULL, 0u, SERVER_TEST_PORT + 4u); + + /* Connect client */ + msocket_t *cli = msocket_new(MSOCKET_ADDR_INET); + CuAssertPtrNotNull(tc, cli); + + msocket_handler_t cli_handler; + memset(&cli_handler, 0, sizeof(cli_handler)); + msocket_set_handler(cli, &cli_handler, NULL); + + msocket_error_t rc = msocket_connect(cli, "127.0.0.1", SERVER_TEST_PORT + 4u); + CuAssertIntEquals(tc, MSOCKET_NO_ERROR, rc); + msocket_start_io(cli); + + /* Wait for server accept */ + int attempts = 0; + while (msocket_sem_test(m_sem_accepted) <= 0 && attempts++ < 50) { + msocket_sleep_ms(20u); + } + CuAssertTrue(tc, attempts < 50); + + /* Disconnect client - on_child_disconnected_cleanup calls msocket_server_cleanup_connection. + * It unsets child->server, so when io_task finishes it will not double reap. */ + msocket_close(cli); + msocket_delete(cli); + + msocket_sleep_ms(50u); + msocket_server_delete(srv); + msocket_sem_delete(m_sem_accepted); +} + +static void on_server_accept_disabled(void *arg, msocket_server_t *srv, void *socket) +{ + (void)arg; + (void)srv; + msocket_t *child = (msocket_t *)socket; + if (child != NULL) { + m_accepted_socket = child; + msocket_handler_t child_handler; + memset(&child_handler, 0, sizeof(child_handler)); + msocket_set_handler(child, &child_handler, NULL); + msocket_start_io(child); + msocket_sem_post(m_sem_accepted); + } +} + +static void test_msocket_server_disable_cleanup(CuTest *tc) +{ + m_sem_accepted = msocket_sem_new(0u); + m_accepted_socket = NULL; + + msocket_server_t *srv = msocket_server_new(MSOCKET_ADDR_INET, NULL); + CuAssertPtrNotNull(tc, srv); + + /* Disable automatic cleanup (as in apx_socket_extension) */ + msocket_server_disable_cleanup(srv); + CuAssertPtrEquals(tc, NULL, srv->destructor); + + msocket_handler_t handler; + memset(&handler, 0, sizeof(handler)); + handler.stream_accept = on_server_accept_disabled; + msocket_server_set_handler(srv, &handler, NULL); + + msocket_server_start(srv, NULL, 0u, SERVER_TEST_PORT + 5u); + + /* Connect client */ + msocket_t *cli = msocket_new(MSOCKET_ADDR_INET); + CuAssertPtrNotNull(tc, cli); + + msocket_handler_t cli_handler; + memset(&cli_handler, 0, sizeof(cli_handler)); + msocket_set_handler(cli, &cli_handler, NULL); + + msocket_error_t rc = msocket_connect(cli, "127.0.0.1", SERVER_TEST_PORT + 5u); + CuAssertIntEquals(tc, MSOCKET_NO_ERROR, rc); + msocket_start_io(cli); + + /* Wait for server accept */ + int attempts = 0; + while (msocket_sem_test(m_sem_accepted) <= 0 && attempts++ < 50) { + msocket_sleep_ms(20u); + } + CuAssertTrue(tc, attempts < 50); + CuAssertPtrNotNull(tc, m_accepted_socket); + + /* Disconnect client */ + msocket_close(cli); + msocket_delete(cli); + + msocket_sleep_ms(30u); + + /* Application manually manages child socket lifecycle */ + msocket_delete(m_accepted_socket); + + msocket_server_delete(srv); + msocket_sem_delete(m_sem_accepted); } CuSuite *testsuite_msocket_server(void) @@ -222,5 +459,8 @@ CuSuite *testsuite_msocket_server(void) SUITE_ADD_TEST(suite, test_msocket_server_cleanup); SUITE_ADD_TEST(suite, test_msocket_server_auto_reap); SUITE_ADD_TEST(suite, test_msocket_server_default_auto_reap); + SUITE_ADD_TEST(suite, test_msocket_server_custom_wrapper); + SUITE_ADD_TEST(suite, test_msocket_server_manual_cleanup_default); + SUITE_ADD_TEST(suite, test_msocket_server_disable_cleanup); return suite; } From da22a22e2b57fc76c3c74487a16457e443c70c48 Mon Sep 17 00:00:00 2001 From: cogu Date: Sat, 19 Sep 2026 16:09:07 +0200 Subject: [PATCH 4/6] Reduce risk of flakiness in unit tests --- tests/testsuite_msocket_server.c | 53 ++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/tests/testsuite_msocket_server.c b/tests/testsuite_msocket_server.c index dc24911..c499d18 100644 --- a/tests/testsuite_msocket_server.c +++ b/tests/testsuite_msocket_server.c @@ -18,6 +18,7 @@ static msocket_sem_t *m_sem_accepted = NULL; static msocket_sem_t *m_sem_cleaned_up = NULL; +static msocket_sem_t *m_sem_disconnected = NULL; static int m_cleanup_count = 0; static msocket_t *m_accepted_socket = NULL; @@ -166,6 +167,13 @@ static void test_msocket_server_auto_reap(CuTest *tc) msocket_sem_delete(m_sem_cleaned_up); } +static void on_child_disconnected_default(void *arg, void *socket) +{ + (void)arg; + (void)socket; + msocket_sem_post(m_sem_disconnected); +} + static void on_server_accept_default(void *arg, msocket_server_t *srv, void *socket) { (void)arg; @@ -174,6 +182,7 @@ static void on_server_accept_default(void *arg, msocket_server_t *srv, void *soc if (child != NULL) { msocket_handler_t child_handler; memset(&child_handler, 0, sizeof(child_handler)); + child_handler.stream_disconnected = on_child_disconnected_default; msocket_set_handler(child, &child_handler, NULL); msocket_start_io(child); msocket_sem_post(m_sem_accepted); @@ -183,6 +192,7 @@ static void on_server_accept_default(void *arg, msocket_server_t *srv, void *soc static void test_msocket_server_default_auto_reap(CuTest *tc) { m_sem_accepted = msocket_sem_new(0u); + m_sem_disconnected = msocket_sem_new(0u); /* Server with default destructor (msocket_vdelete) automatically attaches accepted sockets */ msocket_server_t *srv = msocket_server_new(MSOCKET_ADDR_INET, NULL); @@ -218,9 +228,19 @@ static void test_msocket_server_default_auto_reap(CuTest *tc) msocket_close(cli); msocket_delete(cli); - msocket_sleep_ms(50u); + /* Wait for child socket to detect disconnection */ + attempts = 0; + while (msocket_sem_test(m_sem_disconnected) <= 0 && attempts++ < 50) { + msocket_sleep_ms(20u); + } + CuAssertTrue(tc, attempts < 50); + + /* Give server cleanup queue a moment to process the reaped socket */ + msocket_sleep_ms(20u); + msocket_server_delete(srv); msocket_sem_delete(m_sem_accepted); + msocket_sem_delete(m_sem_disconnected); } static void on_custom_conn_delete(void *arg) @@ -325,6 +345,7 @@ static void on_child_disconnected_cleanup(void *arg, void *socket) msocket_t *child = (msocket_t *)socket; if (srv != NULL && child != NULL) { msocket_server_cleanup_connection(srv, child); + msocket_sem_post(m_sem_disconnected); } } @@ -345,6 +366,7 @@ static void on_server_accept_manual_default(void *arg, msocket_server_t *srv, vo static void test_msocket_server_manual_cleanup_default(CuTest *tc) { m_sem_accepted = msocket_sem_new(0u); + m_sem_disconnected = msocket_sem_new(0u); /* Server with default destructor */ msocket_server_t *srv = msocket_server_new(MSOCKET_ADDR_INET, NULL); @@ -381,9 +403,26 @@ static void test_msocket_server_manual_cleanup_default(CuTest *tc) msocket_close(cli); msocket_delete(cli); - msocket_sleep_ms(50u); + /* Wait for disconnect callback to execute and enqueue cleanup */ + attempts = 0; + while (msocket_sem_test(m_sem_disconnected) <= 0 && attempts++ < 50) { + msocket_sleep_ms(20u); + } + CuAssertTrue(tc, attempts < 50); + + /* Give cleanup thread a brief moment to finish destruction */ + msocket_sleep_ms(20u); + msocket_server_delete(srv); msocket_sem_delete(m_sem_accepted); + msocket_sem_delete(m_sem_disconnected); +} + +static void on_disabled_client_disconnected(void *arg, void *socket) +{ + (void)arg; + (void)socket; + msocket_sem_post(m_sem_disconnected); } static void on_server_accept_disabled(void *arg, msocket_server_t *srv, void *socket) @@ -395,6 +434,7 @@ static void on_server_accept_disabled(void *arg, msocket_server_t *srv, void *so m_accepted_socket = child; msocket_handler_t child_handler; memset(&child_handler, 0, sizeof(child_handler)); + child_handler.stream_disconnected = on_disabled_client_disconnected; msocket_set_handler(child, &child_handler, NULL); msocket_start_io(child); msocket_sem_post(m_sem_accepted); @@ -404,6 +444,7 @@ static void on_server_accept_disabled(void *arg, msocket_server_t *srv, void *so static void test_msocket_server_disable_cleanup(CuTest *tc) { m_sem_accepted = msocket_sem_new(0u); + m_sem_disconnected = msocket_sem_new(0u); m_accepted_socket = NULL; msocket_server_t *srv = msocket_server_new(MSOCKET_ADDR_INET, NULL); @@ -444,13 +485,19 @@ static void test_msocket_server_disable_cleanup(CuTest *tc) msocket_close(cli); msocket_delete(cli); - msocket_sleep_ms(30u); + /* Wait for disconnect event on accepted socket before manual deletion */ + attempts = 0; + while (msocket_sem_test(m_sem_disconnected) <= 0 && attempts++ < 50) { + msocket_sleep_ms(20u); + } + CuAssertTrue(tc, attempts < 50); /* Application manually manages child socket lifecycle */ msocket_delete(m_accepted_socket); msocket_server_delete(srv); msocket_sem_delete(m_sem_accepted); + msocket_sem_delete(m_sem_disconnected); } CuSuite *testsuite_msocket_server(void) From 771abd2a2e35fac3fc6798b7b5688b0f3db3557f Mon Sep 17 00:00:00 2001 From: cogu Date: Sat, 19 Sep 2026 16:31:32 +0200 Subject: [PATCH 5/6] Fixed thread synchronization issue found by TSan --- README.md | 2 +- source/msocket_common.c | 37 +++++++++++++++++++++++------- tests/testsuite_msocket_loopback.c | 5 ++-- 3 files changed, 33 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 346f0e9..c231c34 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ ctest --preset clang-asan # ThreadSanitizer (TSan) cmake --preset clang-tsan cmake --build --preset clang-tsan -ctest --preset clang-tsan +ctest --preset clang-tsan -V # Static Analysis cmake --preset clang-tidy diff --git a/source/msocket_common.c b/source/msocket_common.c index edf8ebf..fc81ce3 100644 --- a/source/msocket_common.c +++ b/source/msocket_common.c @@ -343,14 +343,24 @@ msocket_t *msocket_accept(msocket_t *self, msocket_t *child) msocket_os_mutex_lock(self->os); self->state = MSOCKET_STATE_ACCEPTING; + os_socket_t accept_fd = self->os->tcp_sockfd; msocket_os_mutex_unlock(self->os); + if (OS_SOCKET_IS_INVALID(accept_fd)) { + if (placement_new) { + msocket_destroy(child); + } else { + msocket_delete(child); + } + return NULL; + } + os_socket_t sockfd; int one = 1; msocket_error_t result = MSOCKET_NO_ERROR; if (self->address_family == MSOCKET_ADDR_UNIX) { - sockfd = accept(self->os->tcp_sockfd, NULL, NULL); + sockfd = accept(accept_fd, NULL, NULL); if (OS_SOCKET_IS_INVALID(sockfd)) { result = MSOCKET_SOCKET_ERROR; } else { @@ -362,7 +372,7 @@ msocket_t *msocket_accept(msocket_t *self, msocket_t *child) struct sockaddr_in6 cli_addr6; OS_SOCK_LEN_T cli_len = (OS_SOCK_LEN_T)sizeof(cli_addr6); memset(&cli_addr6, 0, sizeof(cli_addr6)); - sockfd = accept(self->os->tcp_sockfd, (struct sockaddr *)&cli_addr6, &cli_len); + sockfd = accept(accept_fd, (struct sockaddr *)&cli_addr6, &cli_len); if (OS_SOCKET_IS_INVALID(sockfd)) { result = MSOCKET_SOCKET_ERROR; } else if (inet_ntop(AF_INET6, &(cli_addr6.sin6_addr), child->stream_info.addr, MSOCKET_ADDRSTRLEN) == NULL) { @@ -377,7 +387,7 @@ msocket_t *msocket_accept(msocket_t *self, msocket_t *child) struct sockaddr_in cli_addr; OS_SOCK_LEN_T cli_len = (OS_SOCK_LEN_T)sizeof(cli_addr); memset(&cli_addr, 0, sizeof(cli_addr)); - sockfd = accept(self->os->tcp_sockfd, (struct sockaddr *)&cli_addr, &cli_len); + sockfd = accept(accept_fd, (struct sockaddr *)&cli_addr, &cli_len); if (OS_SOCKET_IS_INVALID(sockfd)) { result = MSOCKET_SOCKET_ERROR; } else if (inet_ntop(AF_INET, &(cli_addr.sin_addr), child->stream_info.addr, MSOCKET_ADDRSTRLEN) == NULL) { @@ -391,7 +401,9 @@ msocket_t *msocket_accept(msocket_t *self, msocket_t *child) } msocket_os_mutex_lock(self->os); - self->state = MSOCKET_STATE_LISTENING; + if (self->state == MSOCKET_STATE_ACCEPTING) { + self->state = MSOCKET_STATE_LISTENING; + } msocket_os_mutex_unlock(self->os); if (result != MSOCKET_NO_ERROR) { @@ -416,14 +428,17 @@ static msocket_error_t msocket_start_io_thread(msocket_t *self) if (self == NULL || self->os == NULL) { return MSOCKET_INVALID_ARGUMENT_ERROR; } + msocket_os_mutex_lock(self->os); if (!self->os->thread_running) { self->os->thread_running = true; self->os->io_thread = msocket_thread_create(io_task, self); if (self->os->io_thread == NULL) { self->os->thread_running = false; + msocket_os_mutex_unlock(self->os); return MSOCKET_MEM_ERROR; } } + msocket_os_mutex_unlock(self->os); return MSOCKET_NO_ERROR; } @@ -586,24 +601,30 @@ void msocket_close(msocket_t *self) return; } + msocket_os_mutex_lock(self->os); /* Prevent joining I/O thread from within itself */ if (self->os->thread_running && msocket_thread_is_current(self->os->io_thread)) { + msocket_os_mutex_unlock(self->os); return; } - msocket_os_mutex_lock(self->os); self->state = MSOCKET_STATE_CLOSING; if (OS_SOCKET_IS_VALID(self->os->tcp_sockfd)) { OS_SOCKET_SHUTDOWN(self->os->tcp_sockfd); } - msocket_os_mutex_unlock(self->os); + msocket_thread_t *io_thread = NULL; if (self->os->thread_running) { - msocket_thread_join(self->os->io_thread); - msocket_thread_delete(self->os->io_thread); + io_thread = self->os->io_thread; self->os->io_thread = NULL; self->os->thread_running = false; } + msocket_os_mutex_unlock(self->os); + + if (io_thread != NULL) { + msocket_thread_join(io_thread); + msocket_thread_delete(io_thread); + } msocket_os_mutex_lock(self->os); if (OS_SOCKET_IS_VALID(self->os->tcp_sockfd)) { diff --git a/tests/testsuite_msocket_loopback.c b/tests/testsuite_msocket_loopback.c index 09e8774..20f8802 100644 --- a/tests/testsuite_msocket_loopback.c +++ b/tests/testsuite_msocket_loopback.c @@ -159,6 +159,9 @@ static void test_tcp_loopback(CuTest *tc) CuAssertStrEquals(tc, "Pong!", g_client_recv_buf); /* Cleanup */ + msocket_thread_join(accept_thread); + msocket_thread_delete(accept_thread); + msocket_close(cli); msocket_delete(cli); @@ -169,8 +172,6 @@ static void test_tcp_loopback(CuTest *tc) } msocket_close(srv); - msocket_thread_join(accept_thread); - msocket_thread_delete(accept_thread); msocket_delete(srv); msocket_sem_delete(g_sem_client_connected); From 7105f1bae0ad1098dbe2156901b63a4a9a0bff6b Mon Sep 17 00:00:00 2001 From: cogu Date: Sat, 19 Sep 2026 16:47:21 +0200 Subject: [PATCH 6/6] Fix compiler warning reported by MSVC --- tests/testsuite_msocket_server.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testsuite_msocket_server.c b/tests/testsuite_msocket_server.c index c499d18..294a235 100644 --- a/tests/testsuite_msocket_server.c +++ b/tests/testsuite_msocket_server.c @@ -452,7 +452,7 @@ static void test_msocket_server_disable_cleanup(CuTest *tc) /* Disable automatic cleanup (as in apx_socket_extension) */ msocket_server_disable_cleanup(srv); - CuAssertPtrEquals(tc, NULL, srv->destructor); + CuAssertFnPtrEquals(tc, NULL, srv->destructor); msocket_handler_t handler; memset(&handler, 0, sizeof(handler));