From ffa5f3bf57a9a8e8455be5e2ab387af41f279da3 Mon Sep 17 00:00:00 2001 From: TapTap Date: Mon, 20 Jul 2026 20:57:00 +0200 Subject: [PATCH] fix: race condition in accept_loop, auto-detect valgrind --- src/server/server.c | 1 + src/shared/transport_tcp.c | 11 ++++++++++- src/shared/transport_tcp.h | 1 + tests/test_file.c | 2 +- tests/test_utils.h | 15 +++++++++++++++ 5 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/server/server.c b/src/server/server.c index 21a5b4e..9baf9bd 100644 --- a/src/server/server.c +++ b/src/server/server.c @@ -117,6 +117,7 @@ static volatile sig_atomic_t g_server_cleanup_requested = 0; static void cleanup(int sig) { (void)sig; + server_request_shutdown(); g_server_cleanup_requested = 1; } diff --git a/src/shared/transport_tcp.c b/src/shared/transport_tcp.c index 4a90bd1..3b7c9b0 100644 --- a/src/shared/transport_tcp.c +++ b/src/shared/transport_tcp.c @@ -12,6 +12,15 @@ #include #include +/* Flag set by server_request_shutdown() to request graceful shutdown + of the accept loop. Accessed only from transport_tcp.c so it won't + cause linker errors when this file is compiled into client/test targets. */ +static volatile sig_atomic_t g_tcp_cleanup_requested = 0; + +void server_request_shutdown(void) { + g_tcp_cleanup_requested = 1; +} + Server* server_create(int port) { Server* server = (Server*)malloc(sizeof(Server)); if (server == NULL) { @@ -70,7 +79,7 @@ static void accept_loop(Server* server, void (*child_fn)(int, void*), void* chil return; } signal(SIGCHLD, SIG_IGN); - while (1) { + while (!g_tcp_cleanup_requested) { struct sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); int fd = accept(server->file_descriptor, (struct sockaddr*)&client_addr, &client_len); diff --git a/src/shared/transport_tcp.h b/src/shared/transport_tcp.h index 0c4b7da..942fce2 100644 --- a/src/shared/transport_tcp.h +++ b/src/shared/transport_tcp.h @@ -26,6 +26,7 @@ bool server_listen(Server* server, void (*handler)(int file_descriptor)); void server_accept_loop(Server* server, void (*child_fn)(int, void*), void* child_ctx, const char* log_fmt); void server_delete(Server** server); +void server_request_shutdown(void); Client* client_create(); bool client_connect(Client* client, char* host, int port); void client_disconnect(Client* client); diff --git a/tests/test_file.c b/tests/test_file.c index 1a65a05..ae50a30 100644 --- a/tests/test_file.c +++ b/tests/test_file.c @@ -271,7 +271,7 @@ void test_file() { test_to_disk_basic(); test_to_disk_creates_dirs(); test_file_content_to_buffer(); - if (!getenv("FASTSYNC_UNDER_VALGRIND")) { + if (!is_running_under_valgrind()) { // Fork tests are skipped under valgrind because the parent process runs // orders of magnitude slower than the child (parent is instrumented, child // is not), which causes pipe-based protocol handshake timeouts. The parent diff --git a/tests/test_utils.h b/tests/test_utils.h index c9e9f02..67b6bca 100644 --- a/tests/test_utils.h +++ b/tests/test_utils.h @@ -2,9 +2,24 @@ #define TEST_UTILS_H #include +#include #include #include +// Detect if running under valgrind by checking /proc/self/maps for vgpreload. +// This is used to skip fork-based tests that are incompatible with valgrind +// (the instrumented parent runs too slowly, causing pipe timeouts). +static inline bool is_running_under_valgrind(void) { + FILE* f = fopen("/proc/self/maps", "r"); + if (!f) + return false; + char buf[4096]; + size_t n = fread(buf, 1, sizeof(buf) - 1, f); + fclose(f); + buf[n] = '\0'; + return strstr(buf, "vgpreload") != NULL; +} + // Global test suite status extern int tests_run; extern int tests_failed;