mirror of
https://github.com/gnustep/libs-base.git
synced 2025-04-22 00:11:04 +00:00
2447 lines
64 KiB
Objective-C
2447 lines
64 KiB
Objective-C
/** Control of executable units within a shared virtual memory space
|
||
Copyright (C) 1996-2018 Free Software Foundation, Inc.
|
||
|
||
Original Author: Scott Christley <scottc@net-community.com>
|
||
Rewritten by: Andrew Kachites McCallum <mccallum@gnu.ai.mit.edu>
|
||
Created: 1996
|
||
Rewritten by: Richard Frith-Macdonald <richard@brainstorm.co.uk>
|
||
to add optimisations features for faster thread access.
|
||
Modified by: Nicola Pero <n.pero@mi.flashnet.it>
|
||
to add GNUstep extensions allowing to interact with threads created
|
||
by external libraries/code (eg, a Java Virtual Machine).
|
||
|
||
This file is part of the GNUstep Objective-C Library.
|
||
|
||
This library is free software; you can redistribute it and/or
|
||
modify it under the terms of the GNU Lesser General Public
|
||
License as published by the Free Software Foundation; either
|
||
version 2 of the License, or (at your option) any later version.
|
||
|
||
This library is distributed in the hope that it will be useful,
|
||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
Lesser General Public License for more details.
|
||
|
||
You should have received a copy of the GNU Lesser General Public
|
||
License along with this library; if not, write to the Free
|
||
Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||
Boston, MA 02110 USA.
|
||
|
||
<title>NSThread class reference</title>
|
||
*/
|
||
|
||
#import "common.h"
|
||
|
||
#import "GSPThread.h"
|
||
|
||
#ifdef _WIN32
|
||
#import <processthreadsapi.h>
|
||
#endif
|
||
|
||
// Dummy implementatation
|
||
// cleaner than IFDEF'ing the code everywhere
|
||
#ifndef HAVE_PTHREAD_SPIN_LOCK
|
||
typedef volatile int pthread_spinlock_t;
|
||
int pthread_spin_init(pthread_spinlock_t *lock, int pshared)
|
||
{
|
||
#if DEBUG && !__has_builtin(__sync_bool_compare_and_swap)
|
||
fprintf(stderr,"NSThread.m: Warning this platform does not support spin locks - init.\n");
|
||
#endif
|
||
return 0;
|
||
}
|
||
int pthread_spin_lock(pthread_spinlock_t *lock)
|
||
{
|
||
#if __has_builtin(__sync_bool_compare_and_swap)
|
||
int count = 0;
|
||
// Set the spin lock value to 1 if it is 0.
|
||
while(!__sync_bool_compare_and_swap(lock, 0, 1))
|
||
{
|
||
count++;
|
||
if (0 == count % 10)
|
||
{
|
||
// If it is already 1, let another thread play with the CPU for a
|
||
// bit then try again.
|
||
#if defined(_WIN32)
|
||
Sleep(0);
|
||
#else
|
||
sleep(0);
|
||
#endif
|
||
}
|
||
}
|
||
#else
|
||
#warning no spin_locks, using dummy versions
|
||
#endif
|
||
return 0;
|
||
}
|
||
int pthread_spin_unlock(pthread_spinlock_t *lock)
|
||
{
|
||
#if __has_builtin(__sync_bool_compare_and_swap)
|
||
__sync_synchronize();
|
||
*lock = 0;
|
||
#endif
|
||
return 0;
|
||
}
|
||
int pthread_spin_destroy(pthread_spinlock_t *lock)
|
||
{
|
||
return 0;
|
||
}
|
||
#endif /* HAVE_PTHREAD_SPIN_LOCK */
|
||
|
||
/** Structure for holding lock information for a thread.
|
||
*/
|
||
typedef struct {
|
||
pthread_spinlock_t spin; /* protect access to struct members */
|
||
NSHashTable *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 \
|
||
gs_thread_id_t _pthreadID; \
|
||
NSUInteger _threadID; \
|
||
GSLockInfo _lockInfo
|
||
|
||
|
||
#ifdef HAVE_NANOSLEEP
|
||
# include <time.h>
|
||
#endif
|
||
#ifdef HAVE_SYS_TIME_H
|
||
# include <sys/time.h>
|
||
#endif
|
||
#ifdef HAVE_SYS_RESOURCE_H
|
||
# include <sys/resource.h>
|
||
#endif
|
||
|
||
#if defined(HAVE_SYS_FILE_H)
|
||
# include <sys/file.h>
|
||
#endif
|
||
|
||
#if defined(__ANDROID__)
|
||
# include <string.h> // For strerror
|
||
# include <sys/resource.h> // For getpriority and setpriority
|
||
#endif
|
||
|
||
#if defined(HAVE_SYS_FCNTL_H)
|
||
# include <sys/fcntl.h>
|
||
#elif defined(HAVE_FCNTL_H)
|
||
# include <fcntl.h>
|
||
#endif
|
||
|
||
#if defined(__POSIX_SOURCE)\
|
||
|| defined(__EXT_POSIX1_198808)\
|
||
|| defined(O_NONBLOCK)
|
||
#define NBLK_OPT O_NONBLOCK
|
||
#else
|
||
#define NBLK_OPT FNDELAY
|
||
#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"
|
||
#import "Foundation/NSConnection.h"
|
||
#import "Foundation/NSInvocation.h"
|
||
#import "Foundation/NSUserDefaults.h"
|
||
#import "Foundation/NSValue.h"
|
||
|
||
#import "GSPrivate.h"
|
||
#import "GSRunLoopCtxt.h"
|
||
|
||
#if defined(HAVE_PTHREAD_NP_H)
|
||
# include <pthread_np.h>
|
||
#endif
|
||
|
||
#if defined(HAVE_GETTID)
|
||
# include <unistd.h>
|
||
# include <sys/syscall.h>
|
||
# 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)
|
||
# define IS_MAIN_PTHREAD (getpid() == (pid_t)syscall(SYS_gettid))
|
||
#else
|
||
# define IS_MAIN_PTHREAD (1)
|
||
#endif
|
||
|
||
/* Return the current thread ID as an unsigned long.
|
||
* Ideally, we use the operating-system's notion of a thread ID so
|
||
* that external process monitoring software will be using the same
|
||
* value that we log. If we don't know the system's mechanism, we
|
||
* use the address of the current NSThread object so that, even if
|
||
* it makes no sense externally, it can still be used to show that
|
||
* different threads generated different logs.
|
||
*/
|
||
NSUInteger
|
||
GSPrivateThreadID()
|
||
{
|
||
#if defined(_WIN32)
|
||
return (NSUInteger)GetCurrentThreadId();
|
||
#elif defined(HAVE_GETTID)
|
||
return (NSUInteger)syscall(SYS_gettid);
|
||
#elif defined(HAVE_PTHREAD_GETTHREADID_NP)
|
||
return (NSUInteger)pthread_getthreadid_np();
|
||
#else
|
||
return (NSUInteger)GSCurrentThread();
|
||
#endif
|
||
}
|
||
|
||
#ifndef PTHREAD_SETNAME
|
||
#define PTHREAD_SETNAME(a) -1
|
||
#endif
|
||
|
||
#ifndef PTHREAD_GETNAME
|
||
#define PTHREAD_GETNAME(a, b) -1
|
||
#endif
|
||
|
||
|
||
@interface NSThread (Activation)
|
||
- (void) _makeThreadCurrent;
|
||
@end
|
||
|
||
@interface NSAutoreleasePool (NSThread)
|
||
+ (void) _endThread: (NSThread*)thread;
|
||
@end
|
||
|
||
static Class threadClass = Nil;
|
||
static NSNotificationCenter *nc = nil;
|
||
static BOOL disableTraceLocks = NO;
|
||
|
||
/**
|
||
* This class performs a dual function ...
|
||
* <p>
|
||
* As a class, it is responsible for handling incoming events from
|
||
* the main runloop on a special inputFd. This consumes any bytes
|
||
* written to wake the main runloop.<br />
|
||
* During initialisation, the default runloop is set up to watch
|
||
* for data arriving on inputFd.
|
||
* </p>
|
||
* <p>
|
||
* As instances, each instance retains perform receiver and argument
|
||
* values as long as they are needed, and handles locking to support
|
||
* methods which want to block until an action has been performed.
|
||
* </p>
|
||
* <p>
|
||
* The initialize method of this class is called before any new threads
|
||
* run.
|
||
* </p>
|
||
*/
|
||
@interface GSPerformHolder : NSObject
|
||
{
|
||
id receiver;
|
||
id argument;
|
||
SEL selector;
|
||
NSConditionLock *lock; // Not retained.
|
||
NSArray *modes;
|
||
BOOL invalidated;
|
||
@public
|
||
NSException *exception;
|
||
}
|
||
+ (GSPerformHolder*) newForReceiver: (id)r
|
||
argument: (id)a
|
||
selector: (SEL)s
|
||
modes: (NSArray*)m
|
||
lock: (NSConditionLock*)l;
|
||
- (void) fire;
|
||
- (void) invalidate;
|
||
- (BOOL) isInvalidated;
|
||
- (NSArray*) modes;
|
||
@end
|
||
|
||
/**
|
||
* Sleep until the current date/time is the specified time interval
|
||
* past the reference date/time.<br />
|
||
* Implemented as a function taking an NSTimeInterval argument in order
|
||
* to avoid objc messaging and object allocation/deallocation (NSDate)
|
||
* overheads.<br />
|
||
* Used to implement [NSThread+sleepUntilDate:]
|
||
* If the date is in the past, this function simply allows other threads
|
||
* (if any) to run.
|
||
*/
|
||
void
|
||
GSSleepUntilIntervalSinceReferenceDate(NSTimeInterval when)
|
||
{
|
||
NSTimeInterval delay;
|
||
|
||
// delay is always the number of seconds we still need to wait
|
||
delay = when - GSPrivateTimeNow();
|
||
if (delay <= 0.0)
|
||
{
|
||
/* We don't need to wait, but since we are willing to wait at this
|
||
* point, we should let other threads have preference over this one.
|
||
*/
|
||
GS_YIELD();
|
||
return;
|
||
}
|
||
|
||
#if defined(_WIN32)
|
||
/*
|
||
* Avoid integer overflow by breaking up long sleeps.
|
||
*/
|
||
while (delay > 30.0*60.0)
|
||
{
|
||
// sleep 30 minutes
|
||
Sleep (30*60*1000);
|
||
delay = when - GSPrivateTimeNow();
|
||
}
|
||
|
||
/* Don't use nanosleep (even if available) on mingw ... it's reported no
|
||
* to work with pthreads.
|
||
* Sleeping may return early because of signals, so we need to re-calculate
|
||
* the required delay and check to see if we need to sleep again.
|
||
*/
|
||
while (delay > 0)
|
||
{
|
||
#if defined(HAVE_USLEEP)
|
||
/* On windows usleep() seems to perform a busy wait ... so we only
|
||
* use it for short delays ... otherwise use the less accurate Sleep()
|
||
*/
|
||
if (delay > 0.1)
|
||
{
|
||
Sleep ((NSInteger)(delay*1000));
|
||
}
|
||
else
|
||
{
|
||
usleep ((NSInteger)(delay*1000000));
|
||
}
|
||
#else
|
||
Sleep ((NSInteger)(delay*1000));
|
||
#endif /* HAVE_USLEEP */
|
||
delay = when - GSPrivateTimeNow();
|
||
}
|
||
|
||
#else /* _WIN32 */
|
||
|
||
/*
|
||
* Avoid integer overflow by breaking up long sleeps.
|
||
*/
|
||
while (delay > 30.0*60.0)
|
||
{
|
||
// sleep 30 minutes
|
||
sleep(30*60);
|
||
delay = when - GSPrivateTimeNow();
|
||
}
|
||
|
||
#ifdef HAVE_NANOSLEEP
|
||
if (delay > 0)
|
||
{
|
||
struct timespec request;
|
||
struct timespec remainder;
|
||
|
||
request.tv_sec = (time_t)delay;
|
||
request.tv_nsec = (long)((delay - request.tv_sec) * 1000000000);
|
||
remainder.tv_sec = 0;
|
||
remainder.tv_nsec = 0;
|
||
|
||
/*
|
||
* With nanosleep, we can restart the sleep after a signal by using
|
||
* the remainder information ... so we can be sure to sleep to the
|
||
* desired limit without having to re-generate the delay needed.
|
||
*/
|
||
while (nanosleep(&request, &remainder) < 0
|
||
&& (remainder.tv_sec > 0 || remainder.tv_nsec > 0))
|
||
{
|
||
request.tv_sec = remainder.tv_sec;
|
||
request.tv_nsec = remainder.tv_nsec;
|
||
remainder.tv_sec = 0;
|
||
remainder.tv_nsec = 0;
|
||
}
|
||
}
|
||
#else /* HAVE_NANOSLEEP */
|
||
|
||
/*
|
||
* sleeping may return early because of signals, so we need to re-calculate
|
||
* the required delay and check to see if we need to sleep again.
|
||
*/
|
||
while (delay > 0)
|
||
{
|
||
#if defined(HAVE_USLEEP)
|
||
usleep((NSInteger)(delay*1000000));
|
||
#else /* HAVE_USLEEP */
|
||
sleep((NSInteger)delay);
|
||
#endif /* !HAVE_USLEEP */
|
||
delay = when - GSPrivateTimeNow();
|
||
}
|
||
#endif /* !HAVE_NANOSLEEP */
|
||
#endif /* !_WIN32 */
|
||
}
|
||
|
||
static NSArray *
|
||
commonModes(void)
|
||
{
|
||
static gs_mutex_t modesLock = GS_MUTEX_INIT_STATIC;
|
||
static NSArray *modes = nil;
|
||
|
||
if (modes == nil)
|
||
{
|
||
GS_MUTEX_LOCK(modesLock);
|
||
if (modes == nil)
|
||
{
|
||
Class c = NSClassFromString(@"NSApplication");
|
||
SEL s = @selector(allRunLoopModes);
|
||
|
||
if (c != 0 && [c respondsToSelector: s])
|
||
{
|
||
modes = RETAIN([c performSelector: s]);
|
||
}
|
||
else
|
||
{
|
||
modes = [[NSArray alloc] initWithObjects:
|
||
NSDefaultRunLoopMode, NSConnectionReplyMode, nil];
|
||
}
|
||
}
|
||
GS_MUTEX_UNLOCK(modesLock);
|
||
}
|
||
return modes;
|
||
}
|
||
|
||
/*
|
||
* Flag indicating whether the objc runtime ever went multi-threaded.
|
||
*/
|
||
static BOOL entered_multi_threaded_state = NO;
|
||
|
||
static NSThread *defaultThread;
|
||
|
||
static BOOL keyInitialized = NO;
|
||
static gs_thread_key_t thread_object_key;
|
||
|
||
|
||
static NSHashTable *_activeBlocked = nil;
|
||
static NSHashTable *_activeThreads = nil;
|
||
static gs_mutex_t _activeLock = GS_MUTEX_INIT_STATIC;
|
||
|
||
/**
|
||
* 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(gs_thread_id_t thread)
|
||
{
|
||
return [[NSValue alloc] initWithBytes: &thread
|
||
objCType: @encode(gs_thread_id_t)];
|
||
}
|
||
|
||
/**
|
||
* Conversely, we need to be able to retrieve the pthread_t
|
||
* from an NSValue.
|
||
*/
|
||
static inline void
|
||
_getPthreadFromNSValue(const void *value, gs_thread_id_t *thread_ptr)
|
||
{
|
||
const char *enc;
|
||
|
||
NSCAssert(thread_ptr, @"No storage for thread reference");
|
||
# ifndef NS_BLOCK_ASSERTIONS
|
||
enc = [(NSValue*)value objCType];
|
||
NSCAssert(enc != NULL && (0 == strcmp(@encode(gs_thread_id_t),enc)),
|
||
@"Invalid NSValue container for thread reference");
|
||
# endif
|
||
[(NSValue*)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)
|
||
{
|
||
gs_thread_id_t thread;
|
||
gs_thread_id_t otherThread;
|
||
|
||
_getPthreadFromNSValue(boxed, &thread);
|
||
_getPthreadFromNSValue(boxedOther, &otherThread);
|
||
|
||
#if GS_USE_WIN32_THREADS_AND_LOCKS
|
||
return thread == otherThread;
|
||
#else
|
||
return pthread_equal(thread, otherThread);
|
||
#endif
|
||
}
|
||
|
||
/**
|
||
* 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)
|
||
{
|
||
RETAIN((NSValue*)value);
|
||
}
|
||
|
||
/**
|
||
* Release callback for boxed thread references.
|
||
*/
|
||
static void _boxedPthreadRelease(NSMapTable *t, void *value)
|
||
{
|
||
RELEASE((NSValue*)value);
|
||
}
|
||
|
||
/**
|
||
* 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 = nil;
|
||
static gs_mutex_t _exitingThreadsLock = GS_MUTEX_INIT_STATIC;
|
||
|
||
|
||
/**
|
||
* 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)
|
||
{
|
||
GS_MUTEX_LOCK(_exitingThreadsLock);
|
||
/* The map 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 == _exitingThreads)
|
||
{
|
||
_exitingThreads = NSCreateMapTable(_boxedPthreadKeyCallBacks,
|
||
NSObjectMapValueCallBacks, 10);
|
||
}
|
||
NSMapInsert(_exitingThreads, (const void*)boxedThread,
|
||
(const void*)specific);
|
||
GS_MUTEX_UNLOCK(_exitingThreadsLock);
|
||
}
|
||
|
||
/**
|
||
* 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)
|
||
{
|
||
/* 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.
|
||
*/
|
||
GS_MUTEX_LOCK(_exitingThreadsLock);
|
||
if (nil != _exitingThreads)
|
||
{
|
||
NSMapRemove(_exitingThreads, (const void*)boxedThread);
|
||
}
|
||
GS_MUTEX_UNLOCK(_exitingThreadsLock);
|
||
}
|
||
|
||
/*
|
||
* 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
|
||
* 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 GS_WINAPI
|
||
exitedThread(void *thread)
|
||
{
|
||
if (thread != defaultThread)
|
||
{
|
||
NSValue *ref;
|
||
|
||
if (0 == thread)
|
||
{
|
||
/* On some systems this is called with a null thread pointer,
|
||
* so try to get the NSThread object for the current thread.
|
||
*/
|
||
thread = GS_THREAD_KEY_GET(thread_object_key);
|
||
if (0 == thread)
|
||
{
|
||
return; // no thread info
|
||
}
|
||
}
|
||
RETAIN((NSThread*)thread);
|
||
ref = NSValueCreateFromPthread(GS_THREAD_ID_SELF());
|
||
_willLateUnregisterThread(ref, (NSThread*)thread);
|
||
|
||
{
|
||
CREATE_AUTORELEASE_POOL(arp);
|
||
NS_DURING
|
||
{
|
||
unregisterActiveThread((NSThread*)thread);
|
||
}
|
||
NS_HANDLER
|
||
{
|
||
DESTROY(arp);
|
||
_didLateUnregisterCurrentThread(ref);
|
||
DESTROY(ref);
|
||
RELEASE((NSThread*)thread);
|
||
}
|
||
NS_ENDHANDLER
|
||
DESTROY(arp);
|
||
}
|
||
|
||
/* 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);
|
||
RELEASE((NSThread*)thread);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* These functions needed because sending messages to classes is a seriously
|
||
* slow process with gcc and the gnu runtime.
|
||
*/
|
||
inline NSThread*
|
||
GSCurrentThread(void)
|
||
{
|
||
NSThread *thr;
|
||
|
||
if (NO == keyInitialized)
|
||
{
|
||
if (!GS_THREAD_KEY_INIT(thread_object_key, exitedThread))
|
||
{
|
||
[NSException raise: NSInternalInconsistencyException
|
||
format: @"Unable to create thread key!"];
|
||
}
|
||
keyInitialized = YES;
|
||
}
|
||
thr = GS_THREAD_KEY_GET(thread_object_key);
|
||
if (nil == thr)
|
||
{
|
||
NSValue *selfThread = NSValueCreateFromPthread(GS_THREAD_ID_SELF());
|
||
|
||
/* 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 != _exitingThreads)
|
||
{
|
||
GS_MUTEX_LOCK(_exitingThreadsLock);
|
||
thr = NSMapGet(_exitingThreads, (const void*)selfThread);
|
||
GS_MUTEX_UNLOCK(_exitingThreadsLock);
|
||
}
|
||
DESTROY(selfThread);
|
||
if (nil == thr)
|
||
{
|
||
GSRegisterCurrentThread();
|
||
thr = GS_THREAD_KEY_GET(thread_object_key);
|
||
if ((nil == defaultThread) && IS_MAIN_PTHREAD)
|
||
{
|
||
defaultThread = RETAIN(thr);
|
||
}
|
||
}
|
||
assert(nil != thr && "No main thread");
|
||
}
|
||
return thr;
|
||
}
|
||
|
||
NSMutableDictionary*
|
||
GSDictionaryForThread(NSThread *t)
|
||
{
|
||
if (nil == t)
|
||
{
|
||
t = GSCurrentThread();
|
||
}
|
||
return [t threadDictionary];
|
||
}
|
||
|
||
/**
|
||
* Fast access function for thread dictionary of current thread.
|
||
*/
|
||
NSMutableDictionary*
|
||
GSCurrentThreadDictionary(void)
|
||
{
|
||
return GSDictionaryForThread(nil);
|
||
}
|
||
|
||
/*
|
||
* Callback function to send notifications on becoming multi-threaded.
|
||
*/
|
||
static void
|
||
gnustep_base_thread_callback(void)
|
||
{
|
||
static gs_mutex_t threadLock = GS_MUTEX_INIT_STATIC;
|
||
/*
|
||
* Protect this function with locking ... to avoid any possibility
|
||
* of multiple threads registering with the system simultaneously,
|
||
* and so that all NSWillBecomeMultiThreadedNotifications are sent
|
||
* out before any second thread can interfere with anything.
|
||
*/
|
||
if (entered_multi_threaded_state == NO)
|
||
{
|
||
GS_MUTEX_LOCK(threadLock);
|
||
if (entered_multi_threaded_state == NO)
|
||
{
|
||
/*
|
||
* For apple compatibility ... and to make things easier for
|
||
* code called indirectly within a will-become-multi-threaded
|
||
* notification handler, we set the flag to say we are multi
|
||
* threaded BEFORE sending the notifications.
|
||
*/
|
||
entered_multi_threaded_state = YES;
|
||
/*
|
||
* Enter pool after setting flag, because -[NSAutoreleasePool
|
||
* allocWithZone:] calls GSCurrentThread(), which may end up
|
||
* calling this function, which would cause a deadlock.
|
||
*/
|
||
ENTER_POOL
|
||
NS_DURING
|
||
{
|
||
[GSPerformHolder class]; // Force initialization
|
||
|
||
/*
|
||
* Post a notification if this is the first new thread
|
||
* to be created.
|
||
* Won't work properly if threads are not all created
|
||
* by this class, but it's better than nothing.
|
||
*/
|
||
if (nc == nil)
|
||
{
|
||
nc = RETAIN([NSNotificationCenter defaultCenter]);
|
||
}
|
||
#if !defined(HAVE_INITIALIZE)
|
||
if (NO == [[NSUserDefaults standardUserDefaults]
|
||
boolForKey: @"GSSilenceInitializeWarning"])
|
||
{
|
||
NSLog(@"WARNING your program is becoming multi-threaded, but you are using an ObjectiveC runtime library which does not have a thread-safe implementation of the +initialize method. Please see README.initialize for more information.");
|
||
}
|
||
#endif
|
||
[nc postNotificationName: NSWillBecomeMultiThreadedNotification
|
||
object: nil
|
||
userInfo: nil];
|
||
}
|
||
NS_HANDLER
|
||
{
|
||
fprintf(stderr,
|
||
"ALERT ... exception while becoming multi-threaded ... system may not be\n"
|
||
"properly initialised.\n");
|
||
fflush(stderr);
|
||
}
|
||
NS_ENDHANDLER
|
||
LEAVE_POOL
|
||
}
|
||
GS_MUTEX_UNLOCK(threadLock);
|
||
}
|
||
}
|
||
|
||
@implementation NSThread (Activation)
|
||
- (void) _makeThreadCurrent
|
||
{
|
||
/* NB. We must set up the pointer to the new NSThread instance from
|
||
* pthread specific memory before we do anything which might need to
|
||
* check what the current thread is (like getting the ID)!
|
||
*/
|
||
GS_THREAD_KEY_SET(thread_object_key, self);
|
||
threadID = GSPrivateThreadID();
|
||
GS_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);
|
||
GS_MUTEX_UNLOCK(_activeLock);
|
||
}
|
||
@end
|
||
|
||
@implementation NSThread
|
||
|
||
static void
|
||
setThreadForCurrentThread(NSThread *t)
|
||
{
|
||
[t _makeThreadCurrent];
|
||
gnustep_base_thread_callback();
|
||
}
|
||
|
||
static void
|
||
unregisterActiveThread(NSThread *thread)
|
||
{
|
||
if (thread->_active == YES)
|
||
{
|
||
/* Let observers know this thread is exiting.
|
||
*/
|
||
ENTER_POOL
|
||
if (nc == nil)
|
||
{
|
||
nc = RETAIN([NSNotificationCenter defaultCenter]);
|
||
}
|
||
[nc postNotificationName: NSThreadWillExitNotification
|
||
object: thread
|
||
userInfo: nil];
|
||
|
||
/* Set the thread to be finished *after* notification it will exit.
|
||
* This is the order OSX 10.15.4 does it (May 2020).
|
||
*/
|
||
thread->_active = NO;
|
||
thread->_finished = YES;
|
||
|
||
[(GSRunLoopThreadInfo*)thread->_runLoopInfo invalidate];
|
||
LEAVE_POOL
|
||
RELEASE(thread);
|
||
GS_THREAD_KEY_SET(thread_object_key, nil);
|
||
}
|
||
}
|
||
|
||
+ (NSArray*) callStackReturnAddresses
|
||
{
|
||
GSStackTrace *stack;
|
||
NSArray *addrs;
|
||
|
||
stack = [GSStackTrace new];
|
||
[stack trace];
|
||
addrs = RETAIN([stack addresses]);
|
||
RELEASE(stack);
|
||
return AUTORELEASE(addrs);
|
||
}
|
||
|
||
+ (BOOL) _createThreadForCurrentPthread
|
||
{
|
||
NSThread *t = GS_THREAD_KEY_GET(thread_object_key);
|
||
|
||
if (t == nil)
|
||
{
|
||
t = [self new];
|
||
t->_active = YES;
|
||
[t _makeThreadCurrent];
|
||
GS_CONSUMED(t);
|
||
if (defaultThread != nil && t != defaultThread)
|
||
{
|
||
gnustep_base_thread_callback();
|
||
}
|
||
return YES;
|
||
}
|
||
return NO;
|
||
}
|
||
|
||
+ (NSThread*) currentThread
|
||
{
|
||
return GSCurrentThread();
|
||
}
|
||
|
||
+ (void) detachNewThreadSelector: (SEL)aSelector
|
||
toTarget: (id)aTarget
|
||
withObject: (id)anArgument
|
||
{
|
||
NSThread *thread;
|
||
|
||
/*
|
||
* Create the new thread.
|
||
*/
|
||
thread = [[NSThread alloc] initWithTarget: aTarget
|
||
selector: aSelector
|
||
object: anArgument];
|
||
|
||
[thread start];
|
||
RELEASE(thread);
|
||
}
|
||
|
||
+ (void) exit
|
||
{
|
||
NSThread *t;
|
||
|
||
t = GSCurrentThread();
|
||
if (t->_active == YES)
|
||
{
|
||
unregisterActiveThread(t);
|
||
|
||
if (t == defaultThread || defaultThread == nil)
|
||
{
|
||
/* For the default thread, we exit the process.
|
||
*/
|
||
exit(0);
|
||
}
|
||
else
|
||
{
|
||
#if GS_USE_WIN32_THREADS_AND_LOCKS
|
||
_endthread();
|
||
#else
|
||
pthread_exit(NULL);
|
||
#endif
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
* Class initialization
|
||
*/
|
||
+ (void) initialize
|
||
{
|
||
if (self == [NSThread class])
|
||
{
|
||
if (NO == keyInitialized)
|
||
{
|
||
if (!GS_THREAD_KEY_INIT(thread_object_key, exitedThread))
|
||
{
|
||
[NSException raise: NSInternalInconsistencyException
|
||
format: @"Unable to create thread key!"];
|
||
}
|
||
keyInitialized = YES;
|
||
}
|
||
/* Ensure that the default thread exists.
|
||
* It's safe to create a lock here (since [NSObject+initialize]
|
||
* creates locks, and locks don't depend on any other class),
|
||
* but we want to avoid initialising other classes while we are
|
||
* initialising NSThread.
|
||
*/
|
||
threadClass = self;
|
||
GSCurrentThread();
|
||
}
|
||
}
|
||
|
||
+ (BOOL) isMainThread
|
||
{
|
||
return (GSCurrentThread() == defaultThread ? YES : NO);
|
||
}
|
||
|
||
+ (BOOL) isMultiThreaded
|
||
{
|
||
return entered_multi_threaded_state;
|
||
}
|
||
|
||
+ (NSThread*) mainThread
|
||
{
|
||
return defaultThread;
|
||
}
|
||
|
||
/**
|
||
* Set the priority of the current thread. This is a value in the
|
||
* range 0.0 (lowest) to 1.0 (highest) which is mapped to the underlying
|
||
* system priorities.
|
||
*/
|
||
+ (BOOL) setThreadPriority: (double)pri
|
||
{
|
||
#if GS_USE_WIN32_THREADS_AND_LOCKS
|
||
// convert [0.0,1.0] priority to Windows defines
|
||
int winPri;
|
||
if (pri <= 0.0) {
|
||
winPri = THREAD_PRIORITY_IDLE;
|
||
} else if (pri <= 0.25) {
|
||
winPri = THREAD_PRIORITY_LOWEST;
|
||
} else if (pri < 0.5) {
|
||
winPri = THREAD_PRIORITY_BELOW_NORMAL;
|
||
} else if (pri >= 1.0) {
|
||
winPri = THREAD_PRIORITY_TIME_CRITICAL;
|
||
} else if (pri >= 0.75) {
|
||
winPri = THREAD_PRIORITY_HIGHEST;
|
||
} else if (pri > 0.5) {
|
||
winPri = THREAD_PRIORITY_ABOVE_NORMAL;
|
||
} else {
|
||
winPri = THREAD_PRIORITY_NORMAL;
|
||
}
|
||
|
||
if (!SetThreadPriority(GetCurrentThread(), winPri)) {
|
||
NSLog(@"Failed to set thread priority %d: %@", winPri, [NSError _last]);
|
||
return NO;
|
||
}
|
||
return YES;
|
||
#elif defined(__ANDROID__)
|
||
/* Android's pthread_setschedparam is currently broken, as it checks
|
||
* if the priority is in the range of the system's min and max
|
||
* priorities. The interval bounds are queried with `sched_get_priority_min`,
|
||
* and `sched_get_priority_max` which just return 0, regardless of the
|
||
* specified scheduling policy.
|
||
*
|
||
* The solution is to use `setpriority` to set the thread
|
||
* priority. This is possible because on Linux, it is not a per-process setting
|
||
* as specified by POSIX but a per-thread setting (See the `Bugs` section in `setpriority`).
|
||
*
|
||
* Android's internal implementation also relies on this behavior, so it
|
||
* is safe to use it here.
|
||
*/
|
||
|
||
// Clamp pri into the required range.
|
||
if (pri > 1) { pri = 1; }
|
||
if (pri < 0) { pri = 0; }
|
||
|
||
// Convert [0.0, 1.0] to [-20, 19] range where -20 is the highest
|
||
// and 19 the lowest priority.
|
||
int priority = (int)(-20 + (1-pri) * 39);
|
||
if (setpriority(PRIO_PROCESS, 0, priority) == -1)
|
||
{
|
||
NSLog(@"Failed to set thread priority %d: %s", priority, strerror(errno));
|
||
return NO;
|
||
}
|
||
return YES;
|
||
#elif defined(_POSIX_THREAD_PRIORITY_SCHEDULING) && (_POSIX_THREAD_PRIORITY_SCHEDULING > 0)
|
||
int res;
|
||
int policy;
|
||
struct sched_param param;
|
||
|
||
// Clamp pri into the required range.
|
||
if (pri > 1) { pri = 1; }
|
||
if (pri < 0) { pri = 0; }
|
||
|
||
res = pthread_getschedparam(pthread_self(), &policy, ¶m);
|
||
if (res == 0)
|
||
{
|
||
int min = sched_get_priority_min(policy);
|
||
int max = sched_get_priority_max(policy);
|
||
if (min == max) {
|
||
// priority not settable => fail silently
|
||
return NO;
|
||
}
|
||
|
||
// Scale pri based on the range of the host system.
|
||
pri = min + pri * (max - min);
|
||
|
||
param.sched_priority = pri;
|
||
res = pthread_setschedparam(pthread_self(), policy, ¶m);
|
||
}
|
||
|
||
if (res != 0) {
|
||
NSLog(@"Failed to set thread priority %f: %d", pri, res);
|
||
return NO;
|
||
}
|
||
|
||
return YES;
|
||
#else
|
||
return NO;
|
||
#endif
|
||
}
|
||
|
||
+ (void) sleepForTimeInterval: (NSTimeInterval)ti
|
||
{
|
||
GSSleepUntilIntervalSinceReferenceDate(GSPrivateTimeNow() + ti);
|
||
}
|
||
|
||
/**
|
||
* Delaying a thread ... pause until the specified date.
|
||
*/
|
||
+ (void) sleepUntilDate: (NSDate*)date
|
||
{
|
||
GSSleepUntilIntervalSinceReferenceDate([date timeIntervalSinceReferenceDate]);
|
||
}
|
||
|
||
|
||
/**
|
||
* Return the priority of the current thread.
|
||
*/
|
||
+ (double) threadPriority
|
||
{
|
||
double pri = 0;
|
||
|
||
#if GS_USE_WIN32_THREADS_AND_LOCKS
|
||
// convert [0.0,1.0] priority to Windows defines
|
||
int winPri = GetThreadPriority(GetCurrentThread());
|
||
if (winPri == THREAD_PRIORITY_ERROR_RETURN) {
|
||
NSLog(@"Failed to get thread priority: %@", [NSError _last]);
|
||
return pri;
|
||
}
|
||
|
||
switch (winPri)
|
||
{
|
||
case THREAD_PRIORITY_IDLE:
|
||
pri = 0.0;
|
||
break;
|
||
case THREAD_PRIORITY_LOWEST:
|
||
pri = 0.2;
|
||
break;
|
||
case THREAD_PRIORITY_BELOW_NORMAL:
|
||
pri = 0.4;
|
||
break;
|
||
case THREAD_PRIORITY_TIME_CRITICAL:
|
||
pri = 1.0;
|
||
break;
|
||
case THREAD_PRIORITY_HIGHEST:
|
||
pri = 0.8;
|
||
break;
|
||
case THREAD_PRIORITY_ABOVE_NORMAL:
|
||
pri = 0.6;
|
||
break;
|
||
case THREAD_PRIORITY_NORMAL:
|
||
pri = 0.5;
|
||
break;
|
||
default:
|
||
NSLog(@"Unknown thread priority: %d", winPri);
|
||
break;
|
||
}
|
||
#elif defined(__ANDROID__)
|
||
/* See notes in setThreadPriority
|
||
*/
|
||
int priority = getpriority(PRIO_PROCESS, 0);
|
||
if (priority == -1)
|
||
{
|
||
NSLog(@"Failed to get thread priority: %s", strerror(errno));
|
||
return pri;
|
||
}
|
||
|
||
// Convert [-20, 19] to [0.0, 1.0] range
|
||
pri = 1 - (priority + 20) / 39.0;
|
||
#elif defined(_POSIX_THREAD_PRIORITY_SCHEDULING) && (_POSIX_THREAD_PRIORITY_SCHEDULING > 0)
|
||
int res;
|
||
int policy;
|
||
struct sched_param param;
|
||
|
||
res = pthread_getschedparam(pthread_self(), &policy, ¶m);
|
||
if (res == 0)
|
||
{
|
||
int min = sched_get_priority_min(policy);
|
||
int max = sched_get_priority_max(policy);
|
||
if (min != max) /* avoid division by zero */
|
||
{
|
||
pri = param.sched_priority;
|
||
|
||
// Scale pri based on the range of the host system.
|
||
pri = (pri - min) / (max - min);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
NSLog(@"Failed to get thread priority: %d", res);
|
||
}
|
||
#else
|
||
#warning Your pthread implementation does not support thread priorities
|
||
#endif
|
||
|
||
return pri;
|
||
}
|
||
|
||
/*
|
||
* Thread instance methods.
|
||
*/
|
||
|
||
- (void) cancel
|
||
{
|
||
_cancelled = YES;
|
||
}
|
||
|
||
- (void) dealloc
|
||
{
|
||
int retries = 0;
|
||
|
||
if (_active == YES)
|
||
{
|
||
[NSException raise: NSInternalInconsistencyException
|
||
format: @"Deallocating an active thread without [+exit]!"];
|
||
}
|
||
DESTROY(_runLoopInfo);
|
||
DESTROY(_thread_dictionary);
|
||
DESTROY(_target);
|
||
DESTROY(_arg);
|
||
DESTROY(_name);
|
||
if (_autorelease_vars.pool_cache != 0)
|
||
{
|
||
[NSAutoreleasePool _endThread: self];
|
||
}
|
||
|
||
while ((_thread_dictionary != nil || _runLoopInfo != nil) && retries++ < 10)
|
||
{
|
||
/* Try again.
|
||
*/
|
||
DESTROY(_runLoopInfo);
|
||
DESTROY(_thread_dictionary);
|
||
if (_autorelease_vars.pool_cache != 0)
|
||
{
|
||
[NSAutoreleasePool _endThread: self];
|
||
}
|
||
}
|
||
|
||
if (_runLoopInfo != nil)
|
||
{
|
||
NSLog(@"Oops - leak - run loop is %@", _runLoopInfo);
|
||
if (_autorelease_vars.pool_cache != 0)
|
||
{
|
||
[NSAutoreleasePool _endThread: self];
|
||
}
|
||
}
|
||
if (_thread_dictionary != nil)
|
||
{
|
||
NSLog(@"Oops - leak - thread dictionary is %@", _thread_dictionary);
|
||
if (_autorelease_vars.pool_cache != 0)
|
||
{
|
||
[NSAutoreleasePool _endThread: self];
|
||
}
|
||
}
|
||
DESTROY(_gcontext);
|
||
if (_activeThreads)
|
||
{
|
||
GS_MUTEX_LOCK(_activeLock);
|
||
NSHashRemove(_activeThreads, self);
|
||
GS_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, threadID];
|
||
}
|
||
|
||
- (id) init
|
||
{
|
||
// SetThreadDescription() was added in Windows 10 1607 (Redstone 1)
|
||
#if defined(_WIN32) && (NTDDI_VERSION >= NTDDI_WIN10_RS1)
|
||
HANDLE current;
|
||
HRESULT hr;
|
||
PWSTR name;
|
||
NSString *threadName;
|
||
|
||
current = GetCurrentThread();
|
||
hr = GetThreadDescription(current, &name);
|
||
if (SUCCEEDED(hr))
|
||
{
|
||
threadName = [NSString stringWithCharacters: (const void *) name length: wcslen(name)];
|
||
ASSIGN(_name, threadName);
|
||
LocalFree(name);
|
||
}
|
||
#elif defined(PTHREAD_GETNAME)
|
||
NSString *threadName;
|
||
char name[16];
|
||
int status;
|
||
|
||
status = PTHREAD_GETNAME(name, 16);
|
||
if (status == 0)
|
||
{
|
||
threadName = [NSString stringWithCString: name encoding: NSUTF8StringEncoding];
|
||
ASSIGN(_name, threadName);
|
||
}
|
||
#endif
|
||
|
||
GS_CREATE_INTERNAL(NSThread);
|
||
pthread_spin_init(&lockInfo.spin, 0);
|
||
lockInfo.held = NSCreateHashTable(NSNonOwnedPointerHashCallBacks, 10);
|
||
init_autorelease_thread_vars(&_autorelease_vars);
|
||
return self;
|
||
}
|
||
|
||
- (id) initWithTarget: (id)aTarget
|
||
selector: (SEL)aSelector
|
||
object: (id)anArgument
|
||
{
|
||
if (nil != (self = [self init]))
|
||
{
|
||
/* initialize our ivars. */
|
||
_selector = aSelector;
|
||
_target = RETAIN(aTarget);
|
||
_arg = RETAIN(anArgument);
|
||
}
|
||
return self;
|
||
}
|
||
|
||
- (BOOL) isCancelled
|
||
{
|
||
return _cancelled;
|
||
}
|
||
|
||
- (BOOL) isExecuting
|
||
{
|
||
return _active;
|
||
}
|
||
|
||
- (BOOL) isFinished
|
||
{
|
||
return _finished;
|
||
}
|
||
|
||
- (BOOL) isMainThread
|
||
{
|
||
return (self == defaultThread ? YES : NO);
|
||
}
|
||
|
||
- (void) main
|
||
{
|
||
if (_active == NO)
|
||
{
|
||
[NSException raise: NSInternalInconsistencyException
|
||
format: @"[%@-%@] called on inactive thread",
|
||
NSStringFromClass([self class]),
|
||
NSStringFromSelector(_cmd)];
|
||
}
|
||
|
||
[_target performSelector: _selector withObject: _arg];
|
||
}
|
||
|
||
- (NSString*) name
|
||
{
|
||
return _name;
|
||
}
|
||
|
||
- (void) _setName: (NSString *)aName
|
||
{
|
||
if ([aName isKindOfClass: [NSString class]])
|
||
{
|
||
// SetThreadDescription() was added in Windows 10 1607 (Redstone 1)
|
||
#if defined(_WIN32) && (NTDDI_VERSION >= NTDDI_WIN10_RS1)
|
||
HANDLE current;
|
||
const void *utf16String;
|
||
|
||
current = GetCurrentThread();
|
||
utf16String = [aName cStringUsingEncoding: NSUnicodeStringEncoding];
|
||
SetThreadDescription(current, utf16String);
|
||
#elif defined(PTHREAD_SETNAME)
|
||
int i;
|
||
char buf[200];
|
||
|
||
if (YES == [aName getCString: buf
|
||
maxLength: sizeof(buf)
|
||
encoding: NSUTF8StringEncoding])
|
||
{
|
||
i = strlen(buf);
|
||
}
|
||
else
|
||
{
|
||
/* Too much for buffer ... truncate on a character boundary.
|
||
*/
|
||
i = sizeof(buf) - 1;
|
||
if (buf[i] & 0x80)
|
||
{
|
||
while (i > 0 && (buf[i] & 0x80))
|
||
{
|
||
buf[i--] = '\0';
|
||
}
|
||
}
|
||
else
|
||
{
|
||
buf[i--] = '\0';
|
||
}
|
||
}
|
||
while (i > 0)
|
||
{
|
||
if (PTHREAD_SETNAME(buf) == ERANGE)
|
||
{
|
||
/* Name must be too long ... gnu/linux uses 15 characters
|
||
*/
|
||
if (i > 15)
|
||
{
|
||
NSWarnLog(@"Truncating thread name '%s' to 15 characters due to platform limitations", buf);
|
||
i = 15;
|
||
}
|
||
else
|
||
{
|
||
i--;
|
||
}
|
||
/* too long a name ... truncate on a character boundary.
|
||
*/
|
||
if (buf[i] & 0x80)
|
||
{
|
||
while (i > 0 && (buf[i] & 0x80))
|
||
{
|
||
buf[i--] = '\0';
|
||
}
|
||
}
|
||
else
|
||
{
|
||
buf[i--] = '\0';
|
||
}
|
||
}
|
||
else
|
||
{
|
||
break; // Success or some other error
|
||
}
|
||
}
|
||
#endif
|
||
}
|
||
}
|
||
|
||
- (void) setName: (NSString*)aName
|
||
{
|
||
ASSIGN(_name, aName);
|
||
|
||
if (YES == _active)
|
||
{
|
||
[self performSelector: @selector(_setName:)
|
||
onThread: self
|
||
withObject: aName
|
||
waitUntilDone: NO];
|
||
}
|
||
}
|
||
|
||
- (void) setStackSize: (NSUInteger)stackSize
|
||
{
|
||
_stackSize = stackSize;
|
||
}
|
||
|
||
- (NSUInteger) stackSize
|
||
{
|
||
return _stackSize;
|
||
}
|
||
|
||
/**
|
||
* Trampoline function called to launch the thread
|
||
*/
|
||
static
|
||
#if GS_USE_WIN32_THREADS_AND_LOCKS
|
||
void __cdecl
|
||
#else
|
||
void *
|
||
#endif
|
||
nsthreadLauncher(void *thread)
|
||
{
|
||
NSThread *t = (NSThread*)thread;
|
||
|
||
setThreadForCurrentThread(t);
|
||
|
||
/*
|
||
* Let observers know a new thread is starting.
|
||
*/
|
||
if (nc == nil)
|
||
{
|
||
nc = RETAIN([NSNotificationCenter defaultCenter]);
|
||
}
|
||
[nc postNotificationName: NSThreadDidStartNotification
|
||
object: t
|
||
userInfo: nil];
|
||
|
||
[t _setName: [t name]];
|
||
|
||
[t main];
|
||
|
||
[NSThread exit];
|
||
// Not reached
|
||
#if !GS_USE_WIN32_THREADS_AND_LOCKS
|
||
return NULL;
|
||
#endif
|
||
}
|
||
|
||
- (void) start
|
||
{
|
||
#if !GS_USE_WIN32_THREADS_AND_LOCKS
|
||
pthread_attr_t attr;
|
||
#endif
|
||
|
||
if (_active == YES)
|
||
{
|
||
[NSException raise: NSInternalInconsistencyException
|
||
format: @"[%@-%@] called on active thread",
|
||
NSStringFromClass([self class]),
|
||
NSStringFromSelector(_cmd)];
|
||
}
|
||
if (_cancelled == YES)
|
||
{
|
||
[NSException raise: NSInternalInconsistencyException
|
||
format: @"[%@-%@] called on cancelled thread",
|
||
NSStringFromClass([self class]),
|
||
NSStringFromSelector(_cmd)];
|
||
}
|
||
if (_finished == YES)
|
||
{
|
||
[NSException raise: NSInternalInconsistencyException
|
||
format: @"[%@-%@] called on finished thread",
|
||
NSStringFromClass([self class]),
|
||
NSStringFromSelector(_cmd)];
|
||
}
|
||
|
||
/* Make sure the notification is posted BEFORE the new thread starts.
|
||
*/
|
||
gnustep_base_thread_callback();
|
||
|
||
/* The thread must persist until it finishes executing.
|
||
*/
|
||
RETAIN(self);
|
||
|
||
/* Mark the thread as active while it's running.
|
||
*/
|
||
_active = YES;
|
||
|
||
errno = 0;
|
||
|
||
#if GS_USE_WIN32_THREADS_AND_LOCKS
|
||
if (_beginthread(nsthreadLauncher, _stackSize, self) == -1)
|
||
{
|
||
DESTROY(self);
|
||
[NSException raise: NSInternalInconsistencyException
|
||
format: @"Unable to detach thread (last error %d)",
|
||
errno];
|
||
}
|
||
#else
|
||
pthread_attr_init(&attr);
|
||
/* Create this thread detached, because we never use the return state from
|
||
* threads.
|
||
*/
|
||
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
|
||
/* Set the stack size when the thread is created. Unlike the old setrlimit
|
||
* code, this actually works.
|
||
*/
|
||
if (_stackSize > 0)
|
||
{
|
||
pthread_attr_setstacksize(&attr, _stackSize);
|
||
}
|
||
if (pthread_create(&pthreadID, &attr, nsthreadLauncher, self))
|
||
{
|
||
DESTROY(self);
|
||
[NSException raise: NSInternalInconsistencyException
|
||
format: @"Unable to detach thread (last error %@)",
|
||
[NSError _last]];
|
||
}
|
||
#endif
|
||
}
|
||
|
||
/**
|
||
* Return the thread dictionary. This dictionary can be used to store
|
||
* arbitrary thread specific data.<br />
|
||
* NB. This cannot be autoreleased, since we cannot be sure that the
|
||
* autorelease pool for the thread will continue to exist for the entire
|
||
* life of the thread!
|
||
*/
|
||
- (NSMutableDictionary*) threadDictionary
|
||
{
|
||
if (_thread_dictionary == nil)
|
||
{
|
||
_thread_dictionary = [NSMutableDictionary new];
|
||
}
|
||
return _thread_dictionary;
|
||
}
|
||
|
||
@end
|
||
|
||
|
||
|
||
@implementation NSThread (GSLockInfo)
|
||
|
||
static NSString *
|
||
lockInfoErr(NSString *str)
|
||
{
|
||
if (disableTraceLocks)
|
||
{
|
||
return nil;
|
||
}
|
||
return str;
|
||
}
|
||
|
||
- (NSString *) mutexDrop: (id)mutex
|
||
{
|
||
if (GS_EXISTS_INTERNAL)
|
||
{
|
||
GSLockInfo *li = &lockInfo;
|
||
int err;
|
||
|
||
if (YES == disableTraceLocks) return nil;
|
||
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 (NSHashGet(li->held, (void*)mutex) == (void*)mutex)
|
||
{
|
||
GSStackTrace *stck = [mutex stack];
|
||
|
||
/* The mutex was being held ... if the recursion count was zero
|
||
* we remove it (otherwise the count is decreased).
|
||
*/
|
||
if (stck->recursion-- == 0)
|
||
{
|
||
NSHashRemove(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)
|
||
{
|
||
GSLockInfo *li = &lockInfo;
|
||
int err;
|
||
|
||
if (YES == disableTraceLocks) return nil;
|
||
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");
|
||
}
|
||
if (NSHashGet(li->held, (void*)mutex) == NULL)
|
||
{
|
||
[[mutex stack] trace]; // Get current strack trace
|
||
NSHashInsert(li->held, (void*)mutex);
|
||
// fprintf(stderr, "%lu: Hold %p (initial) %lu\n", (unsigned long)threadID, mutex, [li->held count]);
|
||
}
|
||
else
|
||
{
|
||
GSStackTrace *stck = [mutex stack];
|
||
|
||
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)
|
||
{
|
||
NSMutableArray *dependencies;
|
||
id want;
|
||
BOOL done;
|
||
GSLockInfo *li = &lockInfo;
|
||
BOOL owned = NO;
|
||
int err;
|
||
|
||
if (YES == disableTraceLocks) return nil;
|
||
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:
|
||
@ "trying to lock %@ when already trying to lock %@",
|
||
mutex, li->wait];
|
||
pthread_spin_unlock(&li->spin);
|
||
return lockInfoErr(msg);
|
||
}
|
||
li->wait = mutex;
|
||
if (nil != NSHashGet(li->held, (const void*)mutex))
|
||
{
|
||
owned = YES;
|
||
}
|
||
pthread_spin_unlock(&li->spin);
|
||
// fprintf(stderr, "%lu: Wait %p\n", (unsigned long)_threadID, mutex);
|
||
if (YES == owned && [mutex isKindOfClass: [NSRecursiveLock class]])
|
||
{
|
||
return nil; // We can't deadlock on a recursive lock we own
|
||
}
|
||
|
||
/* While checking for deadlocks we don't want threads created/destroyed
|
||
* So we hold the lock to prevent thread activity changes.
|
||
* This also ensures that no more than one thread can be checking for
|
||
* deadlocks at a time (no interference between checks).
|
||
*/
|
||
GS_MUTEX_LOCK(_activeLock);
|
||
|
||
/* As we isolate dependencies (a thread holding the lock another thread
|
||
* is waiting for) we disable locking in each thread and record the
|
||
* thread in a hash table. Once we have determined all the dependencies
|
||
* we can re-enable locking in each of the threads.
|
||
*/
|
||
if (nil == _activeBlocked)
|
||
{
|
||
_activeBlocked = NSCreateHashTable(
|
||
NSNonRetainedObjectHashCallBacks, 100);
|
||
}
|
||
|
||
dependencies = nil;
|
||
want = mutex;
|
||
done = NO;
|
||
|
||
while (NO == done)
|
||
{
|
||
NSHashEnumerator enumerator;
|
||
NSThread *found = nil;
|
||
BOOL foundWasLocked = NO;
|
||
NSThread *th;
|
||
|
||
/* Look for a thread which is holding the mutex we are currently
|
||
* interested in. We are only interested in thread which are
|
||
* themselves waiting for a lock (if they aren't waiting then
|
||
* they can't be part of a deadlock dependency list).
|
||
*/
|
||
enumerator = NSEnumerateHashTable(_activeThreads);
|
||
while ((th = NSNextHashEnumeratorItem(&enumerator)) != nil)
|
||
{
|
||
GSLockInfo *info = &GSIVar(th, _lockInfo);
|
||
|
||
if (YES == th->_active && nil != info->wait)
|
||
{
|
||
BOOL wasLocked;
|
||
|
||
if (th == self
|
||
|| NULL != NSHashGet(_activeBlocked, (const void*)th))
|
||
{
|
||
/* Don't lock ... this is the current thread or is
|
||
* already in the set of blocked threads.
|
||
*/
|
||
wasLocked = YES;
|
||
}
|
||
else
|
||
{
|
||
pthread_spin_lock(&info->spin);
|
||
wasLocked = NO;
|
||
}
|
||
if (nil != info->wait
|
||
&& nil != (id)NSHashGet(info->held, (const void*)want))
|
||
{
|
||
/* This thread holds the lock we are interested in and
|
||
* is waiting for another lock.
|
||
* We therefore record the details in the dependency list
|
||
* and will go on to look for the thread this found one
|
||
* depends on.
|
||
*/
|
||
found = th;
|
||
foundWasLocked = wasLocked;
|
||
want = info->wait;
|
||
if (nil == dependencies)
|
||
{
|
||
dependencies = [NSMutableArray new];
|
||
}
|
||
[dependencies addObject: found]; // thread
|
||
[dependencies addObject: want]; // mutex
|
||
/* 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;
|
||
}
|
||
/* This thread did not hold the lock we are interested in,
|
||
* so we can unlock it (if necessary) and check another.
|
||
*/
|
||
if (NO == wasLocked)
|
||
{
|
||
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 (foundWasLocked)
|
||
{
|
||
/* The found thread is the current one or in the blocked set
|
||
* so we have a deadlock.
|
||
*/
|
||
done = YES;
|
||
}
|
||
else
|
||
{
|
||
/* Record the found (and locked) thread and continue
|
||
* to find the next dependency.
|
||
*/
|
||
NSHashInsert(_activeBlocked, (const void*)found);
|
||
}
|
||
}
|
||
|
||
/* 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);
|
||
NSResetHashTable(_activeBlocked);
|
||
}
|
||
|
||
/* Finished check ... re-enable thread activity changes.
|
||
*/
|
||
GS_MUTEX_UNLOCK(_activeLock);
|
||
|
||
|
||
if (nil != dependencies)
|
||
{
|
||
GSStackTrace *stack;
|
||
NSUInteger count;
|
||
NSUInteger index = 0;
|
||
NSMutableString *m;
|
||
|
||
disableTraceLocks = YES;
|
||
m = [NSMutableString stringWithCapacity: 1000];
|
||
stack = [GSStackTrace new];
|
||
[stack trace];
|
||
[m appendFormat: @"Deadlock on %@ at\n %@\n",
|
||
mutex, [stack symbols]];
|
||
RELEASE(stack);
|
||
count = [dependencies count];
|
||
while (index < count)
|
||
{
|
||
NSArray *symbols;
|
||
NSThread *thread;
|
||
NSUInteger frameCount;
|
||
|
||
thread = [dependencies objectAtIndex: index++];
|
||
mutex = [dependencies objectAtIndex: index++];
|
||
symbols = [[mutex stack] 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");
|
||
}
|
||
|
||
@end
|
||
|
||
|
||
|
||
@implementation GSRunLoopThreadInfo
|
||
- (void) addPerformer: (id)performer
|
||
{
|
||
BOOL signalled = NO;
|
||
|
||
[lock lock];
|
||
#if defined(_WIN32)
|
||
if (INVALID_HANDLE_VALUE != event)
|
||
{
|
||
if (SetEvent(event) == 0)
|
||
{
|
||
NSLog(@"Set event failed - %@", [NSError _last]);
|
||
}
|
||
else
|
||
{
|
||
signalled = YES;
|
||
}
|
||
}
|
||
#else
|
||
{
|
||
NSTimeInterval start = 0.0;
|
||
|
||
/* The write could concievably fail if the pipe is full.
|
||
* In that case we need to release the lock temporarily to allow the other
|
||
* thread to consume data from the pipe. It's possible that the thread
|
||
* and its runloop might stop during that ... so we need to check that
|
||
* outputFd is still valid.
|
||
*/
|
||
while (outputFd >= 0
|
||
&& NO == (signalled = (write(outputFd, "0", 1) == 1) ? YES : NO))
|
||
{
|
||
NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
|
||
|
||
if (0.0 == start)
|
||
{
|
||
start = now;
|
||
}
|
||
else if (now - start >= 1.0)
|
||
{
|
||
NSLog(@"Unable to signal %@ within a second; blocked?", self);
|
||
break;
|
||
}
|
||
[lock unlock];
|
||
[lock lock];
|
||
}
|
||
}
|
||
#endif
|
||
if (YES == signalled)
|
||
{
|
||
[performers addObject: performer];
|
||
}
|
||
[lock unlock];
|
||
if (NO == signalled)
|
||
{
|
||
/* We failed to add the performer ... so we must invalidate it in
|
||
* case there is code waiting for it to complete.
|
||
*/
|
||
[performer invalidate];
|
||
}
|
||
}
|
||
|
||
- (void) dealloc
|
||
{
|
||
[self invalidate];
|
||
DESTROY(lock);
|
||
DESTROY(loop);
|
||
[super dealloc];
|
||
}
|
||
|
||
- (id) init
|
||
{
|
||
#ifdef _WIN32
|
||
if ((event = CreateEvent(NULL, TRUE, FALSE, NULL)) == INVALID_HANDLE_VALUE)
|
||
{
|
||
DESTROY(self);
|
||
[NSException raise: NSInternalInconsistencyException
|
||
format: @"Failed to create event to handle perform in thread"];
|
||
}
|
||
#else
|
||
int fd[2];
|
||
|
||
if (pipe(fd) == 0)
|
||
{
|
||
int e;
|
||
|
||
inputFd = fd[0];
|
||
outputFd = fd[1];
|
||
if ((e = fcntl(inputFd, F_GETFL, 0)) >= 0)
|
||
{
|
||
e |= NBLK_OPT;
|
||
if (fcntl(inputFd, F_SETFL, e) < 0)
|
||
{
|
||
[NSException raise: NSInternalInconsistencyException
|
||
format: @"Failed to set non block flag for perform in thread"];
|
||
}
|
||
}
|
||
else
|
||
{
|
||
[NSException raise: NSInternalInconsistencyException
|
||
format: @"Failed to get non block flag for perform in thread"];
|
||
}
|
||
if ((e = fcntl(outputFd, F_GETFL, 0)) >= 0)
|
||
{
|
||
e |= NBLK_OPT;
|
||
if (fcntl(outputFd, F_SETFL, e) < 0)
|
||
{
|
||
[NSException raise: NSInternalInconsistencyException
|
||
format: @"Failed to set non block flag for perform in thread"];
|
||
}
|
||
}
|
||
else
|
||
{
|
||
[NSException raise: NSInternalInconsistencyException
|
||
format: @"Failed to get non block flag for perform in thread"];
|
||
}
|
||
}
|
||
else
|
||
{
|
||
DESTROY(self);
|
||
[NSException raise: NSInternalInconsistencyException
|
||
format: @"Failed to create pipe to handle perform in thread"];
|
||
}
|
||
#endif
|
||
lock = [NSLock new];
|
||
performers = [NSMutableArray new];
|
||
return self;
|
||
}
|
||
|
||
- (void) invalidate
|
||
{
|
||
NSArray *p;
|
||
|
||
[lock lock];
|
||
p = AUTORELEASE(performers);
|
||
performers = nil;
|
||
#ifdef _WIN32
|
||
if (event != INVALID_HANDLE_VALUE)
|
||
{
|
||
CloseHandle(event);
|
||
event = INVALID_HANDLE_VALUE;
|
||
}
|
||
#else
|
||
if (inputFd >= 0)
|
||
{
|
||
close(inputFd);
|
||
inputFd = -1;
|
||
}
|
||
if (outputFd >= 0)
|
||
{
|
||
close(outputFd);
|
||
outputFd = -1;
|
||
}
|
||
#endif
|
||
[lock unlock];
|
||
[p makeObjectsPerformSelector: @selector(invalidate)];
|
||
}
|
||
|
||
- (void) fire
|
||
{
|
||
NSArray *toDo;
|
||
unsigned int i;
|
||
unsigned int c;
|
||
|
||
[lock lock];
|
||
#if defined(_WIN32)
|
||
if (event != INVALID_HANDLE_VALUE)
|
||
{
|
||
if (ResetEvent(event) == 0)
|
||
{
|
||
NSLog(@"Reset event failed - %@", [NSError _last]);
|
||
}
|
||
}
|
||
#else
|
||
if (inputFd >= 0)
|
||
{
|
||
char buf[BUFSIZ];
|
||
|
||
/* We don't care how much we read. If there have been multiple
|
||
* performers queued then there will be multiple bytes available,
|
||
* but we always handle all available performers, so we can also
|
||
* read all available bytes.
|
||
* The descriptor is non-blocking ... so it's safe to ask for more
|
||
* bytes than are available.
|
||
*/
|
||
while (read(inputFd, buf, sizeof(buf)) > 0)
|
||
;
|
||
}
|
||
#endif
|
||
|
||
c = [performers count];
|
||
if (0 == c)
|
||
{
|
||
/* We deal with all available performers each time we fire, so
|
||
* it's likely that we will fire when we have no performers left.
|
||
* In that case we can skip the copying and emptying of the array.
|
||
*/
|
||
[lock unlock];
|
||
return;
|
||
}
|
||
toDo = [NSArray arrayWithArray: performers];
|
||
[performers removeAllObjects];
|
||
[lock unlock];
|
||
|
||
for (i = 0; i < c; i++)
|
||
{
|
||
GSPerformHolder *h = [toDo objectAtIndex: i];
|
||
|
||
[loop performSelector: @selector(fire)
|
||
target: h
|
||
argument: nil
|
||
order: 0
|
||
modes: [h modes]];
|
||
}
|
||
}
|
||
@end
|
||
|
||
GSRunLoopThreadInfo *
|
||
GSRunLoopInfoForThread(NSThread *aThread)
|
||
{
|
||
GSRunLoopThreadInfo *info;
|
||
|
||
if (aThread == nil)
|
||
{
|
||
aThread = GSCurrentThread();
|
||
}
|
||
if (aThread->_runLoopInfo == nil)
|
||
{
|
||
static gs_mutex_t infoLock = GS_MUTEX_INIT_STATIC;
|
||
|
||
GS_MUTEX_LOCK(infoLock);
|
||
if (aThread->_runLoopInfo == nil)
|
||
{
|
||
aThread->_runLoopInfo = [GSRunLoopThreadInfo new];
|
||
}
|
||
GS_MUTEX_UNLOCK(infoLock);
|
||
}
|
||
info = aThread->_runLoopInfo;
|
||
return info;
|
||
}
|
||
|
||
@implementation GSPerformHolder
|
||
|
||
+ (GSPerformHolder*) newForReceiver: (id)r
|
||
argument: (id)a
|
||
selector: (SEL)s
|
||
modes: (NSArray*)m
|
||
lock: (NSConditionLock*)l
|
||
{
|
||
GSPerformHolder *h;
|
||
|
||
h = (GSPerformHolder*)NSAllocateObject(self, 0, NSDefaultMallocZone());
|
||
h->receiver = RETAIN(r);
|
||
h->argument = RETAIN(a);
|
||
h->selector = s;
|
||
h->modes = RETAIN(m);
|
||
h->lock = l;
|
||
|
||
return h;
|
||
}
|
||
|
||
- (void) dealloc
|
||
{
|
||
DESTROY(exception);
|
||
DESTROY(receiver);
|
||
DESTROY(argument);
|
||
DESTROY(modes);
|
||
if (lock != nil)
|
||
{
|
||
[lock lock];
|
||
[lock unlockWithCondition: 1];
|
||
lock = nil;
|
||
}
|
||
NSDeallocateObject(self);
|
||
GSNOSUPERDEALLOC;
|
||
}
|
||
|
||
- (NSString*) description
|
||
{
|
||
return [[super description] stringByAppendingFormat: @" [%s %s]",
|
||
class_getName(object_getClass(receiver)), sel_getName(selector)];
|
||
}
|
||
|
||
- (void) fire
|
||
{
|
||
GSRunLoopThreadInfo *threadInfo;
|
||
|
||
if (receiver == nil)
|
||
{
|
||
return; // Already fired!
|
||
}
|
||
threadInfo = GSRunLoopInfoForThread(GSCurrentThread());
|
||
[threadInfo->loop cancelPerformSelectorsWithTarget: self];
|
||
NS_DURING
|
||
{
|
||
[receiver performSelector: selector withObject: argument];
|
||
}
|
||
NS_HANDLER
|
||
{
|
||
ASSIGN(exception, localException);
|
||
if (nil == lock)
|
||
{
|
||
NSLog(@"*** NSRunLoop ignoring exception '%@' (reason '%@') "
|
||
@"raised during perform in other thread... with receiver %p (%s) "
|
||
@"and selector '%s'",
|
||
[localException name], [localException reason], receiver,
|
||
class_getName(object_getClass(receiver)),
|
||
sel_getName(selector));
|
||
}
|
||
}
|
||
NS_ENDHANDLER
|
||
DESTROY(receiver);
|
||
DESTROY(argument);
|
||
DESTROY(modes);
|
||
if (lock != nil)
|
||
{
|
||
NSConditionLock *l = lock;
|
||
|
||
[lock lock];
|
||
lock = nil;
|
||
[l unlockWithCondition: 1];
|
||
}
|
||
}
|
||
|
||
- (void) invalidate
|
||
{
|
||
if (invalidated == NO)
|
||
{
|
||
invalidated = YES;
|
||
DESTROY(receiver);
|
||
if (lock != nil)
|
||
{
|
||
NSConditionLock *l = lock;
|
||
|
||
[lock lock];
|
||
lock = nil;
|
||
[l unlockWithCondition: 1];
|
||
}
|
||
}
|
||
}
|
||
|
||
- (BOOL) isInvalidated
|
||
{
|
||
return invalidated;
|
||
}
|
||
|
||
- (NSArray*) modes
|
||
{
|
||
return modes;
|
||
}
|
||
@end
|
||
|
||
@implementation NSObject (NSThreadPerformAdditions)
|
||
|
||
- (void) performSelectorOnMainThread: (SEL)aSelector
|
||
withObject: (id)anObject
|
||
waitUntilDone: (BOOL)aFlag
|
||
modes: (NSArray*)anArray
|
||
{
|
||
/* It's possible that this method could be called before the NSThread
|
||
* class is initialised, so we check and make sure it's initiailised
|
||
* if necessary.
|
||
*/
|
||
if (defaultThread == nil)
|
||
{
|
||
[NSThread currentThread];
|
||
}
|
||
[self performSelector: aSelector
|
||
onThread: defaultThread
|
||
withObject: anObject
|
||
waitUntilDone: aFlag
|
||
modes: anArray];
|
||
}
|
||
|
||
- (void) performSelectorOnMainThread: (SEL)aSelector
|
||
withObject: (id)anObject
|
||
waitUntilDone: (BOOL)aFlag
|
||
{
|
||
[self performSelectorOnMainThread: aSelector
|
||
withObject: anObject
|
||
waitUntilDone: aFlag
|
||
modes: commonModes()];
|
||
}
|
||
|
||
- (void) performSelector: (SEL)aSelector
|
||
onThread: (NSThread*)aThread
|
||
withObject: (id)anObject
|
||
waitUntilDone: (BOOL)aFlag
|
||
modes: (NSArray*)anArray
|
||
{
|
||
GSRunLoopThreadInfo *info;
|
||
NSThread *t;
|
||
|
||
if ([anArray count] == 0)
|
||
{
|
||
return;
|
||
}
|
||
|
||
t = GSCurrentThread();
|
||
if (aThread == nil)
|
||
{
|
||
aThread = t;
|
||
}
|
||
info = GSRunLoopInfoForThread(aThread);
|
||
if (t == aThread)
|
||
{
|
||
/* Perform in current thread.
|
||
*/
|
||
if (aFlag == YES || info->loop == nil)
|
||
{
|
||
/* Wait until done or no run loop.
|
||
*/
|
||
[self performSelector: aSelector withObject: anObject];
|
||
}
|
||
else
|
||
{
|
||
/* Don't wait ... schedule operation in run loop.
|
||
*/
|
||
[info->loop performSelector: aSelector
|
||
target: self
|
||
argument: anObject
|
||
order: 0
|
||
modes: anArray];
|
||
}
|
||
}
|
||
else
|
||
{
|
||
GSPerformHolder *h;
|
||
NSConditionLock *l = nil;
|
||
|
||
if ([aThread isFinished] == YES)
|
||
{
|
||
[NSException raise: NSInternalInconsistencyException
|
||
format: @"perform [%@-%@] attempted on finished thread (%@)",
|
||
NSStringFromClass([self class]),
|
||
NSStringFromSelector(aSelector),
|
||
aThread];
|
||
}
|
||
if (aFlag == YES)
|
||
{
|
||
l = [[NSConditionLock alloc] init];
|
||
}
|
||
|
||
h = [GSPerformHolder newForReceiver: self
|
||
argument: anObject
|
||
selector: aSelector
|
||
modes: anArray
|
||
lock: l];
|
||
[info addPerformer: h];
|
||
if (l != nil)
|
||
{
|
||
[l lockWhenCondition: 1];
|
||
[l unlock];
|
||
RELEASE(l);
|
||
if ([h isInvalidated] == NO)
|
||
{
|
||
/* If we have an exception passed back from the remote thread,
|
||
* re-raise it.
|
||
*/
|
||
if (nil != h->exception)
|
||
{
|
||
NSException *e = AUTORELEASE(RETAIN(h->exception));
|
||
|
||
RELEASE(h);
|
||
[e raise];
|
||
}
|
||
}
|
||
}
|
||
RELEASE(h);
|
||
}
|
||
}
|
||
|
||
- (void) performSelector: (SEL)aSelector
|
||
onThread: (NSThread*)aThread
|
||
withObject: (id)anObject
|
||
waitUntilDone: (BOOL)aFlag
|
||
{
|
||
[self performSelector: aSelector
|
||
onThread: aThread
|
||
withObject: anObject
|
||
waitUntilDone: aFlag
|
||
modes: commonModes()];
|
||
}
|
||
|
||
- (void) performSelectorInBackground: (SEL)aSelector
|
||
withObject: (id)anObject
|
||
{
|
||
[NSThread detachNewThreadSelector: aSelector
|
||
toTarget: self
|
||
withObject: anObject];
|
||
}
|
||
|
||
@end
|
||
|
||
/**
|
||
* <p>
|
||
* This function is provided to let threads started by some other
|
||
* software library register themselves to be used with the
|
||
* GNUstep system. All such threads should call this function
|
||
* before attempting to use any GNUstep objects.
|
||
* </p>
|
||
* <p>
|
||
* Returns <code>YES</code> if the thread can be registered,
|
||
* <code>NO</code> if it is already registered.
|
||
* </p>
|
||
* <p>
|
||
* Sends out a <code>NSWillBecomeMultiThreadedNotification</code>
|
||
* if the process was not already multithreaded.
|
||
* </p>
|
||
*/
|
||
BOOL
|
||
GSRegisterCurrentThread(void)
|
||
{
|
||
return [NSThread _createThreadForCurrentPthread];
|
||
}
|
||
|
||
/**
|
||
* <p>
|
||
* This function is provided to let threads started by some other
|
||
* software library unregister themselves from the GNUstep threading
|
||
* system.
|
||
* </p>
|
||
* <p>
|
||
* Calling this function causes a
|
||
* <code>NSThreadWillExitNotification</code>
|
||
* to be sent out, and destroys the GNUstep NSThread object
|
||
* associated with the thread (like [NSThread+exit]) but does
|
||
* not exit the underlying thread.
|
||
* </p>
|
||
*/
|
||
void
|
||
GSUnregisterCurrentThread(void)
|
||
{
|
||
unregisterActiveThread(GSCurrentThread());
|
||
}
|