]> err.no Git - sope/blob - sope-appserver/NGObjWeb/WORunLoop.m
fixed copyrights for 2005
[sope] / sope-appserver / NGObjWeb / WORunLoop.m
1 /*
2   Copyright (C) 2000-2005 SKYRIX Software AG
3
4   This file is part of SOPE.
5
6   SOPE is free software; you can redistribute it and/or modify it under
7   the terms of the GNU Lesser General Public License as published by the
8   Free Software Foundation; either version 2, or (at your option) any
9   later version.
10
11   SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
12   WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14   License for more details.
15
16   You should have received a copy of the GNU Lesser General Public
17   License along with SOPE; see the file COPYING.  If not, write to the
18   Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19   02111-1307, USA.
20 */
21
22 #include "WORunLoop.h"
23 #include "common.h"
24
25 #if 0
26 #if !LIB_FOUNDATION_LIBRARY && !APPLE_Foundation_LIBRARY && !NeXT_Foundation_LIBRARY
27
28 #ifndef CREATE_AUTORELEASE_POOL
29 #  define CREATE_AUTORELEASE_POOL(pool) \
30             id pool = [[NSAutoreleasePool alloc] init]
31 #endif
32
33 #include <sys/types.h>
34 #include <sys/errno.h>
35 #include <errno.h>
36
37 #include <sys/time.h>   /* for struct timeval */
38 #include <string.h>
39 #include <memory.h>
40 #include <libc.h>
41 #include <unistd.h>
42 #include <sys/select.h>
43
44 #include "WORunLoop.h"
45 #import <Foundation/Foundation.h>
46
47 #if NeXT_Foundation_LIBRARY || APPLE_Foundation_LIBRARY
48 #  include <FoundationExt/objc-runtime.h>
49 #else
50 #  include <extensions/objc-runtime.h>
51 #endif
52
53 #if 0
54 #warning breaks AppKit, should *extend* NSRunLoop on MacOSX
55 @implementation NSRunLoop(Override)
56 + (NSRunLoop *)currentRunLoop {
57   return [WORunLoop currentRunLoop];
58 }
59 @end
60 #endif
61
62 #if 0
63 typedef enum {
64   NSPosixNoActivity = 0,
65   NSPosixReadableActivity = 1,
66   NSPosixWritableActivity = 2,
67   NSPosixExceptionalActivity = 4
68 } NSPosixFileActivities;
69 #endif
70
71 NSString* NSFileObjectBecameActiveNotificationName =
72   @"NSFileObjectBecameActiveNotificationName";
73
74 static char *activityDesc[8] = {
75   "---", // 0
76   "--R", // 1
77   "-W-", // 2
78   "-WR", // 3
79   "E--", // 4
80   "E-R", // 5
81   "EW-", // 6
82   "EWR"  // 7
83 };
84
85 @interface WORunLoopFileObjectInfo : NSObject
86 {
87   id                    fileObject;
88   NSPosixFileActivities watchedActivities;
89   BOOL                  canCheckAlive;
90 }
91
92 - (id)initWithFileObject:(id)_fileObject
93   activities:(NSPosixFileActivities)_activities;
94
95 - (BOOL)isAlive;
96 - (int)fileDescriptor;
97 - (NSPosixFileActivities)watchedActivities;
98
99 - (void)activity:(NSPosixFileActivities)_activity onDescriptor:(int)_fd;
100
101 @end
102
103 @implementation WORunLoopFileObjectInfo
104
105 - (id)initWithFileObject:(id)_fileObject
106   activities:(NSPosixFileActivities)_activities
107 {
108   self->fileObject        = RETAIN(_fileObject);
109   self->watchedActivities = _activities;
110   self->canCheckAlive     = [_fileObject respondsToSelector:@selector(isAlive)];
111   return self;
112 }
113 - (id)init
114 {
115   [self errorWithFormat:@"do not use init with WORunLoopFileObjectInfo .."];
116   AUTORELEASE(self);
117   return nil;
118 }
119
120 - (void)dealloc
121 {
122   RELEASE(self->fileObject); self->fileObject = nil;
123   [super dealloc];
124 }
125
126 - (BOOL)isEqual:(WORunLoopFileObjectInfo*)otherInfo
127 {
128   return [self->fileObject isEqual:otherInfo->fileObject];
129 }
130
131 - (BOOL)isAlive {
132   return (self->canCheckAlive) ? [self->fileObject isAlive] : YES;
133 }
134 - (int)fileDescriptor
135 {
136   return [self->fileObject fileDescriptor];
137 }
138
139 - (NSPosixFileActivities)watchedActivities
140 {
141   return self->watchedActivities;
142 }
143
144 - (void)activity:(NSPosixFileActivities)_activity onDescriptor:(int)_fd
145 {
146   //NSLog(@"FileObject %@ was active ..", self->fileObject);
147     
148   [[NSNotificationCenter defaultCenter]
149                          postNotificationName:
150                            NSFileObjectBecameActiveNotificationName
151                          object:self->fileObject];
152 }
153
154 - (NSString *)description
155 {
156   return [NSString stringWithFormat:
157                      @"<%@[0x%08X]: object=%@ actitivity=%s>",
158                      NSStringFromClass([self class]), self,
159                      self->fileObject,
160                      activityDesc[self->watchedActivities]
161                    ];
162 }
163
164 @end
165
166 @interface WORunLoopTimerInfo : NSObject
167 {
168     NSTimer* timer;
169     NSDate* fireDate;
170 }
171
172 + (WORunLoopTimerInfo*)infoWithTimer:(NSTimer*)timer;
173 - (void)recomputeFireDate;
174 - (NSComparisonResult)compare:(WORunLoopTimerInfo*)anObject;
175 - (NSTimer*)timer;
176 - (NSDate*)fireDate;
177 @end
178
179 @implementation WORunLoopTimerInfo
180
181 + (WORunLoopTimerInfo*)infoWithTimer:(NSTimer*)aTimer
182 {
183     WORunLoopTimerInfo* info = [self new];
184
185     info->timer    = RETAIN(aTimer);
186     info->fireDate = RETAIN([aTimer fireDate]);
187     return AUTORELEASE(info);
188 }
189
190 - (void)dealloc
191 {
192     RELEASE(timer);
193     RELEASE(fireDate);
194     [super dealloc];
195 }
196
197 - (void)recomputeFireDate
198 {
199   if ([timer isValid]) {
200     id tmp = [timer fireDate];
201     ASSIGN(fireDate, tmp);
202   }
203 }
204
205 - (NSComparisonResult)compare:(WORunLoopTimerInfo*)anObject
206 {
207     return [fireDate compare:anObject->fireDate];
208 }
209
210 - (NSTimer*)timer                       { return timer; }
211 - (NSDate*)fireDate                     { return fireDate; }
212
213 @end
214
215 @interface WORunLoopActionHolder : NSObject
216 {
217     id target;
218     id argument;
219     SEL action;
220     int order;
221 }
222 + objectWithTarget:(id)target
223   argument:(id)argument
224   selector:(SEL)action
225   order:(int)order;
226 - (BOOL)isEqual:(id)anotherHolder;
227 - (void)execute;
228 @end
229
230 @implementation WORunLoopActionHolder
231
232 + objectWithTarget:(id)_target
233   argument:(id)_argument
234   selector:(SEL)_action
235   order:(int)_order
236 {
237     WORunLoopActionHolder* holder = AUTORELEASE([self alloc]);
238
239     holder->target = RETAIN(_target);
240     holder->argument = RETAIN(_argument);
241     holder->action = _action;
242     holder->order = _order;
243
244     return holder;
245 }
246
247 - (unsigned)hash
248 {
249   return [(NSObject*)target hash];
250 }
251
252 - (BOOL)isEqual:(WORunLoopActionHolder*)anotherHolder
253 {
254     return [target isEqual:anotherHolder->target]
255             && [argument isEqual:anotherHolder->argument]
256             && SEL_EQ(action, anotherHolder->action);
257 }
258
259 - (void)execute
260 {
261     [target performSelector:action withObject:argument];
262 }
263
264 - (NSComparisonResult)compare:(WORunLoopActionHolder*)anotherHolder
265 {
266     return order - anotherHolder->order;
267 }
268
269 @end
270
271
272 @interface WORunLoopInputManager : NSObject
273 {
274     NSMutableArray* fileObjects;
275     NSMutableArray* timers;
276     NSMutableArray* otherOperations;
277 }
278
279 - (void)addFileObject:(id)_fileObject
280   activities:(NSPosixFileActivities)_activities;
281 - (void)removeFileObject:(id)_fileObject;
282
283 - (void)addTimer:(NSTimer*)aTimer;
284
285 - (NSMutableArray*)fileObjects;
286 - (NSMutableArray*)timers;
287
288 - (void)addOperation:(WORunLoopActionHolder*)holder;
289 - (void)removeOperation:(WORunLoopActionHolder*)holder;
290 - (void)performAdditionalOperations;
291 @end
292
293
294 @implementation WORunLoopInputManager
295
296 - init
297 {
298     fileObjects     = [NSMutableArray new];
299     timers          = [NSMutableArray new];
300     otherOperations = [NSMutableArray new];
301     return [super init];
302 }
303
304 - (void)dealloc
305 {
306     RELEASE(fileObjects);
307     RELEASE(timers);
308     RELEASE(otherOperations);
309     [super dealloc];
310 }
311
312 - (void)addFileObject:(id)_fileObject
313   activities:(NSPosixFileActivities)_activities
314 {
315     WORunLoopFileObjectInfo *info = nil;
316     //NSAssert(_activities, @"no activity to watch ?!");
317     info = [[WORunLoopFileObjectInfo allocWithZone:[self zone]]
318                                      initWithFileObject:_fileObject
319                                      activities:_activities];
320     [self->fileObjects addObject:info];
321     //NSLog(@"file objects now: %@", self->fileObjects);
322     RELEASE(info); info = nil;
323 }
324 - (void)removeFileObject:(id)_fileObject
325 {
326     WORunLoopFileObjectInfo *info = nil;
327     info = [[WORunLoopFileObjectInfo allocWithZone:[self zone]]
328                                      initWithFileObject:_fileObject
329                                      activities:0];
330     [self->fileObjects removeObject:info];
331     //NSLog(@"file objects now: %@", self->fileObjects);
332     RELEASE(info); info = nil;
333 }
334
335 - (void)addTimer:(NSTimer*)aTimer
336 {
337     [timers addObject:[WORunLoopTimerInfo infoWithTimer:aTimer]];
338 }
339
340 - (void)addOperation:(WORunLoopActionHolder*)holder
341 {
342     [otherOperations addObject:holder];
343     [otherOperations sortUsingSelector:@selector(compare:)];
344 }
345
346 - (void)removeOperation:(WORunLoopActionHolder*)holder
347 {
348     [otherOperations removeObject:holder];
349 }
350
351 - (void)performAdditionalOperations
352 {
353     [otherOperations makeObjectsPerformSelector:@selector(execute)];
354 }
355
356 - (NSMutableArray*)fileObjects { return fileObjects; }
357 - (NSMutableArray*)timers      { return timers; }
358
359 @end /* WORunLoopInputManager */
360
361 @implementation WORunLoop
362
363 /* Class variable */
364 static WORunLoop *currentRunLoop = nil;
365 static BOOL      taskIsMultithreaded = NO;
366
367 + (void)error:(id)_o {
368   NSLog(@"ERROR:");
369   NSLog(@"  %@", _o);
370 }
371
372 + (NSRunLoop *)currentRunLoop
373 {
374   if (taskIsMultithreaded) {
375     NSLog(@"WORunLoop does not work multithreaded, exit ..");
376     return nil;
377   }
378   else {
379     if (!currentRunLoop)
380             currentRunLoop = [[self alloc] init];
381     return currentRunLoop;
382   }
383 }
384
385 - (id)init {
386   self->inputsForMode = [[NSMutableDictionary allocWithZone:[self zone]] init];
387   self->mode = RETAIN(NSDefaultRunLoopMode);
388   return self;
389 }
390
391 - (void)dealloc {
392   RELEASE(self->inputsForMode);
393   RELEASE(self->mode);
394   [super dealloc];
395 }
396
397 - (NSString *)currentMode {
398     return self->mode;
399 }
400
401 static inline WORunLoopInputManager*
402 _getInputManager(WORunLoop *self, NSString *_mode)
403 {
404   WORunLoopInputManager* inputManager;
405
406   inputManager = [self->inputsForMode objectForKey:_mode];
407   if (inputManager == nil) {
408     inputManager = [WORunLoopInputManager new];
409     [self->inputsForMode setObject:inputManager forKey:_mode];
410     RELEASE(inputManager);
411   }
412   return inputManager;
413 }
414
415 static int compare_fire_dates(id timer1, id timer2, void* userData)
416 {
417   return [[timer1 fireDate] compare:[timer2 fireDate]];
418 }
419
420 - (NSDate*)limitDateForMode:(NSString*)aMode
421 {
422     NSString       *format = @"%s: Caught exception %@ with reason %@ ";
423     NSMutableArray *timers = [[inputsForMode objectForKey:aMode] timers];
424     volatile int   i, count;
425     NSMutableArray *copyOfTimers;
426
427     ASSIGN(mode, aMode);
428
429     /* Remove invalid timers */
430     for(count = [timers count], i = count - 1; i >= 0; i--)
431         if(![[[timers objectAtIndex:i] timer] isValid]) {
432             [timers removeObjectAtIndex:i];
433             count--;
434         }
435
436     /* Currently only timers have limit dates associated with them */
437     if(!count)
438         return nil;
439
440     copyOfTimers = [timers mutableCopy];
441
442     /* Sort the timers based on their fire date */
443     [copyOfTimers sortUsingFunction:compare_fire_dates context:NULL];
444
445     /* Fire all the timers with their fire date expired */
446     for(i = 0; i < count; i++) {
447         WORunLoopTimerInfo* timerInfo = [copyOfTimers objectAtIndex:i];
448         NSDate* fireDate = [timerInfo fireDate];
449         NSDate* currentDate = [NSDate date];
450
451         if([fireDate earlierDate:currentDate] == fireDate
452            || [fireDate isEqualToDate:currentDate]) {
453             NSTimer* timer = [timerInfo timer];
454             NS_DURING
455               [timer fire];
456             NS_HANDLER
457               NSLog(format, __PRETTY_FUNCTION__,
458                     [localException name], [localException reason]);
459             NS_ENDHANDLER;
460
461 #if 0
462 #warning no repeated timers !
463             if(![timer repeats])
464 #endif
465                 [timer invalidate];
466         }
467     }
468
469     RELEASE(copyOfTimers);
470
471     /* Recompute the fire dates for this cycle */
472     [timers makeObjectsPerformSelector:@selector(recomputeFireDate)];
473
474     /* Sort the timers based on their fire date */
475     [timers sortUsingFunction:compare_fire_dates context:NULL];
476
477     return [timers count] ? [[timers objectAtIndex:0] fireDate] : nil;
478 }
479
480 - (void)addTimer:(NSTimer*)aTimer
481         forMode:(NSString*)aMode
482 {
483     [_getInputManager(self, aMode) addTimer:aTimer];
484 }
485
486 - (BOOL)runMode:(NSString*)aMode
487         beforeDate:(NSDate*)limitDate
488 {
489     id inputManager, fileObjects;
490     NSArray* timers;
491     NSDate* date;
492
493     /* Retain the limitDate so it doesn't get released by limitDateForMode:
494         if it fires a timer that has as fireDate the limitDate.
495         (bug report from Benhur Stein <Benhur-de-Oliveira.Stein@imag.fr>)
496       */
497     (void)RETAIN(limitDate);
498
499     inputManager = [inputsForMode objectForKey:aMode];
500     timers       = [inputManager timers];
501     fileObjects  = [inputManager fileObjects];
502
503     if (([timers count] != 0) || ([fileObjects count] != 0)) {
504         CREATE_AUTORELEASE_POOL(pool);
505
506         date = [self limitDateForMode:aMode];
507         date = date ? [date earlierDate:limitDate] : limitDate;
508         [self acceptInputForMode:aMode beforeDate:date];
509         RELEASE(pool);
510         RELEASE(limitDate);
511         return YES;
512     }
513
514     RELEASE(limitDate);
515     return NO;
516 }
517
518 /*  Runs the loop until limitDate or until the earliest limit date for input
519     sources in the specified mode. */
520 - (void)acceptInputForMode:(NSString*)aMode
521         beforeDate:(NSDate*)limitDate
522 {
523     id              inputManager, fileObjects;
524     struct timeval  tp = { 0, 0 };
525     struct timeval* timeout = NULL;
526     NSTimeInterval  delay = 0;
527     fd_set          readSet, writeSet, exceptionsSet;
528     volatile int    i, r, count;
529
530     ASSIGN(mode, aMode);
531
532     if(limitDate == nil) // delay = 0
533         limitDate = [NSDate distantFuture];
534     else {
535         delay = [limitDate timeIntervalSinceNow];
536             /* delay > 0 means a future date */
537
538         /* If limitDate is in the past return */
539         if(delay < 0)
540             return;
541     }
542
543     inputManager = [inputsForMode objectForKey:aMode];
544     fileObjects  = [inputManager fileObjects];
545
546     /* Compute the timeout for select */
547     if([limitDate isEqual:[NSDate distantFuture]])
548         timeout = NULL;
549     else {
550         tp.tv_sec = delay;
551         tp.tv_usec = (delay - (NSTimeInterval)tp.tv_sec) * 1000000.0;
552         timeout = &tp;
553     }
554
555     ASSIGN(mode, aMode);
556
557     FD_ZERO(&readSet);
558     FD_ZERO(&writeSet);
559     FD_ZERO(&exceptionsSet);
560
561     do {
562         count = [fileObjects count];
563         for (i = 0; i < count; i++) {
564             WORunLoopFileObjectInfo *info;
565             NSPosixFileActivities   fileActivity;
566             int                     fd;
567
568             info = [fileObjects objectAtIndex:i];
569             if (![info isAlive])
570                 continue;
571
572             fileActivity = [info watchedActivities];
573             fd           = [info fileDescriptor];
574             
575             if (fd >= 0) {
576 #if !defined(__MINGW32__) /* on Windows descriptors can be BIG */
577                 if (fd >= FD_SETSIZE) {
578                     NSLog(@"%s: fd %i of %@ exceeds select size %i",
579                           __PRETTY_FUNCTION__,
580                           fd, info, FD_SETSIZE);
581                     continue;
582                 }
583 #endif /* !defined(__MINGW32__) */
584
585                 //NSLog(@"registering activity %s for fd %i ..",
586                 //      activityDesc[fileActivity], fd);
587
588                 if (fileActivity & NSPosixReadableActivity)
589                     FD_SET(fd, &readSet);
590                 if (fileActivity & NSPosixWritableActivity)
591                     FD_SET(fd, &writeSet);
592                 if (fileActivity & NSPosixExceptionalActivity)
593                     FD_SET(fd, &exceptionsSet);
594             }
595         }
596
597         // ???: errno = 0; What is this good for ?
598         r = select(FD_SETSIZE, &readSet, &writeSet, &exceptionsSet, timeout);
599         if (r == -1) {
600             if (errno == EINTR) {
601                 /* Interrupt occured; break the loop to give a chance to
602                    UnixSignalHandler to handle the signals. */
603                 errno = 0;
604                 break;
605             }
606             else {
607                 NSLog(@"%s: select() error: '%s'",
608                       __PRETTY_FUNCTION__, strerror (errno));
609                 break;
610             }
611             errno = 0;
612         }
613     } while (r == -1);
614
615     if(r > 0) {
616         id fileObjectsCopy;
617         NSString* format = @"%s: Caught exception %@ with reason %@ ";
618
619         *(&fileObjectsCopy) = nil;
620
621         NS_DURING {
622             // made copy, so that modifications in the delegate don't
623             // alter the loop
624             fileObjectsCopy = [fileObjects copyWithZone:[self zone]];
625             count           = [fileObjectsCopy count];
626             
627             for (i = 0; (i < count) && (r > 0); i++) {
628                 WORunLoopFileObjectInfo *info;
629                 NSPosixFileActivities   activity = 0;
630                 int fd;
631
632                 info = [fileObjectsCopy objectAtIndex:i];
633                 fd   = [info fileDescriptor];
634
635                 if (fd >= 0) {
636                     //NSLog(@"checking activity for %i info %@ ..", fd, info);
637                 
638                     if (FD_ISSET(fd, &readSet)) {
639                         activity |= NSPosixReadableActivity;
640                         r--;
641                     }
642                     if (FD_ISSET(fd, &writeSet)) {
643                         activity |= NSPosixWritableActivity;
644                         r--;
645                     }
646                     if (FD_ISSET(fd, &exceptionsSet)) {
647                         activity |= NSPosixExceptionalActivity;
648                         r--;
649                     }
650
651                     if (activity != 0)
652                         [info activity:activity onDescriptor:fd];
653                 }
654             }
655             if (r > 0) {
656               [self warnWithFormat:@"could not resolve all activities (%i) ..",
657                       r);
658             }
659         }
660         NS_HANDLER {
661           [self errorWithFormat:format, __PRETTY_FUNCTION__,
662                   [localException name], [localException reason]];
663         }
664         NS_ENDHANDLER;
665
666         RELEASE(fileObjectsCopy); fileObjectsCopy = nil;
667     }
668
669     [inputManager performAdditionalOperations];
670 #if !NeXT_Foundation_LIBRARY
671     [NSNotificationQueue runLoopASAP];
672 #endif
673 }
674
675 - (void)runUntilDate:(NSDate*)limitDate
676 {
677     BOOL shouldContinue = YES;
678
679     if(!limitDate)
680         limitDate = [NSDate distantFuture];
681     else {
682         /* If limitDate is in the past return */
683         if([limitDate timeIntervalSinceNow] < 0)
684             return;
685     }
686
687     while (shouldContinue) {
688         CREATE_AUTORELEASE_POOL(pool);
689
690         if ([limitDate laterDate:[NSDate date]] == limitDate) {
691             if([self runMode:NSDefaultRunLoopMode beforeDate:limitDate] == NO)
692                 shouldContinue = NO;
693         }
694         else
695             shouldContinue = NO;
696         RELEASE(pool);
697     }
698 }
699
700 - (void)run
701 {
702     [self runUntilDate:[NSDate distantFuture]];
703 }
704
705 - (void)performSelector:(SEL)aSelector
706   target:(id)target
707   argument:(id)anArgument
708   order:(unsigned)order
709   modes:(NSArray*)modes
710 {
711     id holder = [WORunLoopActionHolder objectWithTarget:target
712                                         argument:anArgument
713                                         selector:aSelector
714                                         order:order];
715     int i, count = [modes count];
716
717     for (i = 0; i < count; i++)
718         [[inputsForMode objectForKey:[modes objectAtIndex:i]]
719             addOperation:holder];
720 }
721
722 - (void)cancelPerformSelector:(SEL)aSelector
723   target:(id)target
724   argument:(id)anArgument
725 {
726     id holder = [WORunLoopActionHolder objectWithTarget:target
727                                         argument:anArgument
728                                         selector:aSelector
729                                         order:0];
730     id enumerator = [inputsForMode keyEnumerator];
731     id aMode;
732
733     while ((aMode = [enumerator nextObject]))
734         [[inputsForMode objectForKey:aMode] removeOperation:holder];
735 }
736
737 /* Monitoring file objects */
738
739 - (void)addFileObject:(id)_fileObject
740   activities:(NSPosixFileActivities)_activities
741   forMode:(NSString *)_mode
742 {
743     [_getInputManager(self, _mode) addFileObject:_fileObject
744                                    activities:_activities];
745 }
746
747 - (void)removeFileObject:(id)_fileObject
748   forMode:(NSString *)_mode
749 {
750     [_getInputManager(self, _mode) removeFileObject:_fileObject];
751 }
752
753 @end /* WORunLoop */
754
755 #endif /* !LIB_FOUNDATION_LIBRARY */
756 #endif // 0