diff options
author | Dmitry Antipov <dmantipov@yandex.ru> | 2014-07-28 10:28:15 +0400 |
---|---|---|
committer | Dmitry Antipov <dmantipov@yandex.ru> | 2014-07-28 10:28:15 +0400 |
commit | 768b24eb0e880c0b39e36fd089905cdca572a758 (patch) | |
tree | 75899b103fedc549e5d17294d39bfcb4ed0a9bc9 /src/atimer.c | |
parent | 7daa4ff121ad8da6e609b959d5c95796e5d3a9eb (diff) | |
download | emacs-768b24eb0e880c0b39e36fd089905cdca572a758.tar.gz emacs-768b24eb0e880c0b39e36fd089905cdca572a758.tar.bz2 emacs-768b24eb0e880c0b39e36fd089905cdca572a758.zip |
On GNU/Linux, use timerfd for asynchronous timers.
* configure.ac (toplevel): Check whether GNU/Linux-specific
timerfd functions and macros are available.
* m4/clock_time.m4 (gl_CLOCK_TIME): Check for clock_getres as well.
* src/atimer.c (toplevel) [HAVE_TIMERFD]: Include sys/timerfd.h.
(toplevel): Rename alarm_timer_ok to special_timer_available.
[HAVE_TIMERFD]: Declare timerfd.
[HAVE_CLOCK_GETRES]: Declare resolution.
(start_atimer) [HAVE_CLOCK_GETRES]: Round up timestamp to
system timer resolution.
(set_alarm) [HAVE_TIMERFD]: Use timerfd_settime.
(timerfd_callback) [HAVE_TIMERFD]: New function.
(atimer_result, debug_timer_callback, Fdebug_timer_check)
[ENABLE_CHECKING]: New function for the sake of automated tests.
(init_atimer) [HAVE_TIMERFD]: Setup timerfd.
[HAVE_CLOCK_GETRES]: Likewise for system timer resolution.
[ENABLE_CHECKING]: Defsubr test function.
* src/atimer.h (timerfd_callback) [HAVE_TIMERFD]: Add prototype.
* src/lisp.h (add_timer_wait_descriptor) [HAVE_TIMERFD]: Likewise.
* src/process.c (add_timer_wait_descriptor) [HAVE_TIMERFD]: New function.
* test/automated/timer-tests.el (timer-tests-debug-timer-check): New test.
Diffstat (limited to 'src/atimer.c')
-rw-r--r-- | src/atimer.c | 155 |
1 files changed, 142 insertions, 13 deletions
diff --git a/src/atimer.c b/src/atimer.c index c4f062beb17..9079e7712e0 100644 --- a/src/atimer.c +++ b/src/atimer.c @@ -26,6 +26,15 @@ along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. */ #include "atimer.h" #include <unistd.h> +#ifdef HAVE_TIMERFD +#include <sys/timerfd.h> +#ifdef HAVE_TIMERFD_CLOEXEC +#define TIMERFD_CREATE_FLAGS TFD_CLOEXEC +#else +#define TIMERFD_CREATE_FLAGS 0 +#endif /* HAVE_TIMERFD_CLOEXEC */ +#endif /* HAVE_TIMERFD */ + /* Free-list of atimer structures. */ static struct atimer *free_atimers; @@ -40,11 +49,23 @@ static struct atimer *stopped_atimers; static struct atimer *atimers; -/* The alarm timer and whether it was properly initialized, if - POSIX timers are available. */ -#ifdef HAVE_ITIMERSPEC +#if defined (HAVE_TIMERFD) +/* File descriptor returned by timerfd_create. GNU/Linux-specific. */ +static int timerfd; +#elif defined (HAVE_ITIMERSPEC) +/* The alarm timer used if POSIX timers are available. */ static timer_t alarm_timer; -static bool alarm_timer_ok; +#endif + +#if defined (HAVE_TIMERFD) || defined (HAVE_ITIMERSPEC) +/* Non-zero if one of the above was successfully initialized. Do not + use bool due to special treatment if HAVE_TIMERFD, see below. */ +static int special_timer_available; +#endif + +#ifdef HAVE_CLOCK_GETRES +/* Resolution of CLOCK_REALTIME clock. */ +static struct timespec resolution; #endif /* Block/unblock SIGALRM. */ @@ -96,11 +117,16 @@ start_atimer (enum atimer_type type, struct timespec timestamp, struct atimer *t; sigset_t oldset; - /* Round TIME up to the next full second if we don't have - itimers. */ -#ifndef HAVE_SETITIMER +#if !defined (HAVE_SETITIMER) + /* Round TIME up to the next full second if we don't have itimers. */ if (timestamp.tv_nsec != 0 && timestamp.tv_sec < TYPE_MAXIMUM (time_t)) timestamp = make_timespec (timestamp.tv_sec + 1, 0); +#elif defined (HAVE_CLOCK_GETRES) + /* Check that the system clock is precise enough. If + not, round TIME up to the system clock resolution. */ + if (timespec_valid_p (resolution) + && timespec_cmp (timestamp, resolution) < 0) + timestamp = resolution; #endif /* not HAVE_SETITIMER */ /* Get an atimer structure from the free-list, or allocate @@ -285,16 +311,25 @@ set_alarm (void) #endif struct timespec now, interval; -#ifdef HAVE_ITIMERSPEC - if (alarm_timer_ok) +#if defined (HAVE_TIMERFD) || defined (HAVE_ITIMERSPEC) + if (special_timer_available) { struct itimerspec ispec; ispec.it_value = atimers->expiration; ispec.it_interval.tv_sec = ispec.it_interval.tv_nsec = 0; +#if defined (HAVE_TIMERFD) + if (special_timer_available == 1) + { + add_timer_wait_descriptor (timerfd); + special_timer_available++; + } + if (timerfd_settime (timerfd, TFD_TIMER_ABSTIME, &ispec, 0) == 0) +#elif defined (HAVE_ITIMERSPEC) if (timer_settime (alarm_timer, TIMER_ABSTIME, &ispec, 0) == 0) +#endif return; } -#endif +#endif /* HAVE_TIMERFD || HAVE_ITIMERSPEC */ /* Determine interval till the next timer is ripe. Don't set the interval to 0; this disables the timer. */ @@ -373,6 +408,15 @@ handle_alarm_signal (int sig) pending_signals = 1; } +#ifdef HAVE_TIMERFD + +void +timerfd_callback (int fd, void *arg) +{ + do_pending_atimers (); +} + +#endif /* HAVE_TIMERFD */ /* Do pending timers. */ @@ -401,21 +445,106 @@ turn_on_atimers (bool on) alarm (0); } +/* This is intended to use from automated tests. */ + +#ifdef ENABLE_CHECKING + +#define MAXTIMERS 10 + +struct atimer_result +{ + /* Time when we expect this timer to trigger. */ + struct timespec expected; + + /* Timer status: -1 if not triggered, 0 if triggered + too early or too late, 1 if triggered timely. */ + int intime; +}; + +static void +debug_timer_callback (struct atimer *t) +{ + struct timespec now = current_timespec (); + struct atimer_result *r = (struct atimer_result *) t->client_data; + int result = timespec_cmp (now, r->expected); + + if (result < 0) + /* Too early. */ + r->intime = 0; + else if (result >= 0) + { +#ifdef HAVE_SETITIMER + struct timespec delta = timespec_sub (now, r->expected); + /* Too late if later than expected + 0.01s. FIXME: + this should depend from system clock resolution. */ + if (timespec_cmp (delta, make_timespec (0, 10000000)) > 0) + r->intime = 0; + else +#endif /* HAVE_SETITIMER */ + r->intime = 1; + } +} + +DEFUN ("debug-timer-check", Fdebug_timer_check, Sdebug_timer_check, 0, 0, 0, + doc: /* Run internal self-tests to check timers subsystem. +Return t if all self-tests are passed, nil otherwise. */) + (void) +{ + int i, ok; + struct atimer *timer; + struct atimer_result *results[MAXTIMERS]; + struct timespec t = make_timespec (0, 0); + + /* Arm MAXTIMERS relative timers to trigger with 0.1s intervals. */ + for (i = 0; i < MAXTIMERS; i++) + { + results[i] = xmalloc (sizeof (struct atimer_result)); + t = timespec_add (t, make_timespec (0, 100000000)); + results[i]->expected = timespec_add (current_timespec (), t); + results[i]->intime = -1; + timer = start_atimer (ATIMER_RELATIVE, t, + debug_timer_callback, results[i]); + } + + /* Wait for 1s but process timers. */ + wait_reading_process_output (1, 0, 0, false, Qnil, NULL, 0); + /* Shut up the compiler by "using" this variable. */ + (void) timer; + + for (i = 0, ok = 0; i < MAXTIMERS; i++) + ok += results[i]->intime, xfree (results[i]); + + return ok == MAXTIMERS ? Qt : Qnil; +} + +#endif /* ENABLE_CHECKING */ void init_atimer (void) { -#ifdef HAVE_ITIMERSPEC +#if defined (HAVE_TIMERFD) + timerfd = timerfd_create (CLOCK_REALTIME, TIMERFD_CREATE_FLAGS); + special_timer_available = !!(timerfd != -1); +#elif defined (HAVE_ITIMERSPEC) struct sigevent sigev; sigev.sigev_notify = SIGEV_SIGNAL; sigev.sigev_signo = SIGALRM; sigev.sigev_value.sival_ptr = &alarm_timer; - alarm_timer_ok = timer_create (CLOCK_REALTIME, &sigev, &alarm_timer) == 0; -#endif + special_timer_available + = timer_create (CLOCK_REALTIME, &sigev, &alarm_timer) == 0; +#endif /* HAVE_TIMERFD */ +#ifdef HAVE_CLOCK_GETRES + if (clock_getres (CLOCK_REALTIME, &resolution)) + resolution = invalid_timespec (); +#endif free_atimers = stopped_atimers = atimers = NULL; /* pending_signals is initialized in init_keyboard. */ struct sigaction action; emacs_sigaction_init (&action, handle_alarm_signal); sigaction (SIGALRM, &action, 0); + +#ifdef ENABLE_CHECKING + defsubr (&Sdebug_timer_check); +#endif } |