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