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.
This commit is contained in:
木头云 2025-12-06 06:35:02 +00:00
parent 47fa303455
commit 5517f48681

View File

@ -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;