Add automatic unregistration of threads that have not been

been explicitly unregistered. This works by keeping around 
a map table with all threads currently undergoing cleanup, 
and using that as a fallback if pthread_getspecific would
not return the NSThread object from TLS. 


git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@39318 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
thebeing 2016-01-29 13:42:07 +00:00
parent 59081dd5c4
commit 3346a30b7c
3 changed files with 360 additions and 32 deletions

View file

@ -1,3 +1,12 @@
2016-01-29 Niels Grewe <niels.grewe@halbordnung.de>
* Source/NSThread.m: Automatic 'late' unregistration of thread objects: When
+exit is not called, we no longer log a warning and leak the thread object.
Instead, we add it to a map table, keyed under the current thread ID, and
use that in GSCurrentThread() to find the correct NSThread object if
pthread_getspecific wont return it to us.
* Tests/base/NSThread/late_unregister.m: Test case for late unregistration.
2016-01-21 Niels Grewe <niels.grewe@halbordnung.de>
* Source/Additions/GSMime.m: Fix folding of headers containing
@ -394,7 +403,7 @@
* Source/Additions/Unicode.m
* Tools/AGSOutput.m
return NULL or nil instead of NO where pointers are to
be returned
be returned
2015-05-26 Richard Frith-Macdonald <rfm@gnu.org>
@ -513,7 +522,7 @@
2015-03-10 Niels Grewe <niels.grewe@halbordnung.de>
* Source/GSTimSort.m: Fix a DoS vulnerability discovered in the
* Source/GSTimSort.m: Fix a DoS vulnerability discovered in the
Timsort algorithm. For information about the problem please refer to
http://www.envisage-project.eu/proving-android-java-and-python-sorting
-algorithm-is-broken-and-how-to-fix-it/
@ -576,7 +585,7 @@
2015-01-13 Marcus Mueller <znek@mulle-kybernetik.com>
* Headers/Foundation/Foundation.h: added NSUUID.h
* Headers/Foundation/Foundation.h: added NSUUID.h
2014-12-28 Wolfgang Lux <wolfgang.lux@gmail.com>
@ -756,7 +765,7 @@
* Tools/make_strings/GNUmakefile (CONFIG_SYSTEM_LIBS): Define
to the empty string to avoid linking the tools against
external libraries.
2014-06-30 Yavor Doganov <yavor@gnu.org>
* base.make.in: make base dependencies explicit only when statically

View file

