X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=OSX%2Fjwxyz-timers.m;h=3956569c0afa15c339e14e5ca88ec3e69acc420f;hb=88cfe534a698a0562e81345957a50714af1453bc;hp=0ad4630fb6a9eeb12d3409b829b3002f9c4a8d8b;hpb=c494fd2e6b3b25582375d62e40f4f5cc984ca424;p=xscreensaver diff --git a/OSX/jwxyz-timers.m b/OSX/jwxyz-timers.m index 0ad4630f..3956569c 100644 --- a/OSX/jwxyz-timers.m +++ b/OSX/jwxyz-timers.m @@ -1,4 +1,4 @@ -/* xscreensaver, Copyright (c) 2006 Jamie Zawinski +/* xscreensaver, Copyright (c) 2006-2014 Jamie Zawinski * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that @@ -12,36 +12,38 @@ /* This is the OSX implementation of Xt timers, for libjwxyz. */ +//#define DEBUG_TIMERS +//#define DEBUG_SOURCES + #import -#import + #import "jwxyz.h" #import "jwxyz-timers.h" -//#define DEBUG_TIMERS -//#define DEBUG_SOURCES - -/* If this is defined, we implement timers in terms of CFRunLoopTimerCreate. - But I couldn't get that to work right: when the left window was accepting - input, the right window would starve. So I implemented them in terms of - select() instead. This is more efficient anyway, since instead of the - event loop telling us that input is ready constantly, we only check once - just before the draw method is called. - */ -#undef USE_COCOA_SOURCES #ifdef DEBUG_TIMERS -# define LOGT(str,arg1,arg2) NSLog(str,arg1,arg2) +# define LOGT( str,arg1) NSLog(str,arg1) +# define LOGT2(str,arg1,arg2) NSLog(str,arg1,arg2) #else -# define LOGT(str,arg1,arg2) +# define LOGT( str,arg1) +# define LOGT2(str,arg1,arg2) #endif #ifdef DEBUG_SOURCES -# define LOGI(str,arg1,arg2,arg3) NSLog(str,arg1,arg2,arg3) +# define LOGI( str,arg1,arg2) NSLog(str,arg1,arg2) +# define LOGI2(str,arg1,arg2,arg3) NSLog(str,arg1,arg2,arg3) #else -# define LOGI(str,arg1,arg2,arg3) +# define LOGI( str,arg1,arg2) +# define LOGI2(str,arg1,arg2,arg3) #endif +#define ASSERT_RET(C,S) do { \ + if (!(C)) { \ + jwxyz_abort ("jwxyz-timers: %s",(S)); \ + return; \ + }} while(0) + XtAppContext XtDisplayToApplicationContext (Display *dpy) @@ -52,21 +54,25 @@ XtDisplayToApplicationContext (Display *dpy) #define app_to_display(APP) ((Display *) (APP)) +struct jwxyz_sources_data { + int fd_count; + XtInputId ids[FD_SETSIZE]; + struct jwxyz_XtIntervalId *all_timers; +}; + struct jwxyz_XtIntervalId { + XtAppContext app; CFRunLoopTimerRef cftimer; int refcount; XtTimerCallbackProc cb; XtPointer closure; + + struct jwxyz_XtIntervalId *next; }; struct jwxyz_XtInputId { -# ifdef USE_COCOA_SOURCES - CFRunLoopSourceRef cfsource; - CFSocketRef socket; -# else XtAppContext app; -# endif int refcount; XtInputCallbackProc cb; @@ -80,18 +86,46 @@ jwxyz_timer_retain (const void *arg) { struct jwxyz_XtIntervalId *data = (struct jwxyz_XtIntervalId *) arg; data->refcount++; - LOGT(@"timer 0x%08X: retain %d", (unsigned int) data, data->refcount); + LOGT2(@"timer 0x%08X: retain %d", (unsigned int) data, data->refcount); return arg; } +/* This is called both by the user to manually kill a timer (XtRemoveTimeOut) + and by the run loop after a timer has fired (CFRunLoopTimerInvalidate). + */ static void jwxyz_timer_release (const void *arg) { struct jwxyz_XtIntervalId *data = (struct jwxyz_XtIntervalId *) arg; + jwxyz_sources_data *td = display_sources_data (app_to_display (data->app)); + data->refcount--; - LOGT(@"timer 0x%08X: release %d", (unsigned int) data, data->refcount); - if (data->refcount < 0) abort(); - if (data->refcount == 0) free (data); + LOGT2(@"timer 0x%08X: release %d", (unsigned int) data, data->refcount); + ASSERT_RET (data->refcount >= 0, "double free"); + + if (data->refcount == 0) { + + // Remove it from the list of live timers. + XtIntervalId prev, timer; + int hit = 0; + for (timer = td->all_timers, prev = 0; + timer; + prev = timer, timer = timer->next) { + if (timer == data) { + ASSERT_RET (!hit, "circular timer list"); + if (prev) + prev->next = timer->next; + else + td->all_timers = timer->next; + timer->next = 0; + hit = 1; + } else { + ASSERT_RET (timer->refcount > 0, "timer list corrupted"); + } + } + + free (data); + } } static const void * @@ -99,8 +133,8 @@ jwxyz_source_retain (const void *arg) { struct jwxyz_XtInputId *data = (struct jwxyz_XtInputId *) arg; data->refcount++; - LOGI(@"source 0x%08X %2d: retain %d", (unsigned int) data, data->fd, - data->refcount); + LOGI2(@"source 0x%08X %2d: retain %d", (unsigned int) data, data->fd, + data->refcount); return arg; } @@ -109,16 +143,10 @@ jwxyz_source_release (const void *arg) { struct jwxyz_XtInputId *data = (struct jwxyz_XtInputId *) arg; data->refcount--; - LOGI(@"source 0x%08X %2d: release %d", (unsigned int) data, data->fd, - data->refcount); - if (data->refcount < 0) abort(); + LOGI2(@"source 0x%08X %2d: release %d", (unsigned int) data, data->fd, + data->refcount); + ASSERT_RET (data->refcount >= 0, "double free"); if (data->refcount == 0) { -# ifdef USE_COCOA_SOURCES - if (data->socket) - CFRelease (data->socket); - if (data->cfsource) - CFRelease (data->cfsource); -# endif /* USE_COCOA_SOURCES */ memset (data, 0xA1, sizeof(*data)); data->fd = -666; free (data); @@ -130,7 +158,7 @@ static void jwxyz_timer_cb (CFRunLoopTimerRef timer, void *arg) { struct jwxyz_XtIntervalId *data = (struct jwxyz_XtIntervalId *) arg; - LOGT(@"timer 0x%08X: fire", (unsigned int) data, 0); + LOGT(@"timer 0x%08X: fire", (unsigned int) data); data->cb (data->closure, &data); // Our caller (__CFRunLoopDoTimer) will now call CFRunLoopTimerInvalidate, @@ -138,56 +166,18 @@ jwxyz_timer_cb (CFRunLoopTimerRef timer, void *arg) } -#ifdef USE_COCOA_SOURCES - -/* whether there is data available to be read on the file descriptor - */ -static int -input_available_p (int fd) -{ - struct timeval tv = { 0, }; - fd_set fds; - FD_ZERO (&fds); - FD_SET (fd, &fds); - return select (fd+1, &fds, NULL, NULL, &tv); -} - - -static void -jwxyz_source_cb (CFSocketRef s, CFSocketCallBackType type, - CFDataRef address, const void *call_data, void *info) -{ - struct jwxyz_XtInputId *data = (struct jwxyz_XtInputId *) info; - - if (type != kCFSocketReadCallBack) abort(); - if (call_data != 0) abort(); // not used for kCFSocketRead - - // We are sometimes called when there is not, in fact, data available! - // So don't call data->cb if we're being fed a pack of lies. - // - if (! input_available_p (data->fd)) { - LOGI(@"source 0x%08X %2d: false alarm!", (unsigned int) data, data->fd, 0); - return; - } - - LOGI(@"source 0x%08X %2d: fire", (unsigned int) data, data->fd, 0); - - data->cb (data->closure, &data->fd, &data); -} - -#endif /* USE_COCOA_SOURCES */ - - XtIntervalId XtAppAddTimeOut (XtAppContext app, unsigned long msecs, XtTimerCallbackProc cb, XtPointer closure) { + jwxyz_sources_data *td = display_sources_data (app_to_display (app)); struct jwxyz_XtIntervalId *data = (struct jwxyz_XtIntervalId *) calloc (1, sizeof(*data)); + data->app = app; data->cb = cb; data->closure = closure; - LOGT(@"timer 0x%08X: alloc %d", (unsigned int) data, msecs); + LOGT2(@"timer 0x%08X: alloc %lu", (unsigned int) data, msecs); CFRunLoopTimerContext ctx = { 0, }; ctx.info = data; @@ -202,6 +192,9 @@ XtAppAddTimeOut (XtAppContext app, unsigned long msecs, jwxyz_timer_cb, &ctx); // CFRunLoopTimerCreate called jwxyz_timer_retain. + data->next = td->all_timers; + td->all_timers = data; + CFRunLoopAddTimer (CFRunLoopGetCurrent(), data->cftimer, kCFRunLoopCommonModes); return data; @@ -211,9 +204,9 @@ XtAppAddTimeOut (XtAppContext app, unsigned long msecs, void XtRemoveTimeOut (XtIntervalId id) { - LOGT(@"timer 0x%08X: remove", (unsigned int) id, 0); - if (id->refcount <= 0) abort(); - if (!id->cftimer) abort(); + LOGT(@"timer 0x%08X: remove", (unsigned int) id); + ASSERT_RET (id->refcount > 0, "already freed"); + ASSERT_RET (id->cftimer, "timers corrupted"); CFRunLoopRemoveTimer (CFRunLoopGetCurrent(), id->cftimer, kCFRunLoopCommonModes); @@ -222,13 +215,6 @@ XtRemoveTimeOut (XtIntervalId id) } -#ifndef USE_COCOA_SOURCES - -struct jwxyz_sources_data { - int count; - XtInputId ids[FD_SETSIZE]; -}; - jwxyz_sources_data * jwxyz_sources_init (XtAppContext app) { @@ -236,38 +222,31 @@ jwxyz_sources_init (XtAppContext app) return td; } -void -jwxyz_sources_free (jwxyz_sources_data *td) -{ - free (td); -} - - static void jwxyz_source_select (XtInputId id) { jwxyz_sources_data *td = display_sources_data (app_to_display (id->app)); - if (id->fd <= 0 || id->fd >= FD_SETSIZE) abort(); - if (td->ids[id->fd]) abort(); + ASSERT_RET (id->fd > 0 && id->fd < FD_SETSIZE, "fd out of range"); + ASSERT_RET (td->ids[id->fd] == 0, "sources corrupted"); td->ids[id->fd] = id; - td->count++; + td->fd_count++; } static void jwxyz_source_deselect (XtInputId id) { jwxyz_sources_data *td = display_sources_data (app_to_display (id->app)); - if (td->count <= 0) abort(); - if (id->fd <= 0 || id->fd >= FD_SETSIZE) abort(); - if (td->ids[id->fd] != id) abort(); + ASSERT_RET (td->fd_count > 0, "sources corrupted"); + ASSERT_RET (id->fd > 0 && id->fd < FD_SETSIZE, "fd out of range"); + ASSERT_RET (td->ids[id->fd] == id, "sources corrupted"); td->ids[id->fd] = 0; - td->count--; + td->fd_count--; } void jwxyz_sources_run (jwxyz_sources_data *td) { - if (td->count == 0) return; + if (td->fd_count == 0) return; struct timeval tv = { 0, }; fd_set fds; @@ -282,22 +261,20 @@ jwxyz_sources_run (jwxyz_sources_data *td) } } - if (!max) abort(); + ASSERT_RET (max > 0, "no fds"); if (0 < select (max+1, &fds, NULL, NULL, &tv)) { for (i = 0; i < FD_SETSIZE; i++) { if (FD_ISSET (i, &fds)) { XtInputId id = td->ids[i]; - if (!id || !id->cb) abort(); - if (id->fd != i) abort(); + ASSERT_RET (id && id->cb, "sources corrupted"); + ASSERT_RET (id->fd == i, "sources corrupted"); id->cb (id->closure, &id->fd, &id); } } } } -#endif /* !USE_COCOA_SOURCES */ - XtInputId XtAppAddInput (XtAppContext app, int fd, XtPointer flags, @@ -309,75 +286,76 @@ XtAppAddInput (XtAppContext app, int fd, XtPointer flags, data->fd = fd; data->closure = closure; - LOGI(@"source 0x%08X %2d: alloc", (unsigned int) data, data->fd, 0); - -# ifdef USE_COCOA_SOURCES - - CFSocketContext ctx = { 0, }; - ctx.info = data; - ctx.retain = jwxyz_source_retain; - ctx.release = jwxyz_source_release; - - data->socket = CFSocketCreateWithNative (NULL, fd, kCFSocketReadCallBack, - jwxyz_source_cb, &ctx); - // CFSocketCreateWithNative called jwxyz_source_retain. - - CFSocketSetSocketFlags (data->socket, - kCFSocketAutomaticallyReenableReadCallBack - ); - // not kCFSocketCloseOnInvalidate. - - data->cfsource = CFSocketCreateRunLoopSource (NULL, data->socket, 0); - - CFRunLoopAddSource (CFRunLoopGetCurrent(), data->cfsource, - kCFRunLoopCommonModes); - -# else /* !USE_COCOA_SOURCES */ + LOGI(@"source 0x%08X %2d: alloc", (unsigned int) data, data->fd); data->app = app; jwxyz_source_retain (data); jwxyz_source_select (data); -# endif /* !USE_COCOA_SOURCES */ - return data; } void XtRemoveInput (XtInputId id) { - LOGI(@"source 0x%08X %2d: remove", (unsigned int) id, id->fd, 0); - if (id->refcount <= 0) abort(); -# ifdef USE_COCOA_SOURCES - if (! id->cfsource) abort(); - if (! id->socket) abort(); - - CFRunLoopRemoveSource (CFRunLoopGetCurrent(), id->cfsource, - kCFRunLoopCommonModes); - CFSocketInvalidate (id->socket); - // CFSocketInvalidate called jwxyz_source_release. - -# else /* !USE_COCOA_SOURCES */ + LOGI(@"source 0x%08X %2d: remove", (unsigned int) id, id->fd); + ASSERT_RET (id->refcount > 0, "sources corrupted"); jwxyz_source_deselect (id); jwxyz_source_release (id); - -# endif /* !USE_COCOA_SOURCES */ } -void -jwxyz_XtRemoveInput_all (Display *dpy) +static void +jwxyz_XtRemoveInput_all (jwxyz_sources_data *td) { -# ifdef USE_COCOA_SOURCES - abort(); -# else /* !USE_COCOA_SOURCES */ - - jwxyz_sources_data *td = display_sources_data (dpy); int i; for (i = 0; i < FD_SETSIZE; i++) { XtInputId id = td->ids[i]; if (id) XtRemoveInput (id); } +} + + +static void +jwxyz_XtRemoveTimeOut_all (jwxyz_sources_data *td) +{ + struct jwxyz_XtIntervalId *timer, *next; + int count = 0; + + // Iterate the timer list, being careful that XtRemoveTimeOut removes + // things from that list. + if (td->all_timers) { + for (timer = td->all_timers, next = timer->next; + timer; + timer = next, next = (timer ? timer->next : 0)) { + XtRemoveTimeOut (timer); + count++; + ASSERT_RET (count < 10000, "way too many timers to free"); + } + ASSERT_RET (!td->all_timers, "timer list didn't empty"); + } +} + -# endif /* !USE_COCOA_SOURCES */ +void +jwxyz_sources_free (jwxyz_sources_data *td) +{ + jwxyz_XtRemoveInput_all (td); + jwxyz_XtRemoveTimeOut_all (td); + memset (td, 0xA1, sizeof(*td)); + free (td); +} + + +XtInputMask +XtAppPending (XtAppContext app) +{ + return XtIMAlternateInput; /* just always say yes */ +} + +void +XtAppProcessEvent (XtAppContext app, XtInputMask mask) +{ + jwxyz_sources_data *td = display_sources_data (app_to_display (app)); + jwxyz_sources_run (td); }