Experimental deadlock detection code

This commit is contained in:
Richard Frith-Macdonald 2018-03-26 15:20:48 +01:00
parent 43673452a5
commit 05439fe15d
6 changed files with 671 additions and 66 deletions

View file

@ -1,3 +1,14 @@
2018-03-26 Richard Frith-Macdonald <rfm@gnu.org>
* Headers/Foundation/NSLock.h:
* Headers/Foundation/NSThread.h:
* Source/GSPrivate.h:
* Source/NSLock.m:
* Source/NSThread.m:
Experimental code to add support for tracing locks held by different
threads and to report deadlock situations where threads have
obtained locks in the wrong order and can never proceed.
2018-03-26 Richard Frith-Macdonald <rfm@gnu.org>
Source/NSBundle.m:

View file

@ -72,7 +72,7 @@ extern "C" {
@interface NSLock : NSObject <NSLocking>
{
#if GS_EXPOSE(NSLock)
@private
@protected
gs_mutex_t _mutex;
NSString *_name;
#endif
@ -123,7 +123,7 @@ extern "C" {
@interface NSCondition : NSObject <NSLocking>
{
#if GS_EXPOSE(NSCondition)
@private
@protected
gs_cond_t _condition;
gs_mutex_t _mutex;
NSString *_name;
@ -173,7 +173,7 @@ extern "C" {
@interface NSConditionLock : NSObject <NSLocking>
{
#if GS_EXPOSE(NSConditionLock)
@private
@protected
NSCondition *_condition;
int _condition_value;
NSString *_name;
@ -273,7 +273,7 @@ extern "C" {
@interface NSRecursiveLock : NSObject <NSLocking>
{
#if GS_EXPOSE(NSRecursiveLock)
@private
@protected
gs_mutex_t _mutex;
NSString *_name;
#endif
@ -320,7 +320,7 @@ extern "C" {
@end
#if !NO_GNUSTEP
typedef void NSLock_error_handler (id obj, SEL _cmd, BOOL stop);
typedef void NSLock_error_handler(id obj, SEL _cmd, BOOL stop, NSString *msg);
/** Code may replace this function pointer in order to intercept the normal
* logging of a deadlock.
*/

View file

@ -73,6 +73,9 @@ extern "C" {
void *_runLoopInfo; // Per-thread runloop related info.
#endif
#if GS_NONFRAGILE
# if defined(GS_NSThread_IVARS)
@public GS_NSThread_IVARS;
# endif
#else
/* Pointer to private additional data used to avoid breaking ABI
* when we don't have the non-fragile ABI available.
@ -367,6 +370,37 @@ GS_EXPORT BOOL GSRegisterCurrentThread (void);
* this method call is not safe. Posts an NSThreadWillExit
* notification. */
GS_EXPORT void GSUnregisterCurrentThread (void);
@interface NSThread (GSLockInfo)
/** Turns on/off tracing of locks for deadlock tracking. Use of this option
* introduces a major overhead processing lock operations, but enables
* detection of deadlocks between threads.
*/
+ (void) setTraceLocks: (BOOL)aFlag;
/** Returns an array containing a snapshot of threads which are active and
* waiting for a mutex.
*/
+ (NSMutableArray *) waitingThreads;
/* Removes the mutex (either as the one we are waiting for or as a held mutex.
* For internal use only ... do not call this method.<br />
*/
- (NSString *) mutexDrop: (id)mutex;
/* Converts a waiting mutex to a held one (if mutex is nil), or increments
* the recursion count of a mutex already held by this thread.<br />
* For internal use only ... do not call this method.
*/
- (NSString *) mutexHold: (id)mutex;
/* Register the mutex that the thread is waiting for.<br />
* For internal use only ... do not call this method.
*/
- (NSString *) mutexWait: (id)mutex;
@end
#endif
/*

View file

@ -599,6 +599,14 @@ NSUInteger
GSPrivateThreadID()
GS_ATTRIB_PRIVATE;
/* Used by NSThread.m to inform NSLock.m that normal locks should be
* traced (and therefore call private NSThread methods to perform the
* tracing.
*/
void
GSPrivateTraceLocks(BOOL aFlag)
GS_ATTRIB_PRIVATE;
/** Function to base64 encode data. The destination buffer must be of
* size (((length + 2) / 3) * 4) or more.
*/
@ -606,6 +614,5 @@ void
GSPrivateEncodeBase64(const uint8_t *src, NSUInteger length, uint8_t *dst)
GS_ATTRIB_PRIVATE;
#endif /* _GSPrivate_h_ */

View file

@ -23,6 +23,7 @@
*/
#import "common.h"
#include <pthread.h>
#import "GSPrivate.h"
#define gs_cond_t pthread_cond_t
@ -38,6 +39,29 @@
#import "Foundation/NSLock.h"
#import "Foundation/NSException.h"
#import "Foundation/NSThread.h"
#import "GSPThread.h"
static BOOL traceLocks = NO;
void
GSPrivateTraceLocks(BOOL aFlag)
{
traceLocks = (aFlag ? YES : NO);
}
#define CHKT(T,X) \
if (traceLocks) { \
NSString *msg = [T mutex ## X: self]; \
if (nil != msg) \
{ \
(*_NSLock_error_handler)(self, _cmd, YES, msg); \
} \
};
#define CHK(X) CHKT(GSCurrentThread(), X)
/*
* Methods shared between NSLock, NSRecursiveLock, and NSCondition
@ -130,19 +154,46 @@
return _name;\
}
#define MLOCK_TRACED \
{ \
NSThread *t = GSCurrentThread(); \
CHKT(t,Wait) \
int err = pthread_mutex_lock(&_mutex);\
if (EDEADLK == err)\
{\
CHKT(t,Drop) \
(*_NSLock_error_handler)(self, _cmd, YES, @"deadlock");\
}\
else if (err != 0)\
{\
CHKT(t,Drop) \
[NSException raise: NSLockException\
format: @"failed to lock mutex"];\
}\
CHKT(t,Hold) \
}
#define MLOCK_UNTRACED \
{ \
int err = pthread_mutex_lock(&_mutex);\
if (EDEADLK == err)\
{\
(*_NSLock_error_handler)(self, _cmd, YES, @"deadlock");\
}\
else if (err != 0)\
{\
[NSException raise: NSLockException\
format: @"failed to lock mutex"];\
}\
}
#define MLOCK \
- (void) lock\
{\
int err = pthread_mutex_lock(&_mutex);\
if (EINVAL == err)\
{\
[NSException raise: NSLockException\
format: @"failed to lock mutex"];\
}\
if (EDEADLK == err)\
{\
(*_NSLock_error_handler)(self, _cmd, YES);\
}\
if (traceLocks) \
MLOCK_TRACED \
else \
MLOCK_UNTRACED \
}
#define MLOCKBEFOREDATE \
@ -153,6 +204,7 @@
int err = pthread_mutex_trylock(&_mutex);\
if (0 == err)\
{\
CHK(Hold) \
return YES;\
}\
sched_yield();\
@ -164,7 +216,15 @@
- (BOOL) tryLock\
{\
int err = pthread_mutex_trylock(&_mutex);\
return (0 == err) ? YES : NO;\
if (0 == err) \
{ \
CHK(Hold) \
return YES; \
} \
else \
{ \
return NO;\
} \
}
#define MUNLOCK \
@ -175,6 +235,7 @@
[NSException raise: NSLockException\
format: @"failed to unlock mutex"];\
}\
CHK(Drop) \
}
static pthread_mutex_t deadlock;
@ -185,10 +246,10 @@ static pthread_mutexattr_t attr_recursive;
/*
* OS X 10.5 compatibility function to allow debugging deadlock conditions.
*/
void _NSLockError(id obj, SEL _cmd, BOOL stop)
void _NSLockError(id obj, SEL _cmd, BOOL stop, NSString *msg)
{
NSLog(@"*** -[%@ %@]: deadlock (%@)", [obj class],
NSStringFromSelector(_cmd), obj);
NSLog(@"*** -[%@ %@]: %@ (%@)", [obj class], NSStringFromSelector(_cmd),
msg, obj);
NSLog(@"*** Break on _NSLockError() to debug.");
if (YES == stop)
pthread_mutex_lock(&deadlock);
@ -267,11 +328,12 @@ MLOCK
int err = pthread_mutex_trylock(&_mutex);
if (0 == err)
{
if (traceLocks) CHK(Hold)
return YES;
}
if (EDEADLK == err)
{
(*_NSLock_error_handler)(self, _cmd, NO);
(*_NSLock_error_handler)(self, _cmd, NO, @"deadlock");
}
sched_yield();
} while ([limit timeIntervalSinceNow] > 0);
@ -367,33 +429,70 @@ MUNLOCK
- (void) wait
{
pthread_cond_wait(&_condition, &_mutex);
if (traceLocks)
{
NSThread *t = GSCurrentThread();
CHKT(t,Drop)
CHKT(t,Wait)
pthread_cond_wait(&_condition, &_mutex);
CHKT(t,Hold)
}
else
{
pthread_cond_wait(&_condition, &_mutex);
}
}
- (BOOL) waitUntilDate: (NSDate*)limit
{
NSTimeInterval t = [limit timeIntervalSince1970];
NSTimeInterval ti = [limit timeIntervalSince1970];
double secs, subsecs;
struct timespec timeout;
int retVal = 0;
// Split the float into seconds and fractions of a second
subsecs = modf(t, &secs);
subsecs = modf(ti, &secs);
timeout.tv_sec = secs;
// Convert fractions of a second to nanoseconds
timeout.tv_nsec = subsecs * 1e9;
retVal = pthread_cond_timedwait(&_condition, &_mutex, &timeout);
/* NB. On timeout the lock is still held even through condition is not met
*/
if (retVal == 0)
if (traceLocks)
{
return YES;
NSThread *t = GSCurrentThread();
CHKT(t,Drop)
retVal = pthread_cond_timedwait(&_condition, &_mutex, &timeout);
if (retVal == 0)
{
CHKT(t,Hold)
return YES;
}
if (retVal == ETIMEDOUT)
{
CHKT(t,Hold)
return NO;
}
}
else if (retVal == EINVAL)
else
{
retVal = pthread_cond_timedwait(&_condition, &_mutex, &timeout);
if (retVal == 0)
{
return YES;
}
if (retVal == ETIMEDOUT)
{
return NO;
}
}
if (retVal == EINVAL)
{
NSLog(@"Invalid arguments to pthread_cond_timedwait");
}
return NO;
}
@ -434,6 +533,8 @@ MUNLOCK
else
{
_condition_value = value;
[_condition setName:
[NSString stringWithFormat: @"condition-for-lock-%p", self]];
}
}
return self;
@ -468,21 +569,21 @@ MUNLOCK
{
if (NO == [_condition lockBeforeDate: limitDate])
{
return NO;
return NO; // Not locked
}
if (condition_to_meet == _condition_value)
{
return YES;
return YES; // Keeping the lock
}
while ([_condition waitUntilDate: limitDate])
{
if (condition_to_meet == _condition_value)
{
return YES; // KEEP THE LOCK
return YES; // Keeping the lock
}
}
[_condition unlock];
return NO;
return NO; // Not locked
}
MNAME
@ -521,3 +622,62 @@ MNAME
}
@end
/* Versions of the lock classes where the locking is unconditionally traced
*/
@interface GSTracedRecursiveLock : NSRecursiveLock
@end
@interface GSTracedLock : NSLock
@end
#undef CHKT
#define CHKT(T,X) \
{ \
NSString *msg = [T mutex ## X: self]; \
if (nil != msg) \
{ \
(*_NSLock_error_handler)(self, _cmd, YES, msg); \
} \
}
#undef MLOCK
#define MLOCK \
- (void) lock\
MLOCK_TRACED
@implementation GSTracedLock
MLOCK
MLOCKBEFOREDATE
MTRYLOCK
MUNLOCK
@end
@implementation GSTracedRecursiveLock
MLOCK
MLOCKBEFOREDATE
MTRYLOCK
MUNLOCK
@end
/* Versions of the lock classes where the locking is never traced
*/
#undef CHKT
#define CHKT(T,X)
#undef MLOCK
#define MLOCK \
- (void) lock\
MLOCK_UNTRACED
@implementation GSUntracedLock
MLOCK
MLOCKBEFOREDATE
MTRYLOCK
MUNLOCK
@end
@implementation GSUntracedRecursiveLock
MLOCK
MLOCKBEFOREDATE
MTRYLOCK
MUNLOCK
@end

View file

@ -32,7 +32,24 @@
*/
#import "common.h"
#import "GSPThread.h"
/** Structure for holding lock information for a thread.
*/
typedef struct {
pthread_spinlock_t spin; /* protect access to struct members */
NSMapTable *held; /* all locks/conditions held by thread */
id wait; /* the lock/condition we are waiting for */
} GSLockInfo;
#define EXPOSE_NSThread_IVARS 1
#define GS_NSThread_IVARS \
pthread_t _pthreadID; \
NSUInteger _threadID; \
GSLockInfo _lockInfo
#ifdef HAVE_NANOSLEEP
# include <time.h>
#endif
@ -42,9 +59,6 @@
#ifdef HAVE_SYS_RESOURCE_H
# include <sys/resource.h>
#endif
#ifdef HAVE_PTHREAD_H
# include <pthread.h>
#endif
#if defined(HAVE_SYS_FILE_H)
# include <sys/file.h>
@ -65,8 +79,10 @@
#endif
#import "Foundation/NSException.h"
#import "Foundation/NSHashTable.h"
#import "Foundation/NSThread.h"
#import "Foundation/NSLock.h"
#import "Foundation/NSMapTable.h"
#import "Foundation/NSNotification.h"
#import "Foundation/NSNotificationQueue.h"
#import "Foundation/NSRunLoop.h"
@ -88,6 +104,15 @@
# include <sys/types.h>
#endif
#define GSInternal NSThreadInternal
#include "GSInternal.h"
GS_PRIVATE_INTERNAL(NSThread)
#define pthreadID (internal->_pthreadID)
#define threadID (internal->_threadID)
#define lockInfo (internal->_lockInfo)
#if defined(HAVE_PTHREAD_MAIN_NP)
# define IS_MAIN_PTHREAD (pthread_main_np() == 1)
#elif defined(HAVE_GETTID)
@ -181,12 +206,17 @@ static int SetThreadName(DWORD dwThreadID, const char *threadName)
#endif
@interface NSThread (Activation)
- (void) _makeThreadActive;
@end
@interface NSAutoreleasePool (NSThread)
+ (void) _endThread: (NSThread*)thread;
@end
static Class threadClass = Nil;
static NSNotificationCenter *nc = nil;
static Class threadClass = Nil;
static NSNotificationCenter *nc = nil;
static BOOL traceLocks = NO;
/**
* This class performs a dual function ...
@ -385,6 +415,10 @@ static NSThread *defaultThread;
static pthread_key_t thread_object_key;
static NSHashTable *_activeBlocked = nil;
static NSHashTable *_activeThreads = nil;
static pthread_mutex_t _activeLock = PTHREAD_MUTEX_INITIALIZER;
/**
* pthread_t is an opaque type. It might be a scalar type or
* some kind of struct depending on the implementation, so we
@ -496,7 +530,8 @@ static const NSMapTableKeyCallBacks _boxedPthreadKeyCallBacks =
* thred if called from within the late-cleanup function.
*/
static NSMapTable *_exitingThreads = nil;
static NSLock *_exitingThreadsLock;
static pthread_mutex_t _exitingThreadsLock = PTHREAD_MUTEX_INITIALIZER;
/**
* Called before late cleanup is run and inserts the NSThread object into the
@ -507,9 +542,9 @@ static NSLock *_exitingThreadsLock;
static inline void _willLateUnregisterThread(NSValue *boxedThread,
NSThread *specific)
{
[_exitingThreadsLock lock];
pthread_mutex_lock(&_exitingThreadsLock);
/* The map table is created lazily/late so that the NSThread
* +initilize method can be called without causing other
* +initialize method can be called without causing other
* classes to be initialized.
* NB this locked section cannot be protected by an exception handler
* because the exception handler stores information in the current
@ -522,7 +557,7 @@ static inline void _willLateUnregisterThread(NSValue *boxedThread,
}
NSMapInsert(_exitingThreads, (const void*)boxedThread,
(const void*)specific);
[_exitingThreadsLock unlock];
pthread_mutex_unlock(&_exitingThreadsLock);
}
/**
@ -536,12 +571,12 @@ static inline void _didLateUnregisterCurrentThread(NSValue *boxedThread)
* because the exception handler stores information in the current
* thread variables ... which causes recursion.
*/
[_exitingThreadsLock lock];
pthread_mutex_lock(&_exitingThreadsLock);
if (nil != _exitingThreads)
{
NSMapRemove(_exitingThreads, (const void*)boxedThread);
}
[_exitingThreadsLock unlock];
pthread_mutex_unlock(&_exitingThreadsLock);
}
/*
@ -571,7 +606,7 @@ static void exitedThread(void *thread)
if (0 == thread)
{
/* On some systems this is called with a null thread pointer,
* so try to ger the NSThread object for the current thread.
* so try to get the NSThread object for the current thread.
*/
thread = pthread_getspecific(thread_object_key);
if (0 == thread)
@ -628,22 +663,22 @@ GSCurrentThread(void)
*/
if (nil != _exitingThreads)
{
[_exitingThreadsLock lock];
pthread_mutex_lock(&_exitingThreadsLock);
thr = NSMapGet(_exitingThreads, (const void*)selfThread);
[_exitingThreadsLock unlock];
pthread_mutex_unlock(&_exitingThreadsLock);
}
DESTROY(selfThread);
}
if (nil == thr)
{
GSRegisterCurrentThread();
thr = pthread_getspecific(thread_object_key);
if ((nil == defaultThread) && IS_MAIN_PTHREAD)
if (nil == thr)
{
defaultThread = RETAIN(thr);
GSRegisterCurrentThread();
thr = pthread_getspecific(thread_object_key);
if ((nil == defaultThread) && IS_MAIN_PTHREAD)
{
defaultThread = RETAIN(thr);
}
}
assert(nil != thr && "No main thread");
}
assert(nil != thr && "No main thread");
return thr;
}
@ -728,11 +763,34 @@ gnustep_base_thread_callback(void)
}
}
@implementation NSThread (Activation)
- (void) _makeThreadActive
{
threadID = GSPrivateThreadID();
pthread_mutex_lock(&_activeLock);
/* The hash table is created lazily/late so that the NSThread
* +initialize method can be called without causing other
* classes to be initialized.
* NB this locked section cannot be protected by an exception handler
* because the exception handler stores information in the current
* thread variables ... which causes recursion.
*/
if (nil == _activeThreads)
{
_activeThreads = NSCreateHashTable(
NSNonRetainedObjectHashCallBacks, 100);
}
NSHashInsert(_activeThreads, (const void*)self);
pthread_mutex_unlock(&_activeLock);
}
@end
@implementation NSThread
static void
setThreadForCurrentThread(NSThread *t)
{
[t _makeThreadActive];
pthread_setspecific(thread_object_key, t);
gnustep_base_thread_callback();
}
@ -761,7 +819,6 @@ unregisterActiveThread(NSThread *thread)
[(GSRunLoopThreadInfo*)thread->_runLoopInfo invalidate];
RELEASE(thread);
pthread_setspecific(thread_object_key, nil);
}
}
@ -781,6 +838,7 @@ unregisterActiveThread(NSThread *thread)
{
t = [self new];
t->_active = YES;
[t _makeThreadActive];
pthread_setspecific(thread_object_key, t);
GS_CONSUMED(t);
if (defaultThread != nil && t != defaultThread)
@ -855,7 +913,6 @@ unregisterActiveThread(NSThread *thread)
* initialising NSThread.
*/
threadClass = self;
_exitingThreadsLock = [NSLock new];
GSCurrentThread();
}
}
@ -996,17 +1053,39 @@ unregisterActiveThread(NSThread *thread)
}
}
DESTROY(_gcontext);
if (_activeThreads)
{
pthread_mutex_lock(&_activeLock);
NSHashRemove(_activeThreads, self);
pthread_mutex_unlock(&_activeLock);
}
if (GS_EXISTS_INTERNAL)
{
pthread_spin_lock(&lockInfo.spin);
DESTROY(lockInfo.held);
lockInfo.wait = nil;
pthread_spin_unlock(&lockInfo.spin);
pthread_spin_destroy(&lockInfo.spin);
if (internal != nil)
{
GS_DESTROY_INTERNAL(NSThread);
}
}
[super dealloc];
}
- (NSString*) description
{
return [NSString stringWithFormat: @"%@{name = %@, num = %"PRIuPTR"}",
[super description], _name, GSPrivateThreadID()];
[super description], _name, threadID];
}
- (id) init
{
GS_CREATE_INTERNAL(NSThread);
pthread_spin_init(&lockInfo.spin, 0);
lockInfo.held = NSCreateMapTable(NSNonRetainedObjectMapKeyCallBacks,
NSObjectMapValueCallBacks, 10);
init_autorelease_thread_vars(&_autorelease_vars);
return self;
}
@ -1015,11 +1094,13 @@ unregisterActiveThread(NSThread *thread)
selector: (SEL)aSelector
object: (id)anArgument
{
/* initialize our ivars. */
_selector = aSelector;
_target = RETAIN(aTarget);
_arg = RETAIN(anArgument);
init_autorelease_thread_vars(&_autorelease_vars);
if (nil != (self = [self init]))
{
/* initialize our ivars. */
_selector = aSelector;
_target = RETAIN(aTarget);
_arg = RETAIN(anArgument);
}
return self;
}
@ -1189,7 +1270,6 @@ nsthreadLauncher(void *thread)
- (void) start
{
pthread_attr_t attr;
pthread_t thr;
if (_active == YES)
{
@ -1221,7 +1301,7 @@ nsthreadLauncher(void *thread)
*/
RETAIN(self);
/* Mark the thread as active whiul it's running.
/* Mark the thread as active while it's running.
*/
_active = YES;
@ -1238,7 +1318,7 @@ nsthreadLauncher(void *thread)
{
pthread_attr_setstacksize(&attr, _stackSize);
}
if (pthread_create(&thr, &attr, nsthreadLauncher, self))
if (pthread_create(&pthreadID, &attr, nsthreadLauncher, self))
{
DESTROY(self);
[NSException raise: NSInternalInconsistencyException
@ -1267,6 +1347,319 @@ nsthreadLauncher(void *thread)
@implementation NSThread (GSLockInfo)
static NSString *
lockInfoErr(NSString *str)
{
if (traceLocks)
{
return str;
}
return nil;
}
+ (NSMutableArray *) waitingThreads
{
NSMutableArray *a;
pthread_mutex_lock(&_activeLock);
{
NSHashEnumerator enumerator;
NSUInteger count = [_activeThreads count];
NSUInteger index;
GS_BEGINITEMBUF(objects, count, NSThread*);
enumerator = NSEnumerateHashTable(_activeThreads);
index = 0;
while (index < count
&& (objects[index] = NSNextHashEnumeratorItem(&enumerator)) != nil)
{
if (YES == objects[index]->_active
&& nil != GSIVar(objects[index], _lockInfo.wait))
{
index++; // Currently active and waiting
}
}
NSEndHashTableEnumeration(&enumerator);
a = [[NSMutableArray alloc] initWithObjects: objects count: index];
GS_ENDITEMBUF();
}
pthread_mutex_unlock(&_activeLock);
return AUTORELEASE(a);
}
- (NSString *) mutexDrop: (id)mutex
{
if (GS_EXISTS_INTERNAL && traceLocks)
{
GSLockInfo *li = &lockInfo;
GSStackTrace *stck;
int err;
err = pthread_spin_lock(&li->spin);
if (EDEADLK == err) return lockInfoErr(@"thread spin lock deadlocked");
if (EINVAL == err) return lockInfoErr(@"thread spin lock invalid");
if (mutex == li->wait)
{
/* The mutex was being waited for ... simply remove it.
*/
li->wait = nil;
}
else if ((stck = NSMapGet(li->held, (void*)mutex)) != nil)
{
/* The mutex was being held ... if the recursion count was zero
* we remove it (otherwise the count is decreased).
*/
if (stck->recursion-- == 0)
{
NSMapRemove(li->held, (void*)mutex);
//fprintf(stderr, "%lu: Drop %p (final) %lu\n", (unsigned long)_threadID, mutex, [li->held count]);
}
else
{
//fprintf(stderr, "%lu: Drop %p (%lu) %lu\n", (unsigned long)threadID, mutex, (unsigned long)stck->recursion, [li->held count]);
}
}
else
{
//fprintf(stderr, "%lu: Drop %p (bad) %lu\n", (unsigned long)threadID, mutex, [li->held count]);
pthread_spin_unlock(&li->spin);
return lockInfoErr(
@"attempt to unlock mutex not locked by this thread");
}
pthread_spin_unlock(&li->spin);
return nil;
}
return lockInfoErr(@"thread not active");
}
- (NSString *) mutexHold: (id)mutex
{
if (GS_EXISTS_INTERNAL && traceLocks)
{
GSLockInfo *li = &lockInfo;
GSStackTrace *stck;
int err;
err = pthread_spin_lock(&li->spin);
if (EDEADLK == err) return lockInfoErr(@"thread spin lock deadlocked");
if (EINVAL == err) return lockInfoErr(@"thread spin lock invalid");
if (nil == mutex)
{
mutex = li->wait;
if (nil == mutex)
{
pthread_spin_unlock(&li->spin);
return lockInfoErr(@"attempt to hold nil mutex");
}
}
else if (nil != li->wait && mutex != li->wait)
{
pthread_spin_unlock(&li->spin);
return lockInfoErr(@"attempt to hold mutex without waiting for it");
}
stck = NSMapGet(li->held, (void*)mutex);
if (nil == stck)
{
stck = [GSStackTrace new];
NSMapInsert(li->held, (void*)mutex, (void*)stck);
RELEASE(stck);
//fprintf(stderr, "%lu: Hold %p (initial) %lu\n", (unsigned long)threadID, mutex, [li->held count]);
}
else
{
stck->recursion++;
//fprintf(stderr, "%lu: Hold %p (%lu) %lu\n", (unsigned long)threadID, mutex, (unsigned long)stck->recursion, [li->held count]);
}
li->wait = nil;
pthread_spin_unlock(&li->spin);
return nil;
}
return lockInfoErr(@"thread not active");
}
- (NSString *) mutexWait: (id)mutex
{
if (GS_EXISTS_INTERNAL && traceLocks)
{
GSLockInfo *li = &lockInfo;
int err;
err = pthread_spin_lock(&li->spin);
if (EDEADLK == err) return lockInfoErr(@"thread spin lock deadlocked");
if (EINVAL == err) return lockInfoErr(@"thread spin lock invalid");
if (nil != li->wait)
{
NSString *msg = [NSString stringWithFormat:
@ "-mutexWait: for %@ when already waiting for %@",
mutex, li->wait];
pthread_spin_unlock(&li->spin);
return lockInfoErr(msg);
}
li->wait = mutex;
pthread_spin_unlock(&li->spin);
pthread_mutex_lock(&_activeLock);
if (nil == _activeBlocked)
{
_activeBlocked = NSCreateHashTable(
NSNonRetainedObjectHashCallBacks, 100);
}
else
{
NSResetHashTable(_activeBlocked);
}
NSMutableArray *dependencies = nil;
id want = mutex;
BOOL done = NO;
while (NO == done)
{
NSHashEnumerator enumerator;
NSThread *found = nil;
NSThread *th;
enumerator = NSEnumerateHashTable(_activeThreads);
while ((th = NSNextHashEnumeratorItem(&enumerator)) != nil)
{
GSLockInfo *info = &GSIVar(th, _lockInfo);
if (YES == th->_active && nil != info->wait)
{
GSStackTrace *stck;
pthread_spin_lock(&info->spin);
if (nil != info->wait
&& nil != (stck = NSMapGet(info->held, (const void*)want)))
{
found = th;
want = info->wait;
if (nil == dependencies)
{
dependencies = [NSMutableArray new];
}
[dependencies addObject: found]; // thread
[dependencies addObject: want]; // mutex
[dependencies addObject: stck]; // stacktrace
/* NB. breaking out here holds the spin lock so that
* the lock state of each dependency thread is
* preserved (if we don't have a deadlock, we get a
* consistent snapshot of the threads and their locks).
* We therefore have to unlock the threads when done.
*/
break;
}
pthread_spin_unlock(&info->spin);
}
}
NSEndHashTableEnumeration(&enumerator);
if (nil == found)
{
/* There is no thread blocked on the mutex we are checking,
* so we can't have a deadlock.
*/
DESTROY(dependencies);
done = YES;
}
else
{
if (found == self
|| NULL != NSHashGet(_activeBlocked,(const void*)found))
{
/* The found thread is the current one or in the blocked set
* so we have a deadlock.
*/
done = YES;
}
else
{
NSHashInsert(_activeBlocked, (const void*)found);
/* Continue to find the next dependency.
*/
}
}
}
/* Ensure any locked threads are unlocked again.
*/
if (NSCountHashTable(_activeBlocked) > 0)
{
NSHashEnumerator enumerator;
NSThread *th;
enumerator = NSEnumerateHashTable(_activeThreads);
while ((th = NSNextHashEnumeratorItem(&enumerator)) != nil)
{
GSLockInfo *info = &GSIVar(th, _lockInfo);
pthread_spin_unlock(&info->spin);
}
NSEndHashTableEnumeration(&enumerator);
}
pthread_mutex_unlock(&_activeLock);
if (nil != dependencies)
{
NSUInteger count;
NSUInteger index = 0;
NSMutableString *m;
[NSThread setTraceLocks: NO];
m = [NSMutableString stringWithCapacity: 1000];
[m appendFormat: @"Deadlock on %@\n", mutex];
count = [dependencies count];
while (index < count)
{
NSArray *symbols;
NSThread *thread;
NSUInteger frameCount;
mutex = [dependencies objectAtIndex: index++];
thread = [dependencies objectAtIndex: index++];
symbols = [[dependencies objectAtIndex: index++] symbols];
frameCount = [symbols count];
if (frameCount > 0)
{
NSUInteger i;
[m appendFormat: @" depends on %@\n blocked by %@\n at\n",
mutex, thread];
for (i = 0; i < frameCount; i++)
{
[m appendFormat: @" %@\n", [symbols objectAtIndex: i]];
}
}
else
{
[m appendFormat: @" depends on %@\n blocked by %@\n",
mutex, thread];
}
}
DESTROY(dependencies);
/* NB. Return m directly because we have turned off tracing to
* avoid recursion, and don't want lockInfoErr() to stop the
* error being ruturned.
*/
return m;
}
return nil;
}
return lockInfoErr(@"thread not active");
}
+ (void) setTraceLocks: (BOOL)aFlag
{
traceLocks = (aFlag ? YES : NO); // Update our variable ...
GSPrivateTraceLocks(traceLocks); // and tell NSLock too.
}
@end
@implementation GSRunLoopThreadInfo
- (void) addPerformer: (id)performer
{