@ -74,6 +74,7 @@
#import "Foundation/NSInvocation.h"
#import "Foundation/NSUserDefaults.h"
#import "Foundation/NSGarbageCollector.h"
#import "Foundation/NSValue.h"
#import "GSPrivate.h"
#import "GSRunLoopCtxt.h"
@ -126,7 +127,7 @@ GSPrivateThreadID()
}
#if 0
/*
/*
* NSThread setName: method for windows.
* FIXME ... This is code for the microsoft compiler;
* how do we make it work for gcc/clang?
@ -157,7 +158,7 @@ static int SetThreadName(DWORD dwThreadID, const char *threadName)
info.dwFlags = 0;
__try
{
{
RaiseException(MS_VC_EXCEPTION, 0,
sizeof(info)/sizeof(ULONG_PTR), (ULONG_PTR*)&info);
result = 0;
@ -391,41 +392,210 @@ static NSThread *defaultThread;
static pthread_key_t thread_object_key;
/**
* pthread_t is an opaque type. It might be a scalar type or
* some kind of struct depending on the implementation, so we
* need to wrap it up in an NSValue object if we want to pass
* it around.
* This follows the CoreFoundation 'create rule' and returns an object with
* a reference count of 1.
*/
static inline NSValue* NSValueCreateFromPthread(pthread_t thread)
{
return [[NSValue alloc] initWithBytes: &thread
objCType: @encode(pthread_t)];
}
/**
* Conversely, we need to be able to retrieve the pthread_t
* from an NSValue.
*/
static inline void _getPthreadFromNSValue(NSValue* value, pthread_t *thread_ptr)
{
NSCAssert(thread_ptr, @"No storage for thread reference");
# ifndef NS_BLOCK_ASSERTIONS
const char* enc = [value objCType];
NSCAssert(enc != NULL && (0 == strcmp(@encode(pthread_t),enc)),
@"Invalid NSValue container for thread reference");
# endif
[value getValue: (void*)thread_ptr];
}
/**
* This is the comparison function for boxed pthreads, as used by the
* NSMapTable containing them.
*/
static BOOL
_boxedPthreadIsEqual(NSMapTable *t,
const void* boxed,
const void* boxedOther)
{
pthread_t thread;
pthread_t otherThread;
_getPthreadFromNSValue(boxed, &thread);
_getPthreadFromNSValue(boxedOther, &otherThread);
return pthread_equal(thread, otherThread);
}
/**
* Since pthread_t is opaque, we cannot make any assumption about how
* to hash it. There are a few problems here:
* 1. Functions to obtain the thread ID of an arbitrary thread
* exist in the in the Win32 and some pthread APIs (GetThreadId() and
* pthread_getunique_np(), respectively), but there is no protable solution
* for this problem.
* 2. Even where pthread_getunique_np() is available, it might have different
* definitions, so it's not really robust to use it.
*
* For these reasons, we always return the same hash. That fulfills the API
* contract for NSMapTable (key-hash equality as a necessary condition for key
* equality), but makes things quite inefficient (linear search over all
* elements), so we need to keep the table small.
*/
static NSUInteger _boxedPthreadHash(NSMapTable* t, const void* value)
{
return 0;
}
/**
* Retain callback for boxed thread references.
*/
static void _boxedPthreadRetain(NSMapTable* t, const void* value)
{
[(NSValue*)value retain];
}
/**
* Release callback for boxed thread references.
*/
static void _boxedPthreadRelease(NSMapTable* t, void* value)
{
[(NSValue*)value release];
}
/**
* Description callback for boxed thread references.
*/
static NSString *_boxedPthreadDescribe(NSMapTable* t, const void* value)
{
return [(NSValue*)value description];
}
static const NSMapTableKeyCallBacks _boxedPthreadKeyCallBacks =
{
_boxedPthreadHash,
_boxedPthreadIsEqual,
_boxedPthreadRetain,
_boxedPthreadRelease,
_boxedPthreadDescribe,
NULL
};
/**
* This map table maintains a list of all threads currently undergoing
* cleanup. This is a required so that +currentThread can still find the
* thred if called from within the late-cleanup function.
*/
static NSMapTable *_exitingThreads;
static NSLock *_exitingThreadsLock;
/**
* Called before late cleanup is run and inserts the NSThread object into the
* table that is used by GSCurrentThread to find the thread if it is called
* during cleanup. The boxedThread variable contains a boxed reference to
* the result of calling pthread_self().
*/
static inline void _willLateUnregisterThread(NSValue *boxedThread,
NSThread *specific)
{
[_exitingThreadsLock lock];
NS_DURING
{
NSMapInsert(_exitingThreads, (const void*)boxedThread,
(const void*)specific);
}
NS_HANDLER
{
[_exitingThreadsLock unlock];
[localException raise];
}
NS_ENDHANDLER
[_exitingThreadsLock unlock];
}
/**
* Called after late cleanup has run. Will remove the current thread from
* the lookup table again. The boxedThread variable contains a boxed reference
* to the result of calling pthread_self().
*/
static inline void _didLateUnregisterCurrentThread(NSValue *boxedThread)
{
[_exitingThreadsLock lock];
NS_DURING
{
NSMapRemove(_exitingThreads, (const void*)boxedThread);
}
NS_HANDLER
{
[_exitingThreadsLock unlock];
[localException raise];
}
NS_ENDHANDLER
[_exitingThreadsLock unlock];
}
/*
* Forward declaration of the thread unregistration function
*/
static void
unregisterActiveThread(NSThread *thread);
/**
* Pthread cleanup call.
*
* We should normally not get here ... because threads should exit properly
* and clean up, so that this function doesn't get called. However if a
* thread terminates for some reason without calling the exit method, we
* can at least log it.
*
* We can't do anything more than that since at the point
* when this function is called, the thread specific data is no longer
* available, so the currentThread method will always fail and the
* repercussions of that would well be a crash.
*
* As a special case, we ignore the exit of the default thread ... that one
* will usually terminate without calling the exit method as it ends the
* whole process by returning from the 'main' function.
* we add it to a special lookup table that is used by GSCurrentThread() to
* obtain the NSThread object.
* We need to be a bit careful about this regarding object allocation because
* we must not call into NSAutoreleasePool unless the NSThread object can still
* be found using GSCurrentThread()
*/
static void exitedThread(void *thread)
{
if (thread != defaultThread)
{
NSUInteger tid;
[(NSThread*)thread retain];
NSValue *ref = NSValueCreateFromPthread(pthread_self());
_willLateUnregisterThread(ref, (NSThread*)thread);
/* We create a pool for all objects used during cleanup to go into.
*/
NSAutoreleasePool *arp = [NSAutoreleasePool new];
NS_DURING
{
unregisterActiveThread((NSThread*)thread);
}
NS_HANDLER
{
DESTROY(arp);
_didLateUnregisterCurrentThread(ref);
DESTROY(ref);
[(NSThread*)thread release];
}
NS_ENDHANDLER
DESTROY(arp);
#if defined(__MINGW__)
tid = (NSUInteger)GetCurrentThreadId();
#elif defined(HAVE_GETTID)
tid = (NSUInteger)syscall(SYS_gettid);
#elif defined(HAVE_PTHREAD_GETTHREADID_NP)
tid = (NSUInteger)pthread_getthreadid_np();
#else
tid = (NSUInteger)thread;
#endif
fprintf(stderr, "WARNING thread %"PRIuPTR
" terminated without calling +exit!\n", tid);
/* At this point threre shouldn't be any autoreleased objects lingering
* around anymore. So we may remove the thread from the lookup table.
*/
_didLateUnregisterCurrentThread(ref);
DESTROY(ref);
[(NSThread*)thread release];
}
}
@ -437,6 +607,24 @@ inline NSThread*
GSCurrentThread(void)
{
NSThread *thr = pthread_getspecific(thread_object_key);
if (nil == thr)
{
NSValue *selfThread = NSValueCreateFromPthread(pthread_self());
[_exitingThreadsLock lock];
NS_DURING
{
thr = NSMapGet(_exitingThreads, (const void*)selfThread);
}
NS_HANDLER
{
[_exitingThreadsLock unlock];
DESTROY(selfThread);
[localException raise];
}
NS_ENDHANDLER
[_exitingThreadsLock unlock];
DESTROY(selfThread);
}
if (nil == thr)
{
GSRegisterCurrentThread();
@ -538,7 +726,6 @@ gnustep_base_thread_callback(void)
}
}
@implementation NSThread
static void
@ -662,8 +849,11 @@ unregisterActiveThread(NSThread *thread)
* Ensure that the default thread exists.
*/
threadClass = self;
_exitingThreads = NSCreateMapTable(_boxedPthreadKeyCallBacks,
NSObjectMapValueCallBacks, 10);
_exitingThreadsLock = [NSLock new];
GSCurrentThread();
}
}
@ -1514,7 +1704,7 @@ GSRunLoopInfoForThread(NSThread *aThread)
modes: commonModes()];
}
- (void) performSelectorInBackground: (SEL)aSelector
- (void) performSelectorInBackground: (SEL)aSelector
withObject: (id)anObject
{
[NSThread detachNewThreadSelector: aSelector

View file

@ -0,0 +1,129 @@
#import "ObjectTesting.h"
#import <Foundation/NSThread.h>
#import <Foundation/NSLock.h>
#import <Foundation/NSNotification.h>
#include <pthread.h>
@interface ThreadExpectation : NSObject <NSLocking>
{
NSCondition *condition;
NSThread *origThread;
BOOL done;
BOOL deallocated;
}
- (void)onThreadExit: (NSNotification*)n;
- (BOOL)isDone;
@end
@implementation ThreadExpectation
- (id)init
{
if (nil == (self = [super init]))
{
return nil;
}
condition = [NSCondition new];
return self;
}
- (void)inThread: (NSThread*)thread
{
/* We explicitly don't retain this so that we can check that it actually says
* alive until the notification is sent. That check is implicit since
* PASS_EQUAL in the -onThreadExit method will throw or crash if that isn't
* the case.
*/
origThread = thread;
[[NSNotificationCenter defaultCenter] addObserver: self
selector: @selector(onThreadExit:)
name: NSThreadWillExitNotification
object: thread];
}
- (void)onThreadExit: (NSNotification*)thr
{
NSThread *current = [NSThread currentThread];
PASS_EQUAL(origThread,current,
"Correct thread reference can be obtained on exit");
[[NSNotificationCenter defaultCenter] removeObserver: self];
origThread = nil;
[condition lock];
done = YES;
[condition broadcast];
[condition unlock];
}
- (BOOL)isDone
{
return done;
}
- (void)waitUntilDate: (NSDate*)date
{
[condition waitUntilDate: date];
}
- (void)lock
{
[condition lock];
}
- (void)unlock
{
[condition unlock];
}
- (void)dealloc
{
DESTROY(condition);
[super dealloc];
}
@end
void *thread(void *expectation)
{
[(ThreadExpectation*)expectation inThread: [NSThread currentThread]];
return nil;
}
/**
* This test checks whether we can still obtain a reference to the NSThread
* object of a thread that is in the process of exiting without an explicit
* call to [NSThread exit]. To do this, we pass an expectation object to
* a thread created purely using the pthreads API. We then wait on a condition
* until the thread exits and posts the NSThreadWillExitNotification. If that
* does not happen within 5 seconds, we flag the test as failed.
*/
int main(void)
{
pthread_t thr;
pthread_attr_t attr;
void *ret;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
NSAutoreleasePool *arp = [NSAutoreleasePool new];
ThreadExpectation *expectation = [ThreadExpectation new];
pthread_create(&thr, &attr, thread, expectation);
NSDate *start = [NSDate date];
[expectation lock];
while (![expectation isDone] && [start timeIntervalSinceNow] > -5.0f)
{
[expectation waitUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.5f]];
}
PASS([expectation isDone], "Notification for thread exit was sent");
[expectation unlock];
DESTROY(expectation);
DESTROY(arp);
return 0;
}