diff --git a/tests/test_signal_safety.c b/tests/test_signal_safety.c new file mode 100644 index 0000000..ac3d8d0 --- /dev/null +++ b/tests/test_signal_safety.c @@ -0,0 +1,112 @@ +/* + * Test: Signal safety with pthread_mutex_timedlock (pcl-17t) + * + * Verifies that signal handlers do not deadlock when signals are raised + * while holding mutexes, using the timeout mechanism from pthread_mutex_timedlock. + */ + +#define _DEFAULT_SOURCE +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include + +static pthread_mutex_t test_mutex = PTHREAD_MUTEX_INITIALIZER; +static volatile sig_atomic_t signal_received = 0; +static volatile sig_atomic_t handler_attempted_lock = 0; +static volatile sig_atomic_t handler_timeout = 0; + +static void signal_handler(int sig) { + (void)sig; + signal_received = 1; + handler_attempted_lock = 1; + + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += 1; + + int ret = pthread_mutex_timedlock(&test_mutex, &ts); + if (ret == ETIMEDOUT) { + handler_timeout = 1; + } else if (ret == 0) { + pthread_mutex_unlock(&test_mutex); + } +} + +static int passes = 0, failures = 0; +#define PASS(n) do { printf("PASS: %s\n", n); passes++; } while(0) +#define FAIL(n, ...) do { printf("FAIL: %s — ", n); printf(__VA_ARGS__); printf("\n"); failures++; } while(0) + +static void test_signal_while_holding_lock(void) { + signal_received = 0; + handler_attempted_lock = 0; + handler_timeout = 0; + + struct sigaction sa = {0}; + sa.sa_handler = signal_handler; + sigaction(SIGUSR1, &sa, NULL); + + pthread_mutex_lock(&test_mutex); + raise(SIGUSR1); + usleep(10000); + pthread_mutex_unlock(&test_mutex); + + if (signal_received && handler_attempted_lock && handler_timeout) + PASS("signal raised while holding lock: handler timeout, no deadlock"); + else + FAIL("signal while holding lock", "received=%d attempted=%d timeout=%d", + signal_received, handler_attempted_lock, handler_timeout); +} + +static void test_signal_handler_acquires_lock(void) { + signal_received = 0; + handler_attempted_lock = 0; + handler_timeout = 0; + + struct sigaction sa = {0}; + sa.sa_handler = signal_handler; + sigaction(SIGUSR1, &sa, NULL); + + raise(SIGUSR1); + usleep(10000); + + if (signal_received && handler_attempted_lock && !handler_timeout) + PASS("signal handler acquires lock when available"); + else + FAIL("signal handler acquires lock", "received=%d attempted=%d timeout=%d", + signal_received, handler_attempted_lock, handler_timeout); +} + +static void test_no_deadlock_with_timeout(void) { + signal_received = 0; + handler_attempted_lock = 0; + handler_timeout = 0; + + struct sigaction sa = {0}; + sa.sa_handler = signal_handler; + sigaction(SIGUSR1, &sa, NULL); + + pthread_mutex_lock(&test_mutex); + raise(SIGUSR1); + usleep(1500000); + pthread_mutex_unlock(&test_mutex); + + if (signal_received && handler_timeout) + PASS("no deadlock: timeout mechanism prevents hang"); + else + FAIL("no deadlock with timeout", "received=%d timeout=%d", + signal_received, handler_timeout); +} + +int main(void) { + test_signal_while_holding_lock(); + test_signal_handler_acquires_lock(); + test_no_deadlock_with_timeout(); + + printf("\n%d passed, %d failed\n", passes, failures); + return failures ? 1 : 0; +}