Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
2 changes: 1 addition & 1 deletion docs/Doxyfile
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
project = 'msocket'
copyright = '2026, Conny Gustafsson'
author = 'Conny Gustafsson'
release = '2.0.2'
release = '2.0.3'

# -- General configuration ---------------------------------------------------

Expand Down
5 changes: 4 additions & 1 deletion include/msocket.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion source/msocket_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
4 changes: 4 additions & 0 deletions source/msocket_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ typedef int os_socket_t;

#endif

#ifndef MSG_NOSIGNAL
# define MSG_NOSIGNAL 0
#endif

//////////////////////////////////////////////////////////////////////////////
// PLATFORM THREADING & SYNC
//////////////////////////////////////////////////////////////////////////////
Expand Down
68 changes: 68 additions & 0 deletions tests/testsuite_msocket_loopback.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Loading