/ lib / ipc / timer.c
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  }