From 9435042be7cfa488278eedb5a42d621a50f0931a Mon Sep 17 00:00:00 2001 From: cogu Date: Thu, 24 Sep 2026 23:06:55 +0200 Subject: [PATCH 1/2] feat: Use MSG_NOSIGNAL in msocket_send to prevent SIGPIPE on disconnect --- include/msocket.h | 5 ++- source/msocket_common.c | 2 +- source/msocket_internal.h | 4 ++ tests/testsuite_msocket_loopback.c | 68 ++++++++++++++++++++++++++++++ 4 files changed, 77 insertions(+), 2 deletions(-) diff --git a/include/msocket.h b/include/msocket.h index 6382783..86940be 100644 --- a/include/msocket.h +++ b/include/msocket.h @@ -305,7 +305,10 @@ msocket_error_t msocket_unix_connect(msocket_t *self, const char *socket_path); msocket_error_t msocket_send_to(msocket_t *self, const char *addr, uint16_t port, const void *msg_data, uint32_t msg_len); /** - * Sends stream data over an established TCP connection. + * Sends stream data over an established TCP or UNIX domain socket connection. + * + * Transmits with MSG_NOSIGNAL (where supported) to prevent raising SIGPIPE + * if the remote peer has closed or disconnected. * * @param self Pointer to msocket_t instance (configured in MSOCKET_MODE_STREAM). * @param msg_data Pointer to data payload. diff --git a/source/msocket_common.c b/source/msocket_common.c index fc81ce3..f9c3002 100644 --- a/source/msocket_common.c +++ b/source/msocket_common.c @@ -545,7 +545,7 @@ msocket_error_t msocket_send(msocket_t *self, const void *msg_data, uint32_t msg const char *p = (const char *)msg_data; uint32_t remain = msg_len; while (remain > 0u) { - int n = (int)send(self->os->tcp_sockfd, p, (int)remain, 0); + int n = (int)send(self->os->tcp_sockfd, p, (int)remain, MSG_NOSIGNAL); if (n <= 0) { return MSOCKET_SOCKET_ERROR; } diff --git a/source/msocket_internal.h b/source/msocket_internal.h index 12c037c..2918883 100644 --- a/source/msocket_internal.h +++ b/source/msocket_internal.h @@ -125,6 +125,10 @@ typedef int os_socket_t; #endif +#ifndef MSG_NOSIGNAL +# define MSG_NOSIGNAL 0 +#endif + ////////////////////////////////////////////////////////////////////////////// // PLATFORM THREADING & SYNC ////////////////////////////////////////////////////////////////////////////// diff --git a/tests/testsuite_msocket_loopback.c b/tests/testsuite_msocket_loopback.c index 20f8802..057664a 100644 --- a/tests/testsuite_msocket_loopback.c +++ b/tests/testsuite_msocket_loopback.c @@ -219,10 +219,78 @@ static void test_udp_loopback(CuTest *tc) msocket_sem_delete(g_sem_udp_received); } +#define TCP_PORT_SIGPIPE 19878u + +static void test_tcp_send_to_closed_peer_returns_error_without_sigpipe(CuTest *tc) +{ + g_sem_client_connected = msocket_sem_new(0u); + g_accepted_peer = NULL; + + msocket_t *srv = msocket_new(MSOCKET_ADDR_INET); + CuAssertPtrNotNull(tc, srv); + msocket_error_t rc = msocket_listen(srv, MSOCKET_MODE_STREAM, TCP_PORT_SIGPIPE, "127.0.0.1"); + CuAssertIntEquals(tc, MSOCKET_NO_ERROR, rc); + + msocket_handler_t srv_handler; + memset(&srv_handler, 0, sizeof(srv_handler)); + + accept_thread_arg_t ctx = { srv, &srv_handler }; + msocket_thread_t *accept_thread = msocket_thread_create(accept_worker, &ctx); + CuAssertPtrNotNull(tc, accept_thread); + + /* Start client */ + msocket_t *cli = msocket_new(MSOCKET_ADDR_INET); + CuAssertPtrNotNull(tc, cli); + + msocket_handler_t cli_handler; + memset(&cli_handler, 0, sizeof(cli_handler)); + cli_handler.stream_connected = client_on_connected; + msocket_set_handler(cli, &cli_handler, NULL); + + rc = msocket_connect(cli, "127.0.0.1", TCP_PORT_SIGPIPE); + CuAssertIntEquals(tc, MSOCKET_NO_ERROR, rc); + + /* Wait for client connected */ + int attempts = 0; + while (msocket_sem_test(g_sem_client_connected) <= 0 && attempts++ < 50) { + msocket_sleep_ms(20u); + } + CuAssertTrue(tc, attempts < 50); + + /* Wait for accept thread to finish accepting peer */ + msocket_thread_join(accept_thread); + msocket_thread_delete(accept_thread); + CuAssertPtrNotNull(tc, g_accepted_peer); + + /* Close the client abruptly */ + msocket_close(cli); + msocket_delete(cli); + msocket_sleep_ms(50u); + + /* Sending to the closed peer from server side should not trigger SIGPIPE or crash */ + (void)msocket_send(g_accepted_peer, "Test1", 5u); + msocket_sleep_ms(50u); + (void)msocket_send(g_accepted_peer, "Test2", 5u); + + /* Cleanup */ + if (g_accepted_peer != NULL) { + msocket_close(g_accepted_peer); + msocket_delete(g_accepted_peer); + g_accepted_peer = NULL; + } + + msocket_close(srv); + msocket_delete(srv); + + msocket_sem_delete(g_sem_client_connected); +} + CuSuite *testsuite_msocket_loopback(void) { CuSuite *suite = CuSuiteNew(); SUITE_ADD_TEST(suite, test_tcp_loopback); SUITE_ADD_TEST(suite, test_udp_loopback); + SUITE_ADD_TEST(suite, test_tcp_send_to_closed_peer_returns_error_without_sigpipe); return suite; } + From 7fa0d3e171e099602ed4517316fc5a10b455860c Mon Sep 17 00:00:00 2001 From: cogu Date: Thu, 24 Sep 2026 23:17:08 +0200 Subject: [PATCH 2/2] Change version to v2.0.3 --- 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 cce0f1b..6625474 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.2) +project(msocket LANGUAGES C CXX VERSION 2.0.3) set(CMAKE_DISABLE_IN_SOURCE_BUILD ON) set(CMAKE_DISABLE_SOURCE_CHANGES ON) diff --git a/docs/Doxyfile b/docs/Doxyfile index bcb5838..38c4e01 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -1,6 +1,6 @@ # Doxyfile for Sphinx + Breathe integration PROJECT_NAME = "msocket" -PROJECT_NUMBER = "2.0.2" +PROJECT_NUMBER = "2.0.3" PROJECT_BRIEF = "Event-driven socket library for Linux and Windows" # Input configuration diff --git a/docs/conf.py b/docs/conf.py index 43ec73a..96c37a7 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.2' +release = '2.0.3' # -- General configuration ---------------------------------------------------