4 * Timer management is similar to eventloop.js but implemented in C.
5 * In particular, timer insertion is an O(n) operation; in a real world
6 * eventloop based on a heap insertion would be O(log N).
18 #define MAX_TIMERS 4096 /* this is quite excessive for embedded use, but good for testing */
21 #define MAX_WAIT 60000.0
22 #define MAX_EXPIRYS 10
27 int64_t id
; /* numeric ID (returned from e.g. setTimeout); zero if unused */
28 double target
; /* next target time */
29 double delay
; /* delay/interval */
30 int oneshot
; /* oneshot=1 (setTimeout), repeated=0 (setInterval) */
31 int removed
; /* timer has been requested for removal */
33 /* The callback associated with the timer is held in the "global stash",
34 * in <stash>.eventTimers[String(id)]. The references must be deleted
35 * when a timer struct is deleted.
39 /* Active timers. Dense list, terminates to end of list or first unused timer.
40 * The list is sorted by 'target', with lowest 'target' (earliest expiry) last
41 * in the list. When a timer's callback is being called, the timer is moved
42 * to 'timer_expiring' as it needs special handling should the user callback
43 * delete that particular timer.
45 static ev_timer timer_list
[MAX_TIMERS
];
46 static ev_timer timer_expiring
;
47 static int timer_count
; /* last timer at timer_count - 1 */
48 static int64_t timer_next_id
= 1;
50 /* Socket poll state. */
51 static struct pollfd poll_list
[MAX_FDS
];
52 static int poll_count
= 0;
55 static int exit_requested
= 0;
57 /* Get Javascript compatible 'now' timestamp (millisecs since 1970). */
58 static double get_now(void) {
62 rc
= gettimeofday(&tv
, NULL
);
64 /* Should never happen, so return whatever. */
67 return ((double) tv
.tv_sec
) * 1000.0 + ((double) tv
.tv_usec
) / 1000.0;
70 static ev_timer
*find_nearest_timer(void) {
71 /* Last timer expires first (list is always kept sorted). */
72 if (timer_count
<= 0) {
75 return timer_list
+ timer_count
- 1;
78 /* Bubble last timer on timer list backwards until it has been moved to
79 * its proper sorted position (based on 'target' time).
81 static void bubble_last_timer(void) {
87 for (i
= n
- 1; i
> 0; i
--) {
88 /* Timer to bubble is at index i, timer to compare to is
89 * at i-1 (both guaranteed to exist).
92 if (t
->target
<= (t
-1)->target
) {
93 /* 't' expires earlier than (or same time as) 't-1', so we're done. */
96 /* 't' expires later than 't-1', so swap them and repeat. */
97 memcpy((void *) &tmp
, (void *) (t
- 1), sizeof(ev_timer
));
98 memcpy((void *) (t
- 1), (void *) t
, sizeof(ev_timer
));
99 memcpy((void *) t
, (void *) &tmp
, sizeof(ev_timer
));
104 static void expire_timers(duk_context
*ctx
) {
106 int sanity
= MAX_EXPIRYS
;
110 /* Because a user callback can mutate the timer list (by adding or deleting
111 * a timer), we expire one timer and then rescan from the end again. There
112 * is a sanity limit on how many times we do this per expiry round.
115 duk_push_global_stash(ctx
);
116 duk_get_prop_string(ctx
, -1, "eventTimers");
118 /* [ ... stash eventTimers ] */
121 while (sanity
-- > 0) {
123 * If exit has been requested, exit without running further
127 if (exit_requested
) {
129 fprintf(stderr
, "exit requested, exiting timer expiry loop\n");
136 * Expired timer(s) still exist?
139 if (timer_count
<= 0) {
142 t
= timer_list
+ timer_count
- 1;
143 if (t
->target
> now
) {
148 * Move the timer to 'expiring' for the duration of the callback.
149 * Mark a one-shot timer deleted, compute a new target for an interval.
152 memcpy((void *) &timer_expiring
, (void *) t
, sizeof(ev_timer
));
153 memset((void *) t
, 0, sizeof(ev_timer
));
160 t
->target
= now
+ t
->delay
; /* XXX: or t->target + t->delay? */
164 * Call timer callback. The callback can operate on the timer list:
165 * add new timers, remove timers. The callback can even remove the
166 * expired timer whose callback we're calling. However, because the
167 * timer being expired has been moved to 'timer_expiring', we don't
168 * need to worry about the timer's offset changing on the timer list.
172 fprintf(stderr
, "calling user callback for timer id %d\n", (int) t
->id
);
176 duk_push_number(ctx
, (double) t
->id
);
177 duk_get_prop(ctx
, -2); /* -> [ ... stash eventTimers func ] */
178 rc
= duk_pcall(ctx
, 0 /*nargs*/); /* -> [ ... stash eventTimers retval ] */
181 fprintf(stderr
, "timer callback failed for timer %d: %s\n", (int) t
->id
, duk_to_string(ctx
, -1));
185 duk_pop(ctx
); /* ignore errors for now -> [ ... stash eventTimers ] */
188 /* One-shot timer (always removed) or removed by user callback. */
190 fprintf(stderr
, "deleting callback state for timer %d\n", (int) t
->id
);
193 duk_push_number(ctx
, (double) t
->id
);
194 duk_del_prop(ctx
, -2);
196 /* Interval timer, not removed by user callback. Queue back to
197 * timer list and bubble to its final sorted position.
200 fprintf(stderr
, "queueing timer %d back into active list\n", (int) t
->id
);
203 if (timer_count
>= MAX_TIMERS
) {
204 duk_error(ctx
, DUK_ERR_RANGE_ERROR
, "out of timer slots");
206 memcpy((void *) (timer_list
+ timer_count
), (void *) t
, sizeof(ev_timer
));
212 memset((void *) &timer_expiring
, 0, sizeof(ev_timer
));
214 duk_pop_2(ctx
); /* -> [ ... ] */
217 static void compact_poll_list(void) {
221 * j = output index (initially same as i)
225 for (i
= 0, j
= 0; i
< n
; i
++) {
226 struct pollfd
*pfd
= poll_list
+ i
;
228 /* keep output index the same */
230 fprintf(stderr
, "remove pollfd (index %d): fd=%d, events=%d, revents=%d\n",
231 i
, pfd
->fd
, pfd
->events
, pfd
->revents
),
238 fprintf(stderr
, "keep pollfd (index %d -> %d): fd=%d, events=%d, revents=%d\n",
239 i
, j
, pfd
->fd
, pfd
->events
, pfd
->revents
),
243 /* copy only if indices have diverged */
244 memcpy((void *) (poll_list
+ j
), (void *) (poll_list
+ i
), sizeof(struct pollfd
));
249 if (j
< poll_count
) {
250 /* zeroize unused entries for sanity */
251 memset((void *) (poll_list
+ j
), 0, (poll_count
- j
) * sizeof(struct pollfd
));
257 int eventloop_run(duk_context
*ctx
) {
267 /* The Ecmascript poll handler is passed through EventLoop.fdPollHandler
268 * which c_eventloop.js sets before we come here.
270 duk_push_global_object(ctx
);
271 duk_get_prop_string(ctx
, -1, "EventLoop");
272 duk_get_prop_string(ctx
, -1, "fdPollHandler"); /* -> [ global EventLoop fdPollHandler ] */
273 idx_fd_handler
= duk_get_top_index(ctx
);
274 idx_eventloop
= idx_fd_handler
- 1;
284 * If exit requested, bail out as fast as possible.
287 if (exit_requested
) {
289 fprintf(stderr
, "exit requested, exiting event loop\n");
296 * Compact poll list by removing pollfds with fd == 0.
302 * Determine poll() timeout (as close to poll() as possible as
303 * the wait is relative).
307 t
= find_nearest_timer();
309 diff
= t
->target
- now
;
310 if (diff
< MIN_WAIT
) {
312 } else if (diff
> MAX_WAIT
) {
315 timeout
= (int) diff
; /* clamping ensures that fits */
317 if (poll_count
== 0) {
319 fprintf(stderr
, "no timers and no sockets to poll, exiting\n");
324 timeout
= (int) MAX_WAIT
;
328 * Poll for activity or timeout.
332 fprintf(stderr
, "going to poll, timeout %d ms, pollfd count %d\n", timeout
, poll_count
);
336 rc
= poll(poll_list
, poll_count
, timeout
);
338 fprintf(stderr
, "poll rc: %d\n", rc
);
343 } else if (rc
== 0) {
346 /* 'rc' fds active */
350 * Check socket activity, handle all sockets. Handling is offloaded to
351 * Ecmascript code (fd + revents).
353 * If FDs are removed from the poll list while we're processing callbacks,
354 * the entries are simply marked unused (fd set to 0) without actually
355 * removing them from the poll list. This ensures indices are not
356 * disturbed. The poll list is compacted before next poll().
359 n
= (rc
== 0 ? 0 : poll_count
); /* if timeout, no need to check pollfd */
360 for (i
= 0; i
< n
; i
++) {
361 struct pollfd
*pfd
= poll_list
+ i
;
364 /* deleted, perhaps by previous callback */
370 fprintf(stderr
, "fd %d has revents: %d\n", (int) pfd
->fd
, (int) pfd
->revents
);
373 duk_dup(ctx
, idx_fd_handler
);
374 duk_dup(ctx
, idx_eventloop
);
375 duk_push_int(ctx
, pfd
->fd
);
376 duk_push_int(ctx
, pfd
->revents
);
377 rc
= duk_pcall_method(ctx
, 2 /*nargs*/);
380 fprintf(stderr
, "fd callback failed for fd %d: %s\n", (int) pfd
->fd
, duk_to_string(ctx
, -1));
397 static int create_timer(duk_context
*ctx
) {
408 * 0 = function (callback)
410 * 2 = boolean: oneshot
413 delay
= duk_require_number(ctx
, 1);
414 if (delay
< MIN_DELAY
) {
417 oneshot
= duk_require_boolean(ctx
, 2);
419 if (timer_count
>= MAX_TIMERS
) {
420 duk_error(ctx
, DUK_ERR_RANGE_ERROR
, "out of timer slots");
423 timer_id
= timer_next_id
++;
424 t
= timer_list
+ idx
;
426 memset((void *) t
, 0, sizeof(ev_timer
));
428 t
->target
= now
+ delay
;
430 t
->oneshot
= oneshot
;
433 /* Timer is now at the last position; use swaps to "bubble" it to its
434 * correct sorted position.
439 /* Finally, register the callback to the global stash 'eventTimers' object. */
441 duk_push_global_stash(ctx
);
442 duk_get_prop_string(ctx
, -1, "eventTimers"); /* -> [ func delay oneshot stash eventTimers ] */
443 duk_push_number(ctx
, (double) timer_id
);
445 duk_put_prop(ctx
, -3); /* eventTimers[timer_id] = callback */
447 /* Return timer id. */
449 duk_push_number(ctx
, (double) timer_id
);
451 fprintf(stderr
, "created timer id: %d\n", (int) timer_id
);
457 static int delete_timer(duk_context
*ctx
) {
467 timer_id
= (int64_t) duk_require_number(ctx
, 0);
470 * Unlike insertion, deletion needs a full scan of the timer list
471 * and an expensive remove. If no match is found, nothing is deleted.
472 * Caller gets a boolean return code indicating match.
474 * When a timer is being expired and its user callback is running,
475 * the timer has been moved to 'timer_expiring' and its deletion
476 * needs special handling: just mark it to-be-deleted and let the
477 * expiry code remove it.
481 if (t
->id
== timer_id
) {
485 fprintf(stderr
, "deleted expiring timer id: %d\n", (int) timer_id
);
492 for (i
= 0; i
< n
; i
++) {
494 if (t
->id
== timer_id
) {
497 /* Shift elements downwards to keep the timer list dense
498 * (no need if last element).
500 if (i
< timer_count
- 1) {
501 memmove((void *) t
, (void *) (t
+ 1), (timer_count
- i
- 1) * sizeof(ev_timer
));
504 /* Zero last element for clarity. */
505 memset((void *) (timer_list
+ n
- 1), 0, sizeof(ev_timer
));
507 /* Update timer_count. */
510 /* The C state is now up-to-date, but we still need to delete
511 * the timer callback state from the global 'stash'.
514 duk_push_global_stash(ctx
);
515 duk_get_prop_string(ctx
, -1, "eventTimers"); /* -> [ timer_id stash eventTimers ] */
516 duk_push_number(ctx
, (double) timer_id
);
517 duk_del_prop(ctx
, -2); /* delete eventTimers[timer_id] */
520 fprintf(stderr
, "deleted timer id: %d\n", (int) timer_id
);
529 fprintf(stderr
, "trying to delete timer id %d, but not found; ignoring\n", (int) timer_id
);
534 duk_push_boolean(ctx
, found
);
538 static int listen_fd(duk_context
*ctx
) {
539 int fd
= duk_require_int(ctx
, 0);
540 int events
= duk_require_int(ctx
, 1);
545 fprintf(stderr
, "listen_fd: fd=%d, events=%d\n", fd
, events
);
548 /* events == 0 means stop listening to the FD */
551 for (i
= 0; i
< n
; i
++) {
555 fprintf(stderr
, "listen_fd: fd found at index %d\n", i
);
559 /* mark to-be-deleted, cleaned up by next poll */
562 pfd
->events
= events
;
568 /* not found, append to list */
570 fprintf(stderr
, "listen_fd: fd not found on list, add new entry\n");
574 if (poll_count
>= MAX_FDS
) {
575 duk_error(ctx
, DUK_ERR_ERROR
, "out of fd slots");
578 pfd
= poll_list
+ poll_count
;
580 pfd
->events
= events
;
587 static int request_exit(duk_context
*ctx
) {
593 static duk_function_list_entry eventloop_funcs
[] = {
594 { "createTimer", create_timer
, 3 },
595 { "deleteTimer", delete_timer
, 1 },
596 { "listenFd", listen_fd
, 2 },
597 { "requestExit", request_exit
, 0 },
601 void eventloop_register(duk_context
*ctx
) {
602 memset((void *) timer_list
, 0, MAX_TIMERS
* sizeof(ev_timer
));
603 memset((void *) &timer_expiring
, 0, sizeof(ev_timer
));
604 memset((void *) poll_list
, 0, MAX_FDS
* sizeof(struct pollfd
));
606 /* Set global 'EventLoop'. */
607 duk_push_global_object(ctx
);
608 duk_push_object(ctx
);
609 duk_put_function_list(ctx
, -1, eventloop_funcs
);
610 duk_put_prop_string(ctx
, -2, "EventLoop");
613 /* Initialize global stash 'eventTimers'. */
614 duk_push_global_stash(ctx
);
615 duk_push_object(ctx
);
616 duk_put_prop_string(ctx
, -2, "eventTimers");