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