mergerfs/vendored/libfuse/include/mutex_debug.hpp
Antonio S. Musumeci 8baf274cde Replace two-statement mutex_lockguard macro with single RAII declaration
The old macro expanded to two statements (mutex_lock + DEFER unlock).
In an unbraced if/else only the lock would be conditional while the
DEFER always executed — a latent double-unlock / unbalanced-unlock bug.

Add ScopedMutexLock to both debug and ndebug headers:
 - debug: stores __FILE__/__func__/__LINE__ from the call site and
   passes them to _mutex_lock/_mutex_unlock for diagnostics
 - ndebug: lightweight wrapper around mutex_lock/mutex_unlock

Redefine mutex_lockguard to create a single uniquely-named
ScopedMutexLock variable (via __COUNTER__), making it safe in any
statement context. Remove the now-unused scope_guard include.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 21:42:24 -04:00

159 lines
3.6 KiB
C++

#pragma once
#include "fmt/core.h"
#include <ctime>
#include <cstdlib>
#include <pthread.h>
#ifndef PTHREAD_MUTEX_ADAPTIVE_NP
# define PTHREAD_MUTEX_ADAPTIVE_NP PTHREAD_MUTEX_NORMAL
#endif
#define mutex_init(M) _mutex_init((M),__FILE__,__func__,__LINE__)
#define mutex_destroy(M) _mutex_destroy((M),__FILE__,__func__,__LINE__)
#define mutex_lock(M) _mutex_lock((M),__FILE__,__func__,__LINE__)
#define mutex_unlock(M) _mutex_unlock((M),__FILE__,__func__,__LINE__)
static
inline
void
_print_error_and_abort(const char *func1_,
const int errno_,
const char *file_,
const char *func2_,
const int linenum_)
{
fmt::println(stderr,
"ERROR: {} returned {} ({}) - ({}:{}:{})",
func1_,
errno_,
strerror(errno_),
file_,
func2_,
linenum_);
std::abort();
}
static
inline
void
_mutex_init(pthread_mutex_t &mutex_,
const char *file_,
const char *func_,
const int linenum_)
{
int rv;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_ADAPTIVE_NP);
rv = pthread_mutex_init(&mutex_,&attr);
if(rv != 0)
_print_error_and_abort(__func__,rv,file_,func_,linenum_);
pthread_mutexattr_destroy(&attr);
}
static
inline
void
_mutex_destroy(pthread_mutex_t &mutex_,
const char *file_,
const char *func_,
const int linenum_)
{
int rv;
rv = pthread_mutex_destroy(&mutex_);
if(rv != 0)
_print_error_and_abort(__func__,rv,file_,func_,linenum_);
}
static
inline
void
_mutex_lock(pthread_mutex_t &mutex_,
const char *file_,
const char *func_,
const int linenum_)
{
int rv;
int cnt = 0;
timespec timeout;
while(true)
{
clock_gettime(CLOCK_REALTIME,&timeout);
timeout.tv_nsec += 1000000 * 1; // 1ms
if(timeout.tv_nsec >= 1000000000)
{
timeout.tv_sec += 1;
timeout.tv_nsec -= 1000000000;
}
rv = pthread_mutex_timedlock(&mutex_,&timeout);
switch(rv)
{
case 0:
return;
case ETIMEDOUT:
cnt++;
fmt::println(stderr,
"NOTICE: pthread_mutex_timedlock expired - count={}; ({}:{}:{})",
cnt,
file_,
func_,
linenum_);
continue;
default:
_print_error_and_abort(__func__,rv,file_,func_,linenum_);
return;
}
}
}
static
inline
void
_mutex_unlock(pthread_mutex_t &mutex_,
const char *file_,
const char *func_,
const int linenum_)
{
int rv;
rv = pthread_mutex_unlock(&mutex_);
if(rv != 0)
_print_error_and_abort(__func__,rv,file_,func_,linenum_);
}
class ScopedMutexLock
{
private:
pthread_mutex_t &_mutex;
const char *_file;
const char *_func;
int _line;
public:
ScopedMutexLock(pthread_mutex_t &mutex_,
const char *file_,
const char *func_,
int line_)
: _mutex(mutex_), _file(file_), _func(func_), _line(line_)
{
_mutex_lock(_mutex,_file,_func,_line);
}
~ScopedMutexLock()
{
_mutex_unlock(_mutex,_file,_func,_line);
}
ScopedMutexLock(const ScopedMutexLock&) = delete;
ScopedMutexLock& operator=(const ScopedMutexLock&) = delete;
};