From 768b24eb0e880c0b39e36fd089905cdca572a758 Mon Sep 17 00:00:00 2001 From: Dmitry Antipov Date: Mon, 28 Jul 2014 10:28:15 +0400 Subject: [PATCH] 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. --- ChangeLog | 6 ++ configure.ac | 20 +++++ m4/clock_time.m4 | 2 +- src/ChangeLog | 20 +++++ src/atimer.c | 155 +++++++++++++++++++++++++++++++--- src/atimer.h | 3 + src/lisp.h | 3 + src/process.c | 18 ++++ test/ChangeLog | 4 + test/automated/timer-tests.el | 6 +- 10 files changed, 222 insertions(+), 15 deletions(-) diff --git a/ChangeLog b/ChangeLog index f694a42202b..c7fd47636a5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +2014-07-28 Dmitry Antipov + + * 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. + 2014-07-13 Paul Eggert Improve behavior of 'bzr up; cd src; make -k'. diff --git a/configure.ac b/configure.ac index fc7a87a075a..89440446ee5 100644 --- a/configure.ac +++ b/configure.ac @@ -3710,6 +3710,26 @@ fi AC_SUBST(LIBS_TERMCAP) AC_SUBST(TERMCAP_OBJ) +# GNU/Linux-specific timer functions. +if test $opsys = gnu-linux; then + AC_MSG_CHECKING([whether Linux timerfd functions are supported]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include ]], + [[timerfd_create (CLOCK_REALTIME, 0); + timerfd_settime (0, 0, NULL, NULL)]])], + emacs_cv_linux_timerfd=yes, emacs_cv_linux_timerfd=no) + AC_MSG_RESULT([$emacs_cv_linux_timerfd]) + if test $emacs_cv_linux_timerfd = yes; then + AC_DEFINE(HAVE_TIMERFD, 1, [Define to 1 if Linux timerfd functions are supported.]) + AC_MSG_CHECKING([whether TFD_CLOEXEC is defined]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include ]], + [[timerfd_create (CLOCK_REALTIME, TFD_CLOEXEC)]])], + emacs_cv_tfd_cloexec=yes, emacs_cv_tfd_cloexec=no) + AC_MSG_RESULT([$emacs_cv_tfd_cloexec]) + if test $emacs_cv_tfd_cloexec = yes; then + AC_DEFINE(HAVE_TIMERFD_CLOEXEC, 1, [Define to 1 if TFD_CLOEXEC is defined.]) + fi + fi +fi # Do we have res_init, for detecting changes in /etc/resolv.conf? # On Darwin, res_init appears not to be useful: see bug#562 and diff --git a/m4/clock_time.m4 b/m4/clock_time.m4 index 6c4a637dc62..8513c6d781a 100644 --- a/m4/clock_time.m4 +++ b/m4/clock_time.m4 @@ -26,6 +26,6 @@ AC_DEFUN([gl_CLOCK_TIME], AC_SEARCH_LIBS([clock_gettime], [rt posix4], [test "$ac_cv_search_clock_gettime" = "none required" || LIB_CLOCK_GETTIME=$ac_cv_search_clock_gettime]) - AC_CHECK_FUNCS([clock_gettime clock_settime]) + AC_CHECK_FUNCS([clock_getres clock_gettime clock_settime]) LIBS=$gl_saved_libs ]) diff --git a/src/ChangeLog b/src/ChangeLog index b3f3750df75..ebc412c8869 100644 --- a/src/ChangeLog +++ b/src/ChangeLog @@ -1,3 +1,23 @@ +2014-07-28 Dmitry Antipov + + On GNU/Linux, use timerfd for asynchronous timers. + * 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. + * atimer.h (timerfd_callback) [HAVE_TIMERFD]: Add prototype. + * lisp.h (add_timer_wait_descriptor) [HAVE_TIMERFD]: Likewise. + * process.c (add_timer_wait_descriptor) [HAVE_TIMERFD]: New function. + 2014-07-28 Paul Eggert * frame.c (x_set_frame_parameters): Don't use uninitialized locals. 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 . */ #include "atimer.h" #include +#ifdef HAVE_TIMERFD +#include +#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 } diff --git a/src/atimer.h b/src/atimer.h index 379787abca7..2386977cf87 100644 --- a/src/atimer.h +++ b/src/atimer.h @@ -77,5 +77,8 @@ void init_atimer (void); void turn_on_atimers (bool); void stop_other_atimers (struct atimer *); void run_all_atimers (void); +#ifdef HAVE_TIMERFD +void timerfd_callback (int, void *); +#endif #endif /* EMACS_ATIMER_H */ diff --git a/src/lisp.h b/src/lisp.h index bf25f073d4b..67299706c6b 100644 --- a/src/lisp.h +++ b/src/lisp.h @@ -4186,6 +4186,9 @@ extern int wait_reading_process_output (intmax_t, int, int, bool, Lisp_Object, #else # define WAIT_READING_MAX INTMAX_MAX #endif +#ifdef HAVE_TIMERFD +extern void add_timer_wait_descriptor (int); +#endif extern void add_keyboard_wait_descriptor (int); extern void delete_keyboard_wait_descriptor (int); #ifdef HAVE_GPM diff --git a/src/process.c b/src/process.c index 4449493a2b6..cfc1e189cab 100644 --- a/src/process.c +++ b/src/process.c @@ -6827,6 +6827,24 @@ wait_reading_process_output (intmax_t time_limit, int nsecs, int read_kbd, /* The following functions are needed even if async subprocesses are not supported. Some of them are no-op stubs in that case. */ +#ifdef HAVE_TIMERFD + +/* Add FD, which is a descriptor returned by timerfd_create, + to the set of non-keyboard input descriptors. */ + +void +add_timer_wait_descriptor (int fd) +{ + FD_SET (fd, &non_keyboard_wait_mask); + fd_callback_info[fd].func = timerfd_callback; + fd_callback_info[fd].data = NULL; + fd_callback_info[fd].condition |= FOR_READ; + if (fd > max_input_desc) + max_input_desc = fd; +} + +#endif /* HAVE_TIMERFD */ + /* Add DESC to the set of keyboard input descriptors. */ void diff --git a/test/ChangeLog b/test/ChangeLog index 6afa9ea8959..140b5d04efd 100644 --- a/test/ChangeLog +++ b/test/ChangeLog @@ -1,3 +1,7 @@ +2014-07-28 Dmitry Antipov + + * automated/timer-tests.el (timer-tests-debug-timer-check): New test. + 2014-07-26 Ulf Jasper * automated/icalendar-tests.el (icalendar-tests--do-test-import): diff --git a/test/automated/timer-tests.el b/test/automated/timer-tests.el index bc95df5e773..f8e8414f8d7 100644 --- a/test/automated/timer-tests.el +++ b/test/automated/timer-tests.el @@ -34,5 +34,9 @@ (sit-for 0 t) (should timer-ran))) -;;; timer-tests.el ends here +(ert-deftest timer-tests-debug-timer-check () + ;; This function exists only if --enable-checking. + (if (fboundp 'debug-timer-check) + (should (debug-timer-check)) t)) +;;; timer-tests.el ends here -- 2.39.2