Index: src/OFExceptions.h ================================================================== --- src/OFExceptions.h +++ src/OFExceptions.h @@ -866,16 +866,28 @@ * \return The errno from when the exception was created */ - (int)errNo; @end +/** + * \brief An exception indicating that starting a thread failed. + */ +@interface OFThreadStartFailedException: OFException {} +@end + /** * \brief An exception indicating that joining a thread failed. */ @interface OFThreadJoinFailedException: OFException {} @end +/** + * \brief An exception indicating that a thread is still running. + */ +@interface OFThreadStillRunningException: OFException {} +@end + /** * \brief An exception indicating that locking a mutex failed. */ @interface OFMutexLockFailedException: OFException {} @end Index: src/OFExceptions.m ================================================================== --- src/OFExceptions.m +++ src/OFExceptions.m @@ -1231,10 +1231,23 @@ - (int)errNo { return err; } @end + +@implementation OFThreadStartFailedException +- (OFString*)string +{ + if (string != nil) + return string; + + string = [[OFString alloc] initWithFormat: + @"Starting a thread of class %s failed!", [class_ className]]; + + return string; +} +@end @implementation OFThreadJoinFailedException - (OFString*)string { if (string != nil) @@ -1242,10 +1255,24 @@ string = [[OFString alloc] initWithFormat: @"Joining a thread of class %s failed! Most likely, another thread " @"already waits for the thread to join.", [class_ className]]; + return string; +} +@end + +@implementation OFThreadStillRunningException +- (OFString*)string +{ + if (string != nil) + return string; + + string = [[OFString alloc] initWithFormat: + @"Deallocation of a thread of type %s was tried, even though it " + @"was still running", [class_ className]]; + return string; } @end @implementation OFMutexLockFailedException Index: src/OFThread.h ================================================================== --- src/OFThread.h +++ src/OFThread.h @@ -59,12 +59,16 @@ */ @interface OFThread: OFObject { id object; of_thread_t thread; - BOOL running; @public + enum { + OF_THREAD_NOT_RUNNING, + OF_THREAD_RUNNING, + OF_THREAD_WAITING_FOR_JOIN + } running; id retval; } /** * \param obj An object that is passed to the main method as a copy or nil @@ -90,32 +94,60 @@ * * \param key The Thread Local Storage key */ + (id)objectForTLSKey: (OFTLSKey*)key; +/** + * \return The current thread or nil if we are in the main thread + */ ++ (OFThread*)currentThread; + +/** + * Terminates the current thread, letting it return nil. + */ ++ (void)terminate; + +/** + * Terminates the current thread, letting it return the specified object. + * + * \param The object which the terminated thread will return + */ ++ (void)terminateWithObject: (id)obj; + /** * \param obj An object that is passed to the main method as a copy or nil * \return An initialized OFThread. */ - initWithObject: (OFObject *)obj; /** - * The main routine of the thread. You need to reimplement this! + * The run routine of the thread. You need to reimplement this! * * It can access the object passed to the threadWithObject or initWithObject * method using the instance variable named object. * * \return The object the join method should return when called for this thread */ -- (id)main; +- (id)run; + +/** + * This routine is exectued when the thread's run method has finished executing + * or terminate has been called. + */ +- (void)handleTermination; + +/** + * Starts the thread. + */ +- start; /** * Joins a thread. * * \return The object returned by the main method of the thread. */ -- join; +- (id)join; @end /** * \brief A class for creating mutual exclusions. */ Index: src/OFThread.m ================================================================== --- src/OFThread.m +++ src/OFThread.m @@ -17,27 +17,47 @@ #import "OFExceptions.h" #import "threading.h" static OFList *tlskeys; +static of_tlskey_t thread_self; static id -call_main(id obj) +call_run(id obj) { + if (!of_tlskey_set(thread_self, obj)) + @throw [OFInitializationFailedException + newWithClass: [obj class]]; + /* * Nasty workaround for thread implementations which can't return a * value on join. */ - ((OFThread*)obj)->retval = [obj main]; + ((OFThread*)obj)->retval = [[obj run] retain]; + + [obj handleTermination]; + + ((OFThread*)obj)->running = OF_THREAD_WAITING_FOR_JOIN; [OFTLSKey callAllDestructors]; [OFAutoreleasePool releaseAll]; + + [obj release]; return 0; } @implementation OFThread ++ (void)initialize +{ + if (self != [OFThread class]) + return; + + if (!of_tlskey_new(&thread_self)) + @throw [OFInitializationFailedException newWithClass: self]; +} + + threadWithObject: (OFObject *)obj { return [[[self alloc] initWithObject: obj] autorelease]; } @@ -57,10 +77,40 @@ + (id)objectForTLSKey: (OFTLSKey*)key { return [[of_tlskey_get(key->key) retain] autorelease]; } + ++ (OFThread*)currentThread +{ + return of_tlskey_get(thread_self); +} + ++ (void)terminate +{ + [self terminateWithObject: nil]; +} + ++ (void)terminateWithObject: (id)obj +{ + OFThread *thread = of_tlskey_get(thread_self); + + if (thread != nil) { + thread->retval = [obj retain]; + + [thread handleTermination]; + + thread->running = OF_THREAD_WAITING_FOR_JOIN; + } + + [OFTLSKey callAllDestructors]; + [OFAutoreleasePool releaseAll]; + + [thread release]; + + of_thread_exit(); +} - init { @throw [OFNotImplementedException newWithClass: isa selector: _cmd]; @@ -67,48 +117,57 @@ } - initWithObject: (OFObject *)obj { self = [super init]; + object = [obj retain]; - if (!of_thread_new(&thread, call_main, self)) { - Class c = isa; - [object release]; - [super dealloc]; - @throw [OFInitializationFailedException newWithClass: c]; - } - - running = YES; + return self; +} + +- (id)run +{ + @throw [OFNotImplementedException newWithClass: isa + selector: _cmd]; + + return nil; +} + +- (void)handleTermination +{ +} + +- start +{ + if (!of_thread_new(&thread, call_run, self)) + @throw [OFThreadStartFailedException newWithClass: isa]; + + running = OF_THREAD_RUNNING; + [self retain]; return self; } -- main -{ - return nil; -} - -- join -{ - of_thread_join(thread); - running = NO; +- (id)join +{ + if (running != OF_THREAD_WAITING_FOR_JOIN || !of_thread_join(thread)) + @throw [OFThreadJoinFailedException newWithClass: isa]; + + running = OF_THREAD_NOT_RUNNING; return retval; } - (void)dealloc { - /* - * No need to handle errors - if canceling the thread fails, we can't - * do anything anyway. Most likely, it finished already or was already - * canceled. - */ - if (running) - of_thread_cancel(thread); + if (running == OF_THREAD_RUNNING) + @throw [OFThreadStillRunningException newWithClass: isa]; [object release]; + [retval release]; + [super dealloc]; } @end @implementation OFTLSKey Index: src/threading.h ================================================================== --- src/threading.h +++ src/threading.h @@ -76,22 +76,17 @@ return YES; #endif } -static OF_INLINE BOOL -of_thread_cancel(of_thread_t thread) +static OF_INLINE void +of_thread_exit() { #if defined(OF_HAVE_PTHREADS) - return (pthread_cancel(thread) ? NO : YES); + pthread_exit(NULL); #elif defined(_WIN32) - if (thread != INVALID_HANDLE_VALUE) { - TerminateThread(thread, 1); - CloseHandle(thread); - } - - return YES; + ExitThread(0); #endif } static OF_INLINE BOOL of_mutex_new(of_mutex_t *mutex) Index: tests/OFThreadTests.m ================================================================== --- tests/OFThreadTests.m +++ tests/OFThreadTests.m @@ -22,11 +22,11 @@ @interface TestThread: OFThread @end @implementation TestThread -- main +- (id)run { if ([object isEqual: @"foo"]) return @"success"; return nil; @@ -40,10 +40,12 @@ TestThread *t; OFTLSKey *key; TEST(@"+[threadWithObject:]", (t = [TestThread threadWithObject: @"foo"])) + + TEST(@"-[run]", [t start]) TEST(@"-[join]", [[t join] isEqual: @"success"]) TEST(@"OFTLSKey's +[tlsKey]", (key = [OFTLSKey tlsKey]))