From 5517f48681f78995932eeac1fba12603c644d183 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=A8=E5=A4=B4=E4=BA=91?= Date: Sat, 6 Dec 2025 06:35:02 +0000 Subject: [PATCH] Fix FreeBSD segfault by unlocking mutex before destruction Root cause: FreeBSD's robust mutex implementation (libthr) maintains a per-thread robust list of mutexes. The error 'rb error 14' (EFAULT - Bad address) indicates that the robust list contained a dangling pointer to a destroyed mutex. When a mutex object is destroyed (via close() or clear()), if the mutex is still in the current thread's robust list, FreeBSD's libthr may try to access it later and encounter an invalid pointer, causing a segmentation fault. This happened in MutexTest.TryLockExceptionSafety because: 1. The test called try_lock() which successfully acquired the lock 2. The test ended without calling unlock() 3. The mutex destructor called close() 4. close() called pthread_mutex_destroy() on a mutex that was: - Still locked by the current thread, OR - Still in the thread's robust list Solution: Call pthread_mutex_unlock() before pthread_mutex_destroy() in both close() and clear() methods. This ensures: 1. The mutex is unlocked if we hold the lock 2. The mutex is removed from the thread's robust list 3. Subsequent pthread_mutex_destroy() is safe We ignore errors from pthread_mutex_unlock() because: - If we don't hold the lock, EPERM is expected and harmless - If the mutex is already unlocked, this is a no-op - Even if there's an error, we still want to proceed with cleanup This fix is platform-agnostic and should not affect Linux/QNX behavior, as both also use pthread robust mutexes with similar semantics. Fixes the segfault in MutexTest.TryLockExceptionSafety on FreeBSD 15. --- src/libipc/platform/posix/mutex.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/libipc/platform/posix/mutex.h b/src/libipc/platform/posix/mutex.h index 0df842b..8a4ac5f 100644 --- a/src/libipc/platform/posix/mutex.h +++ b/src/libipc/platform/posix/mutex.h @@ -150,6 +150,17 @@ public: void close() noexcept { if ((ref_ != nullptr) && (shm_ != nullptr) && (mutex_ != nullptr)) { + // Try to unlock the mutex before destroying it. + // This is important for robust mutexes on FreeBSD, which maintain + // a per-thread robust list. If we destroy a mutex while it's in + // the robust list (even if not locked), FreeBSD may encounter + // dangling pointers later, leading to segfaults. + // We ignore any errors from unlock() since: + // 1. If we don't hold the lock, EPERM is expected and harmless + // 2. If the mutex is already unlocked, this is a no-op + // 3. If there's an error, we still want to proceed with cleanup + ::pthread_mutex_unlock(mutex_); + if (shm_->name() != nullptr) { release_mutex(shm_->name(), [this] { auto self_ref = ref_->fetch_sub(1, std::memory_order_relaxed); @@ -171,6 +182,9 @@ public: void clear() noexcept { if ((shm_ != nullptr) && (mutex_ != nullptr)) { + // Try to unlock before destroying, same reasoning as in close() + ::pthread_mutex_unlock(mutex_); + if (shm_->name() != nullptr) { release_mutex(shm_->name(), [this] { int eno;