timer.c
1 /* 2 * Copyright (c) 2009 Kungliga Tekniska H�gskolan 3 * (Royal Institute of Technology, Stockholm, Sweden). 4 * All rights reserved. 5 * 6 * Portions Copyright (c) 2009 Apple Inc. All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * 3. Neither the name of the Institute nor the names of its contributors 20 * may be used to endorse or promote products derived from this software 21 * without specific prior written permission. 22 * 23 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 * SUCH DAMAGE. 34 */ 35 36 #include "hi_locl.h" 37 #include <dispatch/dispatch.h> 38 #include <CoreFoundation/CoreFoundation.h> 39 40 #include "heap.h" 41 42 struct heim_event_data { 43 heap_ptr hptr; 44 dispatch_semaphore_t running; 45 int flags; 46 #define RUNNING 1 47 #define IN_FREE 2 48 #define CF_OBJECT 4 49 heim_ipc_event_callback_t callback; 50 heim_ipc_event_final_t final; 51 void *ctx; 52 time_t t; 53 }; 54 55 /** 56 * Event handling framework 57 * 58 * Event lifesyncle 59 * 60 * create ---> set_time ------> do_event --------> delete_event 61 * | | | 62 * \--------\-------> cancel_event -->--/ 63 * 64 */ 65 66 static dispatch_queue_t timer_sync_q; 67 static dispatch_queue_t timer_job_q; 68 static Heap *timer_heap; 69 static dispatch_source_t timer_source; 70 71 /* 72 * Compare to event for heap sorting 73 */ 74 75 static int 76 event_cmp_fn(const void *aptr, const void *bptr) 77 { 78 const struct heim_event_data *a = aptr; 79 const struct heim_event_data *b = bptr; 80 return (int)(a->t - b->t); 81 } 82 83 /* 84 * Calculate next timer event and set the timer 85 */ 86 87 static void 88 reschedule_timer(void) 89 { 90 const struct heim_event_data *e = heap_head(timer_heap); 91 92 if (e == NULL) { 93 /* 94 * if there are no more events, cancel timer by setting timer 95 * to forever, later calls will pull it down to !forever when 96 * needed again 97 */ 98 dispatch_source_set_timer(timer_source, 99 DISPATCH_TIME_FOREVER, DISPATCH_TIME_FOREVER, 0); 100 } else { 101 struct timespec ts; 102 ts.tv_sec = e->t; 103 ts.tv_nsec = 0; 104 dispatch_source_set_timer(timer_source, 105 dispatch_walltime(&ts, 0), 106 DISPATCH_TIME_FOREVER, 107 10ull * NSEC_PER_SEC); 108 } 109 } 110 111 /* 112 * Get jobs that have triggered and run them in the background. 113 */ 114 115 static void 116 trigger_jobs(void) 117 { 118 time_t now = time(NULL); 119 120 while (1) { 121 struct heim_event_data *e = rk_UNCONST(heap_head(timer_heap)); 122 123 if (e != NULL && e->t < now) { 124 heap_remove_head(timer_heap); 125 e->hptr = HEAP_INVALID_PTR; 126 127 /* if its already running, lets retry 10s from now */ 128 if (e->flags & RUNNING) { 129 e->t = now + 10; 130 heap_insert(timer_heap, e, &e->hptr); 131 continue; 132 } 133 e->flags |= RUNNING; 134 135 _heim_ipc_suspend_timer(); 136 137 dispatch_async(timer_job_q, ^{ 138 e->callback(e, e->ctx); 139 dispatch_async(timer_sync_q, ^{ 140 e->flags &= ~RUNNING; 141 if (e->running) 142 dispatch_semaphore_signal(e->running); 143 144 _heim_ipc_restart_timer(); 145 }); 146 }); 147 } else 148 break; 149 } 150 reschedule_timer(); 151 } 152 153 /* 154 * Create sync syncronization queue, heap and timer 155 */ 156 157 static void 158 timer_init(void) 159 { 160 static dispatch_once_t once; 161 162 dispatch_once(&once, ^{ 163 164 timer_sync_q = dispatch_queue_create("hiem-timer-q", NULL); 165 timer_job_q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 166 167 168 timer_heap = heap_new(11, event_cmp_fn); 169 170 timer_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 171 0, 0, timer_sync_q); 172 dispatch_source_set_event_handler(timer_source, ^{ trigger_jobs(); }); 173 dispatch_resume(timer_source); 174 }); 175 } 176 177 /** 178 * Create a event that is (re)schedule and set the callback functions 179 * and context variable. 180 * 181 * The callback function can call heim_ipc_event_cancel() and 182 * heim_ipc_event_free(). 183 * 184 * @param cb callback function when the event is triggered 185 * @param ctx context passed to the callback function 186 * 187 * @return a heim ipc event 188 */ 189 190 heim_event_t 191 heim_ipc_event_create_f(heim_ipc_event_callback_t cb, void *ctx) 192 { 193 heim_event_t e; 194 195 timer_init(); 196 197 e = malloc(sizeof(*e)); 198 if (e == NULL) 199 return NULL; 200 201 e->hptr = HEAP_INVALID_PTR; 202 e->running = NULL; 203 e->flags = 0; 204 e->callback = cb; 205 e->ctx = ctx; 206 e->t = 0; 207 208 return e; 209 } 210 211 /** 212 * Create a event that is (re)schedule and set the callback functions 213 * and context variable. 214 * 215 * The callback function can call heim_ipc_event_cancel() and 216 * heim_ipc_event_free(). 217 * 218 * @param cb callback function when the event is triggered 219 * @param ctx CFTypeRef context passed to the callback function, retained until freed. 220 * 221 * @return a heim ipc event 222 */ 223 224 heim_event_t 225 heim_ipc_event_cf_create_f(heim_ipc_event_callback_t cb, CFTypeRef ctx) 226 { 227 CFRetain(ctx); 228 heim_event_t e = heim_ipc_event_create_f(cb, (void*)ctx); 229 e->flags |= CF_OBJECT; 230 231 return e; 232 } 233 234 /** 235 * (Re)schedule a new timeout for an event 236 * 237 * @param e event to schedule new timeout 238 * @param t absolute time the event will trigger 239 * 240 * @return 0 on success 241 */ 242 243 int 244 heim_ipc_event_set_time(heim_event_t e, time_t t) 245 { 246 dispatch_sync(timer_sync_q, ^{ 247 time_t next; 248 if (e->flags & IN_FREE) 249 abort(); 250 if (e->hptr != HEAP_INVALID_PTR) 251 heap_remove(timer_heap, e->hptr); 252 253 next = time(NULL); 254 255 /* don't allow setting events in the past */ 256 if (t > next) 257 next = t; 258 e->t = next; 259 260 heap_insert(timer_heap, e, &e->hptr); 261 reschedule_timer(); 262 }); 263 return 0; 264 } 265 266 /** 267 * Cancel an event. 268 * 269 * Cancel will block if the callback for the job is running. 270 * 271 * @param e event to schedule new timeout 272 */ 273 274 void 275 heim_ipc_event_cancel(heim_event_t e) 276 { 277 dispatch_sync(timer_sync_q, ^{ 278 if (e->hptr != HEAP_INVALID_PTR) { 279 heap_remove(timer_heap, e->hptr); 280 e->hptr = HEAP_INVALID_PTR; 281 } 282 e->t = 0; 283 reschedule_timer(); 284 }); 285 } 286 287 bool 288 heim_ipc_event_is_cancelled(heim_event_t e) 289 { 290 __block bool result = false; 291 dispatch_sync(timer_sync_q, ^{ 292 result = e->t == 0; 293 }); 294 return result; 295 } 296 297 /** 298 * Free an event, most be either canceled or triggered. Can't delete 299 * an event that is not canceled. 300 * 301 * @param e event to free 302 */ 303 304 void 305 heim_ipc_event_free(heim_event_t e) 306 { 307 dispatch_async(timer_sync_q, ^{ 308 e->flags |= IN_FREE; 309 if ((e->hptr != HEAP_INVALID_PTR)) 310 abort(); 311 if (e->final || (e->flags & RUNNING)) { 312 int wait_running = (e->flags & RUNNING); 313 314 if (wait_running) 315 e->running = dispatch_semaphore_create(0); 316 317 dispatch_async(timer_job_q, ^{ 318 if (wait_running) { 319 dispatch_semaphore_wait(e->running, 320 DISPATCH_TIME_FOREVER); 321 dispatch_release(e->running); 322 } 323 if (e->final) 324 e->final(e->ctx); 325 if (e->flags & CF_OBJECT) { 326 CFRelease(e->ctx); 327 } 328 free(e); 329 }); 330 } else { 331 if (e->flags & CF_OBJECT) { 332 CFRelease(e->ctx); 333 } 334 free(e); 335 } 336 }); 337 } 338 339 /** 340 * Finalizer called when event 'e' is freed. 341 * 342 * @param e event to set finalizer for 343 * @param f finalizer to be called 344 */ 345 346 void 347 heim_ipc_event_set_final_f(heim_event_t e, heim_ipc_event_final_t f) 348 { 349 e->final = f; 350 }