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); self->fileObject = nil;
141 - (BOOL)isEqual:(NSRunLoopFileObjectInfo*)otherInfo
143 return [self->fileObject isEqual:otherInfo->fileObject];
147 return (self->canCheckAlive) ? [self->fileObject isAlive] : YES;
149 - (int)fileDescriptor
151 return [self->fileObject fileDescriptor];
154 - (NSPosixFileActivities)watchedActivities
156 return self->watchedActivities;
159 - (void)activity:(NSPosixFileActivities)_activity onDescriptor:(int)_fd
162 NSLog(@"%s:%i: FileObject %@ became active ..", __PRETTY_FUNCTION__,__LINE__,
166 if ([self->fileObject isKindOfClass:[NSPosixFileDescriptor class]]) {
167 if (_activity & NSPosixReadableActivity) {
168 [[self->fileObject delegate]
169 activity:NSPosixReadableActivity
170 posixFileDescriptor:self->fileObject];
172 if (_activity & NSPosixWritableActivity) {
173 [[self->fileObject delegate]
174 activity:NSPosixWritableActivity
175 posixFileDescriptor:self->fileObject];
177 if (_activity & NSPosixExceptionalActivity) {
178 [[self->fileObject delegate]
179 activity:NSPosixExceptionalActivity
180 posixFileDescriptor:self->fileObject];
184 [[NSNotificationCenter defaultCenter]
185 postNotificationName:
186 NSFileObjectBecameActiveNotificationName
187 object:self->fileObject];
191 - (NSString *)description
193 return [NSString stringWithFormat:
194 @"<%@[0x%08X]: object=%@ actitivity=%s>",
195 NSStringFromClass([self class]), self,
197 activityDesc[self->watchedActivities]
203 @interface NSRunLoopTimerInfo : NSObject
209 + (NSRunLoopTimerInfo*)infoWithTimer:(NSTimer*)timer;
210 - (void)recomputeFireDate;
211 - (NSComparisonResult)compare:(NSRunLoopTimerInfo*)anObject;
216 @implementation NSRunLoopTimerInfo
218 + (NSRunLoopTimerInfo *)infoWithTimer:(NSTimer*)aTimer
220 NSRunLoopTimerInfo *info = [self new];
222 info->timer = RETAIN(aTimer);
223 info->fireDate = RETAIN([aTimer fireDate]);
224 return AUTORELEASE(info);
229 RELEASE(self->timer);
230 RELEASE(self->fireDate);
234 - (void)recomputeFireDate
236 if ([self->timer isValid]) {
237 id tmp = [self->timer fireDate];
238 ASSIGN(self->fireDate, tmp);
242 - (NSComparisonResult)compare:(NSRunLoopTimerInfo*)anObject
244 return [self->fireDate compare:anObject->fireDate];
247 - (NSTimer *)timer { return self->timer; }
248 - (NSDate *)fireDate { return self->fireDate; }
252 @interface NSRunLoopActionHolder : NSObject
259 + objectWithTarget:(id)target
260 argument:(id)argument
263 - (BOOL)isEqual:(id)anotherHolder;
267 @implementation NSRunLoopActionHolder
269 + (id)objectWithTarget:(id)_target
270 argument:(id)_argument
271 selector:(SEL)_action
274 NSRunLoopActionHolder* holder = AUTORELEASE([self alloc]);
276 holder->target = RETAIN(_target);
277 holder->argument = RETAIN(_argument);
278 holder->action = _action;
279 holder->order = _order;
286 return [(NSObject *)self->target hash];
289 - (BOOL)isEqual:(NSRunLoopActionHolder*)anotherHolder
291 return [self->target isEqual:anotherHolder->target]
292 && [argument isEqual:anotherHolder->argument]
293 && SEL_EQ(self->action, anotherHolder->action);
298 [self->target performSelector:self->action withObject:self->argument];
301 - (NSComparisonResult)compare:(NSRunLoopActionHolder*)anotherHolder
303 return (order - anotherHolder->order);
306 @end /* NSRunLoopActionHolder */
309 @interface NSRunLoopInputManager : NSObject
311 NSMutableArray *fileObjects;
312 NSMutableArray *timers;
313 NSMutableArray *otherOperations;
316 - (void)addFileObject:(id)_fileObject
317 activities:(NSPosixFileActivities)_activities;
318 - (void)removeFileObject:(id)_fileObject;
320 - (void)addPosixFileDescriptor:(NSPosixFileDescriptor*)fileDescriptor;
321 - (void)removePosixFileDescriptor:(NSPosixFileDescriptor*)fileDescriptor;
323 - (void)addTimer:(NSTimer*)aTimer;
325 - (NSMutableArray*)fileObjects;
326 - (NSMutableArray*)timers;
328 - (void)addOperation:(NSRunLoopActionHolder*)holder;
329 - (void)removeOperation:(NSRunLoopActionHolder*)holder;
330 - (void)performAdditionalOperations;
334 @implementation NSRunLoopInputManager
338 NSZone *z = [self zone];
339 self->fileObjects = [[NSMutableArray allocWithZone:z] init];
340 self->timers = [[NSMutableArray allocWithZone:z] init];
341 self->otherOperations = [[NSMutableArray allocWithZone:z] init];
347 RELEASE(self->fileObjects);
348 RELEASE(self->timers);
349 RELEASE(self->otherOperations);
353 - (void)addFileObject:(id)_fileObject
354 activities:(NSPosixFileActivities)_activities
356 NSRunLoopFileObjectInfo *info = nil;
357 //NSAssert(_activities, @"no activity to watch ?!");
358 info = [[NSRunLoopFileObjectInfo allocWithZone:[self zone]]
359 initWithFileObject:_fileObject
360 activities:_activities];
361 [self->fileObjects addObject:info];
362 //NSLog(@"file objects now: %@", self->fileObjects);
363 RELEASE(info); info = nil;
365 - (void)removeFileObject:(id)_fileObject
367 NSRunLoopFileObjectInfo *info = nil;
368 info = [[NSRunLoopFileObjectInfo allocWithZone:[self zone]]
369 initWithFileObject:_fileObject
371 [self->fileObjects removeObject:info];
372 //NSLog(@"file objects now: %@", self->fileObjects);
373 RELEASE(info); info = nil;
376 - (void)addPosixFileDescriptor:(NSPosixFileDescriptor*)fileDescriptor
378 [self addFileObject:fileDescriptor
379 activities:[fileDescriptor fileActivity]];
382 - (void)removePosixFileDescriptor:(NSPosixFileDescriptor*)fileDescriptor
384 [self removeFileObject:fileDescriptor];
387 - (void)addTimer:(NSTimer*)aTimer
389 [self->timers addObject:[NSRunLoopTimerInfo infoWithTimer:aTimer]];
392 - (void)addOperation:(NSRunLoopActionHolder*)holder
394 [self->otherOperations addObject:holder];
395 [self->otherOperations sortUsingSelector:@selector(compare:)];
398 - (void)removeOperation:(NSRunLoopActionHolder*)holder
400 [self->otherOperations removeObject:holder];
403 - (void)performAdditionalOperations
405 [self->otherOperations makeObjectsPerform:@selector(execute)];
408 - (NSMutableArray *)fileObjects
410 return self->fileObjects;
412 - (NSMutableArray *)timers
417 @end /* NSRunLoopInputManager */
420 @implementation NSRunLoop
422 extern NSRecursiveLock* libFoundationLock;
425 static NSMutableDictionary* runLoopsDictionary = nil;
426 static NSRunLoop* currentRunLoop = nil;
427 static BOOL taskIsMultithreaded = NO;
432 [[NSNotificationCenter defaultCenter]
434 selector:@selector(taskNowMultiThreaded:)
435 name:NSWillBecomeMultiThreadedNotification
440 + (NSRunLoop *)currentRunLoop
442 if (taskIsMultithreaded) {
443 NSRunLoop* currentLoop;
444 NSThread* currentThread;
446 [libFoundationLock lock];
448 currentThread = [NSThread currentThread];
449 currentLoop = [runLoopsDictionary objectForKey:currentThread];
451 currentLoop = AUTORELEASE([self new]);
452 [runLoopsDictionary setObject:currentLoop forKey:currentThread];
455 [libFoundationLock unlock];
461 currentRunLoop = [self new];
462 return currentRunLoop;
466 + (void)taskNowMultiThreaded:(NSNotification *)notification
468 taskIsMultithreaded = YES;
469 runLoopsDictionary = [NSMutableDictionary new];
471 if (currentRunLoop) {
472 NSThread* currentThread = [NSThread currentThread];
474 [runLoopsDictionary setObject:currentRunLoop forKey:currentThread];
475 RELEASE(currentRunLoop);
476 currentRunLoop = nil;
483 self->inputsForMode =
484 [[NSMutableDictionary allocWithZone:[self zone]] init];
485 self->mode = RETAIN(NSDefaultRunLoopMode);
491 RELEASE(self->inputsForMode);
496 - (NSString*)currentMode
501 static inline NSRunLoopInputManager*
502 _getInputManager(NSRunLoop *self, NSString *_mode)
504 NSRunLoopInputManager* inputManager;
506 inputManager = [self->inputsForMode objectForKey:_mode];
507 if (inputManager == nil) {
508 inputManager = [[NSRunLoopInputManager alloc] init];
509 [self->inputsForMode setObject:inputManager forKey:_mode];
510 RELEASE(inputManager);
515 static int compare_fire_dates(id timer1, id timer2, void* userData)
517 return [[timer1 fireDate] compare:[timer2 fireDate]];
520 - (NSDate*)limitDateForMode:(NSString*)aMode
523 @"%s: During NSTimer:-fire, caught exception %@ with reason %@ ";
524 NSMutableArray *timers = [[inputsForMode objectForKey:aMode] timers];
525 volatile int i, count;
526 NSMutableArray *copyOfTimers;
530 /* Remove invalid timers */
531 for(count = [timers count], i = count - 1; i >= 0; i--) {
532 if(![[[timers objectAtIndex:i] timer] isValid]) {
533 [timers removeObjectAtIndex:i];
538 /* Currently only timers have limit dates associated with them */
542 copyOfTimers = [timers mutableCopy];
544 /* Sort the timers based on their fire date */
545 [copyOfTimers sortUsingFunction:compare_fire_dates context:NULL];
547 /* Fire all the timers with their fire date expired */
548 for(i = 0; i < count; i++) {
549 NSRunLoopTimerInfo* timerInfo = [copyOfTimers objectAtIndex:i];
550 NSDate* fireDate = [timerInfo fireDate];
551 NSDate* currentDate = [NSDate date];
553 if([fireDate earlierDate:currentDate] == fireDate
554 || [fireDate isEqualToDate:currentDate]) {
555 NSTimer* timer = [timerInfo timer];
560 NSLog(format, "NSRunLoop(-limitDateForMode:)",
561 [localException name], [localException reason]);
570 RELEASE(copyOfTimers);
572 /* Recompute the fire dates for this cycle */
573 [timers makeObjectsPerform:@selector(recomputeFireDate)];
575 /* Sort the timers based on their fire date */
576 [timers sortUsingFunction:compare_fire_dates context:NULL];
578 return [timers count] ? [[timers objectAtIndex:0] fireDate] : nil;
581 - (void)addTimer:(NSTimer*)aTimer
582 forMode:(NSString*)aMode
584 [_getInputManager(self, aMode) addTimer:aTimer];
587 - (BOOL)runMode:(NSString *)aMode beforeDate:(NSDate *)limitDate
589 id inputManager, fileObjects;
593 /* Retain the limitDate so it doesn't get released by limitDateForMode:
594 if it fires a timer that has as fireDate the limitDate.
595 (bug report from Benhur Stein <Benhur-de-Oliveira.Stein@imag.fr>)
597 (void)RETAIN(limitDate);
599 inputManager = [self->inputsForMode objectForKey:aMode];
600 timers = [inputManager timers];
601 fileObjects = [inputManager fileObjects];
603 if (([timers count] != 0) || ([fileObjects count] != 0)) {
604 CREATE_AUTORELEASE_POOL(pool);
606 date = [self limitDateForMode:aMode];
607 date = date ? [date earlierDate:limitDate] : limitDate;
608 [self acceptInputForMode:aMode beforeDate:date];
618 /* Runs the loop until limitDate or until the earliest limit date for input
619 sources in the specified mode. */
620 - (void)acceptInputForMode:(NSString *)aMode beforeDate:(NSDate *)limitDate
622 id inputManager, fileObjects;
623 struct timeval tp = { 0, 0 };
624 struct timeval* timeout = NULL;
625 NSTimeInterval delay = 0;
626 fd_set readSet, writeSet, exceptionsSet;
627 volatile int i, r, count;
629 ASSIGN(self->mode, aMode);
631 if(limitDate == nil) { // delay = 0
632 limitDate = [NSDate distantFuture];
635 delay = [limitDate timeIntervalSinceNow];
636 /* delay > 0 means a future date */
638 /* If limitDate is in the past return */
643 inputManager = [inputsForMode objectForKey:aMode];
644 fileObjects = [inputManager fileObjects];
646 /* Compute the timeout for select */
647 if([limitDate isEqual:[NSDate distantFuture]])
651 tp.tv_usec = (delay - (NSTimeInterval)tp.tv_sec) * 1000000.0;
655 [NSNotificationQueue runLoopASAP];
657 ASSIGN(self->mode, aMode);
661 FD_ZERO(&exceptionsSet);
664 count = [fileObjects count];
665 for (i = 0; i < count; i++) {
666 NSRunLoopFileObjectInfo *info = [fileObjects objectAtIndex:i];
667 NSPosixFileActivities fileActivity;
673 fileActivity = [info watchedActivities];
674 fd = [info fileDescriptor];
677 #if !defined(__MINGW32__) /* on Windows descriptors can be BIG */
678 if (fd >= FD_SETSIZE) {
679 NSLog(@"%s: fd %i of %@ exceeds select size %i",
680 "NSRunLoop(-acceptInputForMode:beforeDate:)",
681 fd, info, FD_SETSIZE);
686 //NSLog(@"registering activity %s for fd %i ..",
687 // activityDesc[fileActivity], fd);
689 if (fileActivity & NSPosixReadableActivity)
690 FD_SET(fd, &readSet);
691 if (fileActivity & NSPosixWritableActivity)
692 FD_SET(fd, &writeSet);
693 if (fileActivity & NSPosixExceptionalActivity)
694 FD_SET(fd, &exceptionsSet);
698 /* ???: errno = 0; What is this good for ? */
699 r = select(FD_SETSIZE, &readSet, &writeSet, &exceptionsSet, timeout);
701 if (errno == EINTR) {
702 /* Interrupt occured; break the loop to give a chance to
703 UnixSignalHandler to handle the signals. */
704 //printf("%s: GOT EINTR ...\n", __PRETTY_FUNCTION__);
709 NSLog(@"%s: select() error: '%s'",
710 "NSRunLoop(-acceptInputForMode:beforeDate:)",
721 *(&fileObjectsCopy) = nil;
724 // made copy, so that modifications in the delegate don't
726 fileObjectsCopy = [fileObjects copyWithZone:[self zone]];
727 count = [fileObjectsCopy count];
729 for (i = 0; (i < count) && (r > 0); i++) {
730 NSRunLoopFileObjectInfo *info;
731 NSPosixFileActivities activity = 0;
734 info = [fileObjectsCopy objectAtIndex:i];
735 fd = [info fileDescriptor];
738 //NSLog(@"checking activity for %i info %@ ..", fd, info);
740 if (FD_ISSET(fd, &readSet)) {
741 activity |= NSPosixReadableActivity;
744 if (FD_ISSET(fd, &writeSet)) {
745 activity |= NSPosixWritableActivity;
748 if (FD_ISSET(fd, &exceptionsSet)) {
749 activity |= NSPosixExceptionalActivity;
754 [info activity:activity onDescriptor:fd];
759 NSLog(@"WARNING: did not resolve all activities (%i) ..", r);
764 RELEASE(fileObjectsCopy); fileObjectsCopy = nil;
767 [inputManager performAdditionalOperations];
768 [NSNotificationQueue runLoopASAP];
769 [inputManager performAdditionalOperations];
772 - (void)runUntilDate:(NSDate *)limitDate
774 BOOL shouldContinue = YES;
776 if (limitDate == nil)
777 limitDate = [NSDate distantFuture];
779 /* If limitDate is in the past return */
780 if ([limitDate timeIntervalSinceNow] < 0)
784 while (shouldContinue) {
785 CREATE_AUTORELEASE_POOL(pool);
787 if ([limitDate laterDate:[NSDate date]] == limitDate) {
788 if ([self runMode:NSDefaultRunLoopMode beforeDate:limitDate] == NO)
799 [self runUntilDate:[NSDate distantFuture]];
802 - (void)performSelector:(SEL)aSelector
804 argument:(id)anArgument
805 order:(unsigned)order
806 modes:(NSArray*)modes
808 id holder = [NSRunLoopActionHolder objectWithTarget:target
812 int i, count = [modes count];
814 for (i = 0; i < count; i++)
815 [[inputsForMode objectForKey:[modes objectAtIndex:i]]
816 addOperation:holder];
819 - (void)cancelPerformSelector:(SEL)aSelector
821 argument:(id)anArgument
823 id holder = [NSRunLoopActionHolder objectWithTarget:target
827 id enumerator = [inputsForMode keyEnumerator];
830 while ((aMode = [enumerator nextObject]))
831 [[inputsForMode objectForKey:aMode] removeOperation:holder];
834 /* Monitoring file descriptors */
836 - (void)addPosixFileDescriptor:(NSPosixFileDescriptor *)fileDescriptor
837 forMode:(NSString *)aMode
839 [_getInputManager(self, aMode) addPosixFileDescriptor:fileDescriptor];
842 - (void)removePosixFileDescriptor:(NSPosixFileDescriptor *)fileDescriptor
843 forMode:(NSString *)aMode
845 [_getInputManager(self, aMode) removePosixFileDescriptor:fileDescriptor];
848 /* Monitoring file objects */
850 - (void)addFileObject:(id)_fileObject
851 activities:(NSPosixFileActivities)_activities
852 forMode:(NSString *)_mode
854 [_getInputManager(self, _mode) addFileObject:_fileObject
855 activities:_activities];
858 - (void)removeFileObject:(id)_fileObject
859 forMode:(NSString *)_mode
861 [_getInputManager(self, _mode) removeFileObject:_fileObject];
866 - (void)addPort:(NSPort *)_port forMode:(NSString *)_mode
868 [self notImplemented:_cmd];
871 - (void)removePort:(NSPort *)_port forMode:(NSString *)_mode
873 [self notImplemented:_cmd];
876 /* Server operation */
878 - (void)configureAsServer
880 /* What is special about a server ?, maybe register as service in Win32 */