From 40fd38dde58a890721f79ef592d57d4a95d51b57 Mon Sep 17 00:00:00 2001 From: Levi Neely <141506390+lneely@users.noreply.github.com> Date: Tue, 3 Mar 2026 19:33:56 +0100 Subject: [PATCH] Fix pcl-zqv.5.7: eliminate deadlock in pfs_get_both_locks (#339) * Fix pcl-zqv.5.7: eliminate deadlock in pfs_get_both_locks - Enforce consistent lock ordering: always acquire psql lock before file mutex - Remove retry loop that could spin indefinitely - Replace abort() with error return in pfs_lock_file timeout - Add unit test verifying lock ordering prevents deadlock Fixes GH #258 * Reorganize tests: smoke-tests, fault-inject, unit-tests * Reorganize tests: smoke-tests, fault-inject, unit-tests * Remove validation report --------- Co-authored-by: Levi Neely --- pclsync/pfs.c | 14 +-- pclsync/pfs.h | 14 +-- test_deadlock_forced | Bin 0 -> 21408 bytes tests/fault-inject/test_deadlock_forced.c | 116 ++++++++++++++++++++++ tests/fault-inject/test_new_code.c | 83 ++++++++++++++++ tests/fault-inject/test_old_aggressive.c | 81 +++++++++++++++ tests/smoke-tests/smoke-test-pfs-locks.sh | 39 ++++++++ tests/unit-tests/test_pfs_lock_ordering.c | 51 ++++++++++ 8 files changed, 380 insertions(+), 18 deletions(-) create mode 100755 test_deadlock_forced create mode 100644 tests/fault-inject/test_deadlock_forced.c create mode 100644 tests/fault-inject/test_new_code.c create mode 100644 tests/fault-inject/test_old_aggressive.c create mode 100755 tests/smoke-tests/smoke-test-pfs-locks.sh create mode 100644 tests/unit-tests/test_pfs_lock_ordering.c diff --git a/pclsync/pfs.c b/pclsync/pfs.c index ecb8d6f..c9ab4ce 100644 --- a/pclsync/pfs.c +++ b/pclsync/pfs.c @@ -1765,17 +1765,8 @@ static void pfs_free_openfile(psync_openfile_t *of) { } static void pfs_get_both_locks(psync_openfile_t *of) { -retry: psql_lock(); - if (pthread_mutex_trylock(&of->mutex)) { - psql_unlock(); - pfs_lock_file(of); - if (psql_trylock()) { - pthread_mutex_unlock(&of->mutex); - psys_sleep_milliseconds(1); - goto retry; - } - } + pfs_lock_file(of); } void pfs_dec_of_refcnt(psync_openfile_t *of) { @@ -2431,7 +2422,8 @@ PSYNC_NOINLINE static int pfs_do_check_write_space(psync_openfile_t *of, (unsigned long)speed / 1024, (unsigned long)speed, (unsigned long)mult); pfs_throttle(size, speed); pdbg_logf(D_NOTICE, "continuing write"); - pfs_lock_file(of); + if (pfs_lock_file(of)) + return -EINTR; return 1; } diff --git a/pclsync/pfs.h b/pclsync/pfs.h index f176336..eedd027 100644 --- a/pclsync/pfs.h +++ b/pclsync/pfs.h @@ -142,27 +142,27 @@ typedef struct { // FIXME: wtf... extern PSYNC_THREAD const char *psync_thread_name; -static inline void pfs_do_lock_file(psync_openfile_t *of, const char *file, +static inline int pfs_do_lock_file(psync_openfile_t *of, const char *file, unsigned long line) { if (unlikely(pthread_mutex_trylock(&of->mutex))) { struct timespec tm; clock_gettime(CLOCK_REALTIME, &tm); tm.tv_sec += 60; if (pthread_mutex_timedlock(&of->mutex, &tm)) { - pdbg_logf(D_BUG, - "could not lock mutex of file %s taken in %s:%lu by thread %s, " - "aborting", + pdbg_logf(D_ERROR, + "could not lock mutex of file %s taken in %s:%lu by thread %s", of->currentname, of->lockfile, of->lockline, of->lockthread); - abort(); + return -1; } } of->lockfile = file; of->lockthread = psync_thread_name; of->lockline = line; + return 0; } #else -static inline void pfs_lock_file(psync_openfile_t *of) { - pthread_mutex_lock(&of->mutex); +static inline int pfs_lock_file(psync_openfile_t *of) { + return pthread_mutex_lock(&of->mutex); } #endif diff --git a/test_deadlock_forced b/test_deadlock_forced new file mode 100755 index 0000000000000000000000000000000000000000..8b1963e69fd86caa2e19a807336d1d5e4a8e7636 GIT binary patch literal 21408 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 D$iP8D literal 0 HcmV?d00001 diff --git a/tests/fault-inject/test_deadlock_forced.c b/tests/fault-inject/test_deadlock_forced.c new file mode 100644 index 0000000..7b8ec96 --- /dev/null +++ b/tests/fault-inject/test_deadlock_forced.c @@ -0,0 +1,116 @@ +/* + * Fault injection test: Force lock ordering violation in old code + * + * This test uses strategic delays to force the deadlock scenario: + * Thread 1: holds psql, tries file mutex + * Thread 2: holds file mutex, tries psql + */ + +#include +#include +#include +#include + +typedef struct { + pthread_mutex_t mutex; +} psync_openfile_t; + +static pthread_mutex_t psql_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_barrier_t barrier; + +static void psql_lock() { pthread_mutex_lock(&psql_mutex); } +static void psql_unlock() { pthread_mutex_unlock(&psql_mutex); } +static int psql_trylock() { return pthread_mutex_trylock(&psql_mutex); } + +// OLD implementation with retry loop +static void pfs_get_both_locks_OLD(psync_openfile_t *of) { +retry: + psql_lock(); + if (pthread_mutex_trylock(&of->mutex)) { + psql_unlock(); + pthread_mutex_lock(&of->mutex); + if (psql_trylock()) { + pthread_mutex_unlock(&of->mutex); + usleep(1000); + goto retry; + } + } +} + +static void *thread1_func(void *arg) { + psync_openfile_t *of = arg; + + // T1: Acquire psql first + psql_lock(); + printf("T1: acquired psql\n"); + + // Wait for T2 to acquire file mutex + pthread_barrier_wait(&barrier); + usleep(10000); + + // T1: Try to acquire file mutex (will block - T2 holds it) + printf("T1: trying file mutex...\n"); + pthread_mutex_lock(&of->mutex); + printf("T1: acquired file mutex\n"); + + pthread_mutex_unlock(&of->mutex); + psql_unlock(); + return NULL; +} + +static void *thread2_func(void *arg) { + psync_openfile_t *of = arg; + + // T2: Acquire file mutex first + pthread_mutex_lock(&of->mutex); + printf("T2: acquired file mutex\n"); + + // Signal T1 we have the file mutex + pthread_barrier_wait(&barrier); + usleep(10000); + + // T2: Try to acquire psql (will block - T1 holds it) + printf("T2: trying psql...\n"); + psql_lock(); + printf("T2: acquired psql\n"); + + psql_unlock(); + pthread_mutex_unlock(&of->mutex); + return NULL; +} + +int main() { + psync_openfile_t of; + pthread_mutex_init(&of.mutex, NULL); + pthread_barrier_init(&barrier, NULL, 2); + + printf("=== Testing OLD lock ordering (deadlock scenario) ===\n"); + + pthread_t t1, t2; + pthread_create(&t1, NULL, thread1_func, &of); + pthread_create(&t2, NULL, thread2_func, &of); + + // Wait with timeout + sleep(5); + + // Check if threads are still running (deadlocked) + void *ret1, *ret2; + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += 1; + + int r1 = pthread_timedjoin_np(t1, &ret1, &ts); + int r2 = pthread_timedjoin_np(t2, &ret2, &ts); + + if (r1 != 0 || r2 != 0) { + printf("\nDEADLOCK CONFIRMED: threads hung with opposite lock ordering\n"); + pthread_cancel(t1); + pthread_cancel(t2); + return 1; + } + + pthread_mutex_destroy(&of.mutex); + pthread_barrier_destroy(&barrier); + printf("\nPASS: no deadlock (unexpected)\n"); + return 0; +} diff --git a/tests/fault-inject/test_new_code.c b/tests/fault-inject/test_new_code.c new file mode 100644 index 0000000..5c0d69d --- /dev/null +++ b/tests/fault-inject/test_new_code.c @@ -0,0 +1,83 @@ +/* + * Fault injection test: Verify NEW code prevents deadlock + * + * Same scenario as forced deadlock test, but with new lock ordering. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include + +typedef struct { + pthread_mutex_t mutex; +} psync_openfile_t; + +static pthread_mutex_t psql_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_barrier_t barrier; + +static void psql_lock() { pthread_mutex_lock(&psql_mutex); } +static void psql_unlock() { pthread_mutex_unlock(&psql_mutex); } + +// NEW implementation: consistent lock ordering +static void pfs_get_both_locks_NEW(psync_openfile_t *of) { + psql_lock(); + pthread_mutex_lock(&of->mutex); +} + +static void *thread1_func(void *arg) { + psync_openfile_t *of = arg; + + for (int i = 0; i < 100; i++) { + pfs_get_both_locks_NEW(of); + pthread_mutex_unlock(&of->mutex); + psql_unlock(); + } + return NULL; +} + +static void *thread2_func(void *arg) { + psync_openfile_t *of = arg; + + for (int i = 0; i < 100; i++) { + pfs_get_both_locks_NEW(of); + pthread_mutex_unlock(&of->mutex); + psql_unlock(); + } + return NULL; +} + +int main() { + psync_openfile_t of; + pthread_mutex_init(&of.mutex, NULL); + + printf("=== Testing NEW lock ordering (deadlock-free) ===\n"); + + pthread_t t1, t2, t3, t4; + pthread_create(&t1, NULL, thread1_func, &of); + pthread_create(&t2, NULL, thread2_func, &of); + pthread_create(&t3, NULL, thread1_func, &of); + pthread_create(&t4, NULL, thread2_func, &of); + + // Wait with timeout + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += 5; + + void *ret; + int r1 = pthread_timedjoin_np(t1, &ret, &ts); + int r2 = pthread_timedjoin_np(t2, &ret, &ts); + int r3 = pthread_timedjoin_np(t3, &ret, &ts); + int r4 = pthread_timedjoin_np(t4, &ret, &ts); + + if (r1 != 0 || r2 != 0 || r3 != 0 || r4 != 0) { + printf("\nFAIL: threads hung (unexpected with new code)\n"); + return 1; + } + + pthread_mutex_destroy(&of.mutex); + printf("\nPASS: no deadlock with consistent lock ordering\n"); + return 0; +} diff --git a/tests/fault-inject/test_old_aggressive.c b/tests/fault-inject/test_old_aggressive.c new file mode 100644 index 0000000..a5e802f --- /dev/null +++ b/tests/fault-inject/test_old_aggressive.c @@ -0,0 +1,81 @@ +/* + * Test: OLD pfs_get_both_locks with retry loop (deadlock-prone) + * + * This reproduces the old implementation with more aggressive contention. + */ + +#include +#include +#include +#include +#include + +typedef struct { + pthread_mutex_t mutex; +} psync_openfile_t; + +static pthread_mutex_t psql_mutex = PTHREAD_MUTEX_INITIALIZER; + +static void psql_lock() { pthread_mutex_lock(&psql_mutex); } +static void psql_unlock() { pthread_mutex_unlock(&psql_mutex); } +static int psql_trylock() { return pthread_mutex_trylock(&psql_mutex); } + +static void pfs_get_both_locks_OLD(psync_openfile_t *of) { +retry: + psql_lock(); + if (pthread_mutex_trylock(&of->mutex)) { + psql_unlock(); + pthread_mutex_lock(&of->mutex); + if (psql_trylock()) { + pthread_mutex_unlock(&of->mutex); + usleep(1000); + goto retry; + } + } +} + +static volatile int timeout_flag = 0; +static volatile int iteration_count = 0; + +static void *timeout_thread(void *arg) { + sleep(10); + timeout_flag = 1; + printf("TIMEOUT: deadlock detected after 10 seconds (iterations: %d)\n", iteration_count); + exit(1); + return NULL; +} + +static void *thread_func(void *arg) { + psync_openfile_t *of = arg; + for (int i = 0; i < 10000; i++) { + if (timeout_flag) break; + pfs_get_both_locks_OLD(of); + __sync_fetch_and_add(&iteration_count, 1); + pthread_mutex_unlock(&of->mutex); + psql_unlock(); + // No sleep - maximize contention + } + return NULL; +} + +int main() { + psync_openfile_t of; + pthread_mutex_init(&of.mutex, NULL); + + pthread_t timeout_t; + pthread_create(&timeout_t, NULL, timeout_thread, NULL); + pthread_detach(timeout_t); + + pthread_t threads[8]; + for (int i = 0; i < 8; i++) { + pthread_create(&threads[i], NULL, thread_func, &of); + } + + for (int i = 0; i < 8; i++) { + pthread_join(threads[i], NULL); + } + + pthread_mutex_destroy(&of.mutex); + printf("UNEXPECTED: old code completed without deadlock (iterations: %d)\n", iteration_count); + return 0; +} diff --git a/tests/smoke-tests/smoke-test-pfs-locks.sh b/tests/smoke-tests/smoke-test-pfs-locks.sh new file mode 100755 index 0000000..93689de --- /dev/null +++ b/tests/smoke-tests/smoke-test-pfs-locks.sh @@ -0,0 +1,39 @@ +#!/bin/bash +# Concurrent write test for pfs_get_both_locks + +MOUNT="$HOME/pCloudDrive" +TESTFILE="$MOUNT/test_concurrent_$$" + +# Create test file +touch "$TESTFILE" || exit 1 + +# Function to write concurrently +write_worker() { + local id=$1 + for i in {1..50}; do + echo "worker $id line $i $(date +%s%N)" >> "$TESTFILE" + done +} + +# Launch 4 concurrent writers +for i in {1..4}; do + write_worker $i & +done + +# Wait for all to complete +wait + +# Verify +lines=$(wc -l < "$TESTFILE") +echo "Total lines written: $lines (expected 200)" + +# Cleanup +rm "$TESTFILE" + +if [ "$lines" -eq 200 ]; then + echo "PASS: concurrent writes completed without deadlock" + exit 0 +else + echo "FAIL: line count mismatch" + exit 1 +fi diff --git a/tests/unit-tests/test_pfs_lock_ordering.c b/tests/unit-tests/test_pfs_lock_ordering.c new file mode 100644 index 0000000..1fc0af8 --- /dev/null +++ b/tests/unit-tests/test_pfs_lock_ordering.c @@ -0,0 +1,51 @@ +/* + * Test: pfs_get_both_locks enforces consistent lock ordering + * + * Verifies that pfs_get_both_locks always acquires psql lock before + * file mutex, eliminating the retry loop and deadlock risk. + */ + +#include +#include +#include +#include + +typedef struct { + pthread_mutex_t mutex; +} psync_openfile_t; + +static pthread_mutex_t psql_mutex = PTHREAD_MUTEX_INITIALIZER; + +static void psql_lock() { pthread_mutex_lock(&psql_mutex); } +static void psql_unlock() { pthread_mutex_unlock(&psql_mutex); } + +static void pfs_get_both_locks(psync_openfile_t *of) { + psql_lock(); + pthread_mutex_lock(&of->mutex); +} + +static void *thread_func(void *arg) { + psync_openfile_t *of = arg; + for (int i = 0; i < 1000; i++) { + pfs_get_both_locks(of); + pthread_mutex_unlock(&of->mutex); + psql_unlock(); + } + return NULL; +} + +int main() { + psync_openfile_t of; + pthread_mutex_init(&of.mutex, NULL); + + pthread_t t1, t2; + pthread_create(&t1, NULL, thread_func, &of); + pthread_create(&t2, NULL, thread_func, &of); + + pthread_join(t1, NULL); + pthread_join(t2, NULL); + + pthread_mutex_destroy(&of.mutex); + printf("PASS: no deadlock\n"); + return 0; +}