/* * locks.c * * Created on: 16.07.2013 * Author: sr */ #include "locks.h" #include "helper.h" #include "../shared/timing.h" #ifdef _DEBUG #define MAXLOCKS (SERVER_MAX_CLIENTS * 2 + SERVER_MAX_ALTS + 200 + SERVER_MAX_IMAGES) #define MAXTHREADS (SERVER_MAX_CLIENTS + 100) #define LOCKLEN 60 typedef struct { void *lock; ticks locktime; char locked; pthread_t thread; int lockId; char name[LOCKLEN]; char where[LOCKLEN]; } debug_lock_t; typedef struct { pthread_t tid; ticks time; char name[LOCKLEN]; char where[LOCKLEN]; } debug_thread_t; int debugThreadCount = 0; static debug_lock_t locks[MAXLOCKS]; static debug_thread_t threads[MAXTHREADS]; static int init_done = 0; static pthread_spinlock_t initdestory; static int lockId = 0; static pthread_t watchdog = 0; static dnbd3_signal_t* watchdogSignal = NULL; static void *debug_thread_watchdog(void *something); int debug_spin_init(const char *name, const char *file, int line, pthread_spinlock_t *lock, int shared) { if ( !init_done ) { memset( locks, 0, MAXLOCKS * sizeof(debug_lock_t) ); memset( threads, 0, MAXTHREADS * sizeof(debug_thread_t) ); pthread_spin_init( &initdestory, PTHREAD_PROCESS_PRIVATE ); init_done = 1; } int first = -1; pthread_spin_lock( &initdestory ); for (int i = 0; i < MAXLOCKS; ++i) { if ( locks[i].lock == lock ) { logadd( LOG_ERROR, "Lock %p (%s) already initialized (%s:%d)\n", (void*)lock, name, file, line ); exit( 4 ); } if ( first == -1 && locks[i].lock == NULL ) first = i; } if ( first == -1 ) { logadd( LOG_ERROR, "No more free debug locks (%s:%d)\n", file, line ); pthread_spin_unlock( &initdestory ); debug_dump_lock_stats(); exit( 4 ); } locks[first].lock = (void*)lock; locks[first].locked = 0; snprintf( locks[first].name, LOCKLEN, "%s", name ); snprintf( locks[first].where, LOCKLEN, "I %s:%d", file, line ); pthread_spin_unlock( &initdestory ); return pthread_spin_init( lock, shared ); } int debug_spin_lock(const char *name, const char *file, int line, pthread_spinlock_t *lock) { debug_lock_t *l = NULL; pthread_spin_lock( &initdestory ); for (int i = 0; i < MAXLOCKS; ++i) { if ( locks[i].lock == lock ) { l = &locks[i]; break; } } pthread_spin_unlock( &initdestory ); if ( l == NULL ) { logadd( LOG_ERROR, "Tried to lock uninitialized lock %p (%s) at %s:%d\n", (void*)lock, name, file, line ); debug_dump_lock_stats(); exit( 4 ); } debug_thread_t *t = NULL; pthread_spin_lock( &initdestory ); for (int i = 0; i < MAXTHREADS; ++i) { if ( threads[i].tid != 0 ) continue; threads[i].tid = pthread_self(); timing_get( &threads[i].time ); snprintf( threads[i].name, LOCKLEN, "%s", name ); snprintf( threads[i].where, LOCKLEN, "%s:%d", file, line ); t = &threads[i]; break; } pthread_spin_unlock( &initdestory ); if ( t == NULL ) { logadd( LOG_ERROR, "Lock sanity check: Too many waiting threads for lock %p (%s) at %s:%d\n", (void*)lock, name, file, line ); exit( 4 ); } const int retval = pthread_spin_lock( lock ); pthread_spin_lock( &initdestory ); t->tid = 0; pthread_spin_unlock( &initdestory ); if ( l->locked ) { logadd( LOG_ERROR, "Lock sanity check: lock %p (%s) already locked at %s:%d\n", (void*)lock, name, file, line ); exit( 4 ); } l->locked = 1; timing_get( &l->locktime ); l->thread = pthread_self(); snprintf( l->where, LOCKLEN, "L %s:%d", file, line ); pthread_spin_lock( &initdestory ); l->lockId = ++lockId; pthread_spin_unlock( &initdestory ); return retval; } int debug_spin_trylock(const char *name, const char *file, int line, pthread_spinlock_t *lock) { debug_lock_t *l = NULL; pthread_spin_lock( &initdestory ); for (int i = 0; i < MAXLOCKS; ++i) { if ( locks[i].lock == lock ) { l = &locks[i]; break; } } pthread_spin_unlock( &initdestory ); if ( l == NULL ) { logadd( LOG_ERROR, "Tried to lock uninitialized lock %p (%s) at %s:%d\n", (void*)lock, name, file, line ); debug_dump_lock_stats(); exit( 4 ); } debug_thread_t *t = NULL; pthread_spin_lock( &initdestory ); for (int i = 0; i < MAXTHREADS; ++i) { if ( threads[i].tid != 0 ) continue; threads[i].tid = pthread_self(); timing_get( &threads[i].time ); snprintf( threads[i].name, LOCKLEN, "%s", name ); snprintf( threads[i].where, LOCKLEN, "%s:%d", file, line ); t = &threads[i]; break; } pthread_spin_unlock( &initdestory ); if ( t == NULL ) { logadd( LOG_ERROR, "Lock sanity check: Too many waiting threads for %p (%s) at %s:%d\n", (void*)lock, name, file, line ); exit( 4 ); } const int retval = pthread_spin_trylock( lock ); pthread_spin_lock( &initdestory ); t->tid = 0; pthread_spin_unlock( &initdestory ); if ( retval == 0 ) { if ( l->locked ) { logadd( LOG_ERROR, "Lock sanity check: lock %p (%s) already locked at %s:%d\n", (void*)lock, name, file, line ); exit( 4 ); } l->locked = 1; timing_get( &l->locktime ); l->thread = pthread_self(); snprintf( l->where, LOCKLEN, "L %s:%d", file, line ); pthread_spin_lock( &initdestory ); l->lockId = ++lockId; pthread_spin_unlock( &initdestory ); } return retval; } int debug_spin_unlock(const char *name, const char *file, int line, pthread_spinlock_t *lock) { debug_lock_t *l = NULL; pthread_spin_lock( &initdestory ); for (int i = 0; i < MAXLOCKS; ++i) { if ( locks[i].lock == lock ) { l = &locks[i]; break; } } pthread_spin_unlock( &initdestory ); if ( l == NULL ) { logadd( LOG_ERROR, "Tried to unlock uninitialized lock %p (%s) at %s:%d\n", (void*)lock, name, file, line ); exit( 4 ); } if ( !l->locked ) { logadd( LOG_ERROR, "Unlock sanity check: lock %p (%s) not locked at %s:%d\n", (void*)lock, name, file, line ); exit( 4 ); } l->locked = 0; l->thread = 0; snprintf( l->where, LOCKLEN, "U %s:%d", file, line ); int retval = pthread_spin_unlock( lock ); return retval; } int debug_spin_destroy(const char *name, const char *file, int line, pthread_spinlock_t *lock) { pthread_spin_lock( &initdestory ); for (int i = 0; i < MAXLOCKS; ++i) { if ( locks[i].lock == lock ) { if ( locks[i].locked ) { logadd( LOG_ERROR, "Tried to destroy lock %p (%s) at %s:%d when it is still locked\n", (void*)lock, name, file, line ); exit( 4 ); } locks[i].lock = NULL; snprintf( locks[i].where, LOCKLEN, "D %s:%d", file, line ); pthread_spin_unlock( &initdestory ); return pthread_spin_destroy( lock ); } } logadd( LOG_ERROR, "Tried to destroy non-existent lock %p (%s) at %s:%d\n", (void*)lock, name, file, line ); exit( 4 ); } void debug_dump_lock_stats() { declare_now; pthread_spin_lock( &initdestory ); printf( "\n **** LOCKS ****\n\n" ); for (int i = 0; i < MAXLOCKS; ++i) { if ( locks[i].lock == NULL ) continue; if ( locks[i].locked ) { printf( "* *** %s ***\n" "* Where: %s\n" "* When: %d secs ago\n" "* Locked: %d\n" "* Serial: %d\n" "* Thread: %d\n", locks[i].name, locks[i].where, (int)timing_diff( &locks[i].locktime, &now ), (int)locks[i].locked, locks[i].lockId, (int)locks[i].thread ); } else { printf( "* *** %s ***\n" "* Where: %s\n" "* Locked: %d\n", locks[i].name, locks[i].where, (int)locks[i].locked ); } } printf( "\n **** WAITING THREADS ****\n\n" ); for (int i = 0; i < MAXTHREADS; ++i) { if ( threads[i].tid == 0 ) continue; printf( "* *** Thread %d ***\n" "* Lock: %s\n" "* Where: %s\n" "* How long: %d secs\n", (int)threads[i].tid, threads[i].name, threads[i].where, (int)timing_diff( &threads[i].time, &now ) ); } pthread_spin_unlock( &initdestory ); } static void *debug_thread_watchdog(void *something UNUSED) { setThreadName( "debug-watchdog" ); while ( !_shutdown ) { if ( init_done ) { declare_now; pthread_spin_lock( &initdestory ); for (int i = 0; i < MAXTHREADS; ++i) { if ( threads[i].tid == 0 ) continue; const uint32_t diff = timing_diff( &threads[i].time, &now ); if ( diff > 6 && diff < 100000 ) { printf( "\n\n +++++++++ DEADLOCK ++++++++++++\n\n" ); pthread_spin_unlock( &initdestory ); debug_dump_lock_stats(); exit( 99 ); } } pthread_spin_unlock( &initdestory ); } if ( watchdogSignal == NULL || signal_wait( watchdogSignal, 5000 ) == SIGNAL_ERROR ) sleep( 5 ); } return NULL ; } #endif void debug_locks_start_watchdog() { #ifdef _DEBUG watchdogSignal = signal_new(); if ( 0 != thread_create( &watchdog, NULL, &debug_thread_watchdog, (void *)NULL ) ) { logadd( LOG_ERROR, "Could not start debug-lock watchdog." ); return; } #endif } void debug_locks_stop_watchdog() { #ifdef _DEBUG _shutdown = true; printf( "Killing debug watchdog...\n" ); pthread_spin_lock( &initdestory ); signal_call( watchdogSignal ); pthread_spin_unlock( &initdestory ); thread_join( watchdog, NULL ); signal_close( watchdogSignal ); #endif }