4 Copyright (C) 1995, 1996 Ovidiu Predescu and Mircea Oancea.
7 Author: Ovidiu Predescu <ovidiu@bx.logicnet.ro>
8 Helge Hess <helge.hess@mdlink.de>
10 This file is part of libFoundation.
12 Permission to use, copy, modify, and distribute this software and its
13 documentation for any purpose and without fee is hereby granted, provided
14 that the above copyright notice appear in all copies and that both that
15 copyright notice and this permission notice appear in supporting
18 We disclaim all warranties with regard to this software, including all
19 implied warranties of merchantability and fitness, in no event shall
20 we be liable for any special, indirect or consequential damages or any
21 damages whatsoever resulting from loss of use, data or profits, whether in
22 an action of contract, negligence or other tortious action, arising out of
23 or in connection with the use or performance of this software.
26 #include <Foundation/common.h>
28 #include <sys/types.h>
30 #include <sys/errno.h>
35 # include <sys/time.h> /* for struct timeval */
47 # define memcpy(d, s, n) bcopy((s), (d), (n))
48 # define memmove(d, s, n) bcopy((s), (d), (n))
62 # include <sys/select.h>
65 #include <Foundation/NSRunLoop.h>
66 #include <Foundation/NSArray.h>
67 #include <Foundation/NSDictionary.h>
68 #include <Foundation/NSString.h>
69 #include <Foundation/NSValue.h>
70 #include <Foundation/NSAutoreleasePool.h>
71 #include <Foundation/NSDate.h>
72 #include <Foundation/NSTimer.h>
73 #include <Foundation/NSUtilities.h>
74 #include <Foundation/NSThread.h>
75 #include <Foundation/NSLock.h>
76 #include <Foundation/NSException.h>
77 #include <Foundation/NSPosixFileDescriptor.h>
78 #include <Foundation/NSNotification.h>
79 #include <Foundation/NSNotificationQueue.h>
80 #include <Foundation/UnixSignalHandler.h>
82 #include <extensions/objc-runtime.h>
84 NSString* NSDefaultRunLoopMode = @"NSDefaultRunLoopMode";
85 NSString* NSConnectionReplyMode = @"NSConnectionReplyMode";
86 NSString* NSFileObjectBecameActiveNotificationName =
87 @"NSFileObjectBecameActiveNotificationName";
89 static char *activityDesc[8] = {
100 @interface NSRunLoopFileObjectInfo : NSObject
103 NSPosixFileActivities watchedActivities;
107 - (id)initWithFileObject:(id)_fileObject
108 activities:(NSPosixFileActivities)_activities;
111 - (int)fileDescriptor;
112 - (NSPosixFileActivities)watchedActivities;
114 - (void)activity:(NSPosixFileActivities)_activity onDescriptor:(int)_fd;
118 @implementation NSRunLoopFileObjectInfo
120 - (id)initWithFileObject:(id)_fileObject
121 activities:(NSPosixFileActivities)_activities
123 self->fileObject = RETAIN(_fileObject);
124 self->watchedActivities = _activities;
125 self->canCheckAlive = [_fileObject respondsToSelector:@selector(isAlive)];
130 NSLog(@"ERROR: do not use init with NSRunLoopFileObjectInfo ..");
131 self = AUTORELEASE(self);
137 RELEASE(self->fileObject);
141 - (BOOL)isEqual:(id)otherInfo
143 if (otherInfo == nil)
145 if (*(Class *)self != *(Class *)otherInfo) /* check class */
148 return [self->fileObject isEqual:
149 ((NSRunLoopFileObjectInfo *)otherInfo)->fileObject];
153 return (self->canCheckAlive) ? [self->fileObject isAlive] : YES;
155 - (int)fileDescriptor
157 return [self->fileObject fileDescriptor];
160 - (NSPosixFileActivities)watchedActivities
162 return self->watchedActivities;
165 - (void)activity:(NSPosixFileActivities)_activity onDescriptor:(int)_fd
168 NSLog(@"%s:%i: FileObject %@ became active ..", __PRETTY_FUNCTION__,__LINE__,
172 if ([self->fileObject isKindOfClass:[NSPosixFileDescriptor class]]) {
173 if (_activity & NSPosixReadableActivity) {
174 [[self->fileObject delegate]
175 activity:NSPosixReadableActivity
176 posixFileDescriptor:self->fileObject];
178 if (_activity & NSPosixWritableActivity) {
179 [[self->fileObject delegate]
180 activity:NSPosixWritableActivity
181 posixFileDescriptor:self->fileObject];
183 if (_activity & NSPosixExceptionalActivity) {
184 [[self->fileObject delegate]
185 activity:NSPosixExceptionalActivity
186 posixFileDescriptor:self->fileObject];
190 [[NSNotificationCenter defaultCenter]
191 postNotificationName:
192 NSFileObjectBecameActiveNotificationName
193 object:self->fileObject];
197 - (NSString *)description
199 return [NSString stringWithFormat:
200 @"<%@[0x%p]: object=%@ actitivity=%s>",
201 NSStringFromClass([self class]), self,
203 activityDesc[self->watchedActivities]
209 @interface NSRunLoopTimerInfo : NSObject
215 + (NSRunLoopTimerInfo*)infoWithTimer:(NSTimer*)timer;
216 - (void)recomputeFireDate;
217 - (NSComparisonResult)compare:(NSRunLoopTimerInfo*)anObject;
222 @implementation NSRunLoopTimerInfo
224 + (NSRunLoopTimerInfo *)infoWithTimer:(NSTimer*)aTimer
226 NSRunLoopTimerInfo *info = [self new];
228 info->timer = RETAIN(aTimer);
229 info->fireDate = RETAIN([aTimer fireDate]);
230 return AUTORELEASE(info);
235 RELEASE(self->timer);
236 RELEASE(self->fireDate);
240 - (void)recomputeFireDate
242 if ([self->timer isValid]) {
243 id tmp = [self->timer fireDate];
244 ASSIGN(self->fireDate, tmp);
248 - (NSComparisonResult)compare:(NSRunLoopTimerInfo*)anObject
250 return [self->fireDate compare:anObject->fireDate];
253 - (NSTimer *)timer { return self->timer; }
254 - (NSDate *)fireDate { return self->fireDate; }
258 @interface NSRunLoopActionHolder : NSObject
265 + objectWithTarget:(id)target
266 argument:(id)argument
269 - (BOOL)isEqual:(id)anotherHolder;
273 @implementation NSRunLoopActionHolder
275 + (id)objectWithTarget:(id)_target
276 argument:(id)_argument
277 selector:(SEL)_action
280 NSRunLoopActionHolder* holder = AUTORELEASE([self alloc]);
282 holder->target = RETAIN(_target);
283 holder->argument = RETAIN(_argument);
284 holder->action = _action;
285 holder->order = _order;
292 return [(NSObject *)self->target hash];
295 - (BOOL)isEqual:(id)otherObject
297 NSRunLoopActionHolder *anotherHolder;
299 if ((anotherHolder = otherObject) == nil)
301 if (![anotherHolder isKindOfClass:[NSRunLoopActionHolder class]])
304 return [self->target isEqual:anotherHolder->target]
305 && [argument isEqual:anotherHolder->argument]
306 && SEL_EQ(self->action, anotherHolder->action);
311 [self->target performSelector:self->action withObject:self->argument];
314 - (NSComparisonResult)compare:(NSRunLoopActionHolder*)anotherHolder
316 return (order - anotherHolder->order);
319 @end /* NSRunLoopActionHolder */
322 @interface NSRunLoopInputManager : NSObject
324 NSMutableArray *fileObjects;
325 NSMutableArray *timers;
326 NSMutableArray *otherOperations;
329 - (void)addFileObject:(id)_fileObject
330 activities:(NSPosixFileActivities)_activities;
331 - (void)removeFileObject:(id)_fileObject;
333 - (void)addPosixFileDescriptor:(NSPosixFileDescriptor*)fileDescriptor;
334 - (void)removePosixFileDescriptor:(NSPosixFileDescriptor*)fileDescriptor;
336 - (void)addTimer:(NSTimer*)aTimer;
338 - (NSMutableArray*)fileObjects;
339 - (NSMutableArray*)timers;
341 - (void)addOperation:(NSRunLoopActionHolder*)holder;
342 - (void)removeOperation:(NSRunLoopActionHolder*)holder;
343 - (void)performAdditionalOperations;
347 @implementation NSRunLoopInputManager
351 NSZone *z = [self zone];
352 self->fileObjects = [[NSMutableArray allocWithZone:z] init];
353 self->timers = [[NSMutableArray allocWithZone:z] init];
354 self->otherOperations = [[NSMutableArray allocWithZone:z] init];
360 RELEASE(self->fileObjects);
361 RELEASE(self->timers);
362 RELEASE(self->otherOperations);
366 - (void)addFileObject:(id)_fileObject
367 activities:(NSPosixFileActivities)_activities
369 NSRunLoopFileObjectInfo *info = nil;
370 //NSAssert(_activities, @"no activity to watch ?!");
371 info = [[NSRunLoopFileObjectInfo allocWithZone:[self zone]]
372 initWithFileObject:_fileObject
373 activities:_activities];
374 [self->fileObjects addObject:info];
375 //NSLog(@"file objects now: %@", self->fileObjects);
376 RELEASE(info); info = nil;
378 - (void)removeFileObject:(id)_fileObject
380 NSRunLoopFileObjectInfo *info = nil;
381 info = [[NSRunLoopFileObjectInfo allocWithZone:[self zone]]
382 initWithFileObject:_fileObject
384 [self->fileObjects removeObject:info];
385 //NSLog(@"file objects now: %@", self->fileObjects);
386 RELEASE(info); info = nil;
389 - (void)addPosixFileDescriptor:(NSPosixFileDescriptor*)fileDescriptor
391 [self addFileObject:fileDescriptor
392 activities:[fileDescriptor fileActivity]];
395 - (void)removePosixFileDescriptor:(NSPosixFileDescriptor*)fileDescriptor
397 [self removeFileObject:fileDescriptor];
400 - (void)addTimer:(NSTimer*)aTimer
402 [self->timers addObject:[NSRunLoopTimerInfo infoWithTimer:aTimer]];
405 - (void)addOperation:(NSRunLoopActionHolder*)holder
407 [self->otherOperations addObject:holder];
408 [self->otherOperations sortUsingSelector:@selector(compare:)];
411 - (void)removeOperation:(NSRunLoopActionHolder*)holder
413 [self->otherOperations removeObject:holder];
416 - (void)performAdditionalOperations
418 [self->otherOperations makeObjectsPerform:@selector(execute)];
421 - (NSMutableArray *)fileObjects
423 return self->fileObjects;
425 - (NSMutableArray *)timers
430 @end /* NSRunLoopInputManager */
433 @implementation NSRunLoop
435 extern NSRecursiveLock* libFoundationLock;
438 static NSMutableDictionary* runLoopsDictionary = nil;
439 static NSRunLoop* currentRunLoop = nil;
440 static BOOL taskIsMultithreaded = NO;
445 [[NSNotificationCenter defaultCenter]
447 selector:@selector(taskNowMultiThreaded:)
448 name:NSWillBecomeMultiThreadedNotification
453 + (NSRunLoop *)currentRunLoop
455 if (taskIsMultithreaded) {
456 NSRunLoop* currentLoop;
457 NSThread* currentThread;
459 [libFoundationLock lock];
461 currentThread = [NSThread currentThread];
462 currentLoop = [runLoopsDictionary objectForKey:currentThread];
464 currentLoop = AUTORELEASE([self new]);
465 [runLoopsDictionary setObject:currentLoop forKey:currentThread];
468 [libFoundationLock unlock];
474 currentRunLoop = [self new];
475 return currentRunLoop;
479 + (void)taskNowMultiThreaded:(NSNotification *)notification
481 taskIsMultithreaded = YES;
482 runLoopsDictionary = [NSMutableDictionary new];
484 if (currentRunLoop) {
485 NSThread* currentThread = [NSThread currentThread];
487 [runLoopsDictionary setObject:currentRunLoop forKey:currentThread];
488 RELEASE(currentRunLoop);
489 currentRunLoop = nil;
496 self->inputsForMode =
497 [[NSMutableDictionary allocWithZone:[self zone]] init];
498 self->mode = RETAIN(NSDefaultRunLoopMode);
504 RELEASE(self->inputsForMode);
509 - (NSString*)currentMode
514 static inline NSRunLoopInputManager*
515 _getInputManager(NSRunLoop *self, NSString *_mode)
517 NSRunLoopInputManager* inputManager;
519 inputManager = [self->inputsForMode objectForKey:_mode];
520 if (inputManager == nil) {
521 inputManager = [[NSRunLoopInputManager alloc] init];
522 [self->inputsForMode setObject:inputManager forKey:_mode];
523 RELEASE(inputManager);
528 static int compare_fire_dates(id timer1, id timer2, void* userData)
530 return [[timer1 fireDate] compare:[timer2 fireDate]];
533 - (NSDate*)limitDateForMode:(NSString*)aMode
536 @"%s: During NSTimer:-fire, caught exception %@ with reason %@ ";
537 NSMutableArray *timers = [[inputsForMode objectForKey:aMode] timers];
538 volatile int i, count;
539 NSMutableArray *copyOfTimers;
543 /* Remove invalid timers */
544 for(count = [timers count], i = count - 1; i >= 0; i--) {
545 if(![[[timers objectAtIndex:i] timer] isValid]) {
546 [timers removeObjectAtIndex:i];
551 /* Currently only timers have limit dates associated with them */
555 copyOfTimers = [timers mutableCopy];
557 /* Sort the timers based on their fire date */
558 [copyOfTimers sortUsingFunction:compare_fire_dates context:NULL];
560 /* Fire all the timers with their fire date expired */
561 for(i = 0; i < count; i++) {
562 NSRunLoopTimerInfo* timerInfo = [copyOfTimers objectAtIndex:i];
563 NSDate* fireDate = [timerInfo fireDate];
564 NSDate* currentDate = [NSDate date];
566 if([fireDate earlierDate:currentDate] == fireDate
567 || [fireDate isEqualToDate:currentDate]) {
568 NSTimer* timer = [timerInfo timer];
573 NSLog(format, "NSRunLoop(-limitDateForMode:)",
574 [localException name], [localException reason]);
583 RELEASE(copyOfTimers);
585 /* Recompute the fire dates for this cycle */
586 [timers makeObjectsPerform:@selector(recomputeFireDate)];
588 /* Sort the timers based on their fire date */
589 [timers sortUsingFunction:compare_fire_dates context:NULL];
591 return [timers count] > 0
592 ? [[timers objectAtIndex:0] fireDate] : (NSDate *)nil;
595 - (void)addTimer:(NSTimer *)aTimer forMode:(NSString *)aMode
597 [_getInputManager(self, aMode) addTimer:aTimer];
600 - (BOOL)runMode:(NSString *)aMode beforeDate:(NSDate *)limitDate
602 id inputManager, fileObjects;
606 /* Retain the limitDate so it doesn't get released by limitDateForMode:
607 if it fires a timer that has as fireDate the limitDate.
608 (bug report from Benhur Stein <Benhur-de-Oliveira.Stein@imag.fr>)
610 (void)RETAIN(limitDate);
612 inputManager = [self->inputsForMode objectForKey:aMode];
613 timers = [inputManager timers];
614 fileObjects = [inputManager fileObjects];
616 if (([timers count] != 0) || ([fileObjects count] != 0)) {
617 CREATE_AUTORELEASE_POOL(pool);
619 date = [self limitDateForMode:aMode];
620 date = date ? [date earlierDate:limitDate] : limitDate;
621 [self acceptInputForMode:aMode beforeDate:date];
631 /* Runs the loop until limitDate or until the earliest limit date for input
632 sources in the specified mode. */
633 - (void)acceptInputForMode:(NSString *)aMode beforeDate:(NSDate *)limitDate
635 id inputManager, fileObjects;
636 struct timeval tp = { 0, 0 };
637 struct timeval* timeout = NULL;
638 NSTimeInterval delay = 0;
639 fd_set readSet, writeSet, exceptionsSet;
640 volatile int i, r, count;
642 ASSIGN(self->mode, aMode);
644 if(limitDate == nil) { // delay = 0
645 limitDate = [NSDate distantFuture];
648 delay = [limitDate timeIntervalSinceNow];
649 /* delay > 0 means a future date */
651 /* If limitDate is in the past return */
656 inputManager = [inputsForMode objectForKey:aMode];
657 fileObjects = [inputManager fileObjects];
659 /* Compute the timeout for select */
660 if([limitDate isEqual:[NSDate distantFuture]])
664 tp.tv_usec = (delay - (NSTimeInterval)tp.tv_sec) * 1000000.0;
668 [NSNotificationQueue runLoopASAP];
670 ASSIGN(self->mode, aMode);
674 FD_ZERO(&exceptionsSet);
677 count = [fileObjects count];
678 for (i = 0; i < count; i++) {
679 NSRunLoopFileObjectInfo *info = [fileObjects objectAtIndex:i];
680 NSPosixFileActivities fileActivity;
686 fileActivity = [info watchedActivities];
687 fd = [info fileDescriptor];
690 #if !defined(__MINGW32__) /* on Windows descriptors can be BIG */
691 if (fd >= FD_SETSIZE) {
692 NSLog(@"%s: fd %i of %@ exceeds select size %i",
693 "NSRunLoop(-acceptInputForMode:beforeDate:)",
694 fd, info, FD_SETSIZE);
699 //NSLog(@"registering activity %s for fd %i ..",
700 // activityDesc[fileActivity], fd);
702 if (fileActivity & NSPosixReadableActivity)
703 FD_SET(fd, &readSet);
704 if (fileActivity & NSPosixWritableActivity)
705 FD_SET(fd, &writeSet);
706 if (fileActivity & NSPosixExceptionalActivity)
707 FD_SET(fd, &exceptionsSet);
711 /* ???: errno = 0; What is this good for ? */
712 r = select(FD_SETSIZE, &readSet, &writeSet, &exceptionsSet, timeout);
714 if (errno == EINTR) {
715 /* Interrupt occured; break the loop to give a chance to
716 UnixSignalHandler to handle the signals. */
717 //printf("%s: GOT EINTR ...\n", __PRETTY_FUNCTION__);
722 NSLog(@"%s: select() error: '%s'",
723 "NSRunLoop(-acceptInputForMode:beforeDate:)",
734 *(&fileObjectsCopy) = nil;
737 // made copy, so that modifications in the delegate don't
739 fileObjectsCopy = [fileObjects copyWithZone:[self zone]];
740 count = [fileObjectsCopy count];
742 for (i = 0; (i < count) && (r > 0); i++) {
743 NSRunLoopFileObjectInfo *info;
744 NSPosixFileActivities activity = 0;
747 info = [fileObjectsCopy objectAtIndex:i];
748 fd = [info fileDescriptor];
751 //NSLog(@"checking activity for %i info %@ ..", fd, info);
753 if (FD_ISSET(fd, &readSet)) {
754 activity |= NSPosixReadableActivity;
757 if (FD_ISSET(fd, &writeSet)) {
758 activity |= NSPosixWritableActivity;
761 if (FD_ISSET(fd, &exceptionsSet)) {
762 activity |= NSPosixExceptionalActivity;
767 [info activity:activity onDescriptor:fd];
772 NSLog(@"WARNING: did not resolve all activities (%i) ..", r);
777 RELEASE(fileObjectsCopy); fileObjectsCopy = nil;
780 [inputManager performAdditionalOperations];
781 [NSNotificationQueue runLoopASAP];
782 [inputManager performAdditionalOperations];
785 - (void)runUntilDate:(NSDate *)limitDate
787 BOOL shouldContinue = YES;
789 if (limitDate == nil)
790 limitDate = [NSDate distantFuture];
792 /* If limitDate is in the past return */
793 if ([limitDate timeIntervalSinceNow] < 0)
797 while (shouldContinue) {
798 CREATE_AUTORELEASE_POOL(pool);
800 if ([limitDate laterDate:[NSDate date]] == limitDate) {
801 if ([self runMode:NSDefaultRunLoopMode beforeDate:limitDate] == NO)
812 [self runUntilDate:[NSDate distantFuture]];
815 - (void)performSelector:(SEL)aSelector
817 argument:(id)anArgument
818 order:(unsigned)order
819 modes:(NSArray*)modes
821 id holder = [NSRunLoopActionHolder objectWithTarget:target
825 int i, count = [modes count];
827 for (i = 0; i < count; i++)
828 [[inputsForMode objectForKey:[modes objectAtIndex:i]]
829 addOperation:holder];
832 - (void)cancelPerformSelector:(SEL)aSelector
834 argument:(id)anArgument
836 id holder = [NSRunLoopActionHolder objectWithTarget:target
840 id enumerator = [inputsForMode keyEnumerator];
843 while ((aMode = [enumerator nextObject]))
844 [[inputsForMode objectForKey:aMode] removeOperation:holder];
847 /* Monitoring file descriptors */
849 - (void)addPosixFileDescriptor:(NSPosixFileDescriptor *)fileDescriptor
850 forMode:(NSString *)aMode
852 [_getInputManager(self, aMode) addPosixFileDescriptor:fileDescriptor];
855 - (void)removePosixFileDescriptor:(NSPosixFileDescriptor *)fileDescriptor
856 forMode:(NSString *)aMode
858 [_getInputManager(self, aMode) removePosixFileDescriptor:fileDescriptor];
861 /* Monitoring file objects */
863 - (void)addFileObject:(id)_fileObject
864 activities:(unsigned int)_activities /* NSPosixFileActivities */
865 forMode:(NSString *)_mode
867 [_getInputManager(self, _mode) addFileObject:_fileObject
868 activities:_activities];
871 - (void)removeFileObject:(id)_fileObject
872 forMode:(NSString *)_mode
874 [_getInputManager(self, _mode) removeFileObject:_fileObject];
879 - (void)addPort:(NSPort *)_port forMode:(NSString *)_mode
881 [self notImplemented:_cmd];
884 - (void)removePort:(NSPort *)_port forMode:(NSString *)_mode
886 [self notImplemented:_cmd];
889 /* Server operation */
891 - (void)configureAsServer
893 /* What is special about a server ?, maybe register as service in Win32 */