From 266ccb364ef5379abd1c0c7b0a2aa81c41753de6 Mon Sep 17 00:00:00 2001 From: David Lichteblau Date: Thu, 13 Sep 2012 18:26:19 +0200 Subject: [PATCH] Add a safepoint-based mechanism to avoid SIGALRM for the TIMER facility - Retrofits the signal-free timer thread for Windows (thanks to Anton Kovalenko) to POSIXy platforms. - Provide os_* functions in the C runtime which simulate the win32 API for waitable timers. Currently supported on Linux (timerfd), FreeBSD (kqueue), and SunOS (completion ports). A tentative (untested) implementation is provided for Darwin's kqueue. --- base-target-features.lisp-expr | 7 +++ src/code/save.lisp | 2 + src/code/target-signal.lisp | 2 + src/code/timer.lisp | 87 ++++++++++++++++++++++++++++++---- src/runtime/bsd-os.c | 62 ++++++++++++++++++++++++ src/runtime/darwin-os.c | 74 +++++++++++++++++++++++++++++ src/runtime/linux-os.c | 62 ++++++++++++++++++++++++ src/runtime/sunos-os.c | 101 ++++++++++++++++++++++++++++++++++++++++ 8 files changed, 387 insertions(+), 10 deletions(-) diff --git a/base-target-features.lisp-expr b/base-target-features.lisp-expr index 680e29d..22530df 100644 --- a/base-target-features.lisp-expr +++ b/base-target-features.lisp-expr @@ -327,6 +327,7 @@ ;; for the purpose of stopping and starting the world around GC can be ;; performed using safepoints instead of signals. Enable this feature ;; to compile with safepoints and to use them for GC. + ;; (Replaces use of SIG_STOP_FOR_GC.) ; :sb-safepoint ;; When compiling with safepoints, the INTERRUPT-THREAD mechanism can @@ -334,8 +335,14 @@ ;; can be interrupted safely, instead of using a signal for this ;; purpose. Enable this feature in addition to :SB-SAFEPOINT to enable ;; such behaviour. + ;; (Replaces use of SIGPIPE, except to wake up syscalls.) ; :sb-thruption + ;; When compiling with safepoints and thruptions, the TIMER facility + ;; can replace its use of setitimer with a background thread. + ;; (Replaces use of SIGALRM.) + ; :sb-wtimer + ;; ;; miscellaneous notes on other things which could have special significance ;; in the *FEATURES* list diff --git a/src/code/save.lisp b/src/code/save.lisp index a05a851..c2dd588 100644 --- a/src/code/save.lisp +++ b/src/code/save.lisp @@ -194,6 +194,8 @@ sufficiently motivated to do lengthy fixes." (defun deinit () (call-hooks "save" *save-hooks*) + #!+sb-wtimer + (itimer-emulation-deinit) (when (rest (sb!thread:list-all-threads)) (error "Cannot save core with multiple threads running.")) (float-deinit) diff --git a/src/code/target-signal.lisp b/src/code/target-signal.lisp index e62f87a..feea1cf 100644 --- a/src/code/target-signal.lisp +++ b/src/code/target-signal.lisp @@ -191,6 +191,7 @@ (sb!thread:interrupt-thread (sb!thread::foreground-thread) #'interrupt-it))) +#!-sb-wtimer (defun sigalrm-handler (signal info context) (declare (ignore signal info context)) (declare (type system-area-pointer context)) @@ -227,6 +228,7 @@ (enable-interrupt sigbus #'sigbus-handler) #!-linux (enable-interrupt sigsys #'sigsys-handler) + #!-sb-wtimer (enable-interrupt sigalrm #'sigalrm-handler) #!-sb-thruption (enable-interrupt sigpipe #'sigpipe-handler) diff --git a/src/code/timer.lisp b/src/code/timer.lisp index 88b9c1d..29dc0a8 100644 --- a/src/code/timer.lisp +++ b/src/code/timer.lisp @@ -315,20 +315,87 @@ triggers." (incf (%timer-expire-time timer) (%timer-repeat-interval timer)) (%schedule-timer timer)))))) +;;; setitimer is unavailable for win32, but we can emulate it when +;;; threads are available -- using win32 waitable timers. +;;; +;;; Conversely, when we want to minimize signal use on POSIX, we emulate +;;; win32 waitable timers using a timerfd-like portability layer in +;;; the runtime. + +#!+sb-wtimer +(define-alien-type wtimer + #!+win32 system-area-pointer ;HANDLE, but that's not defined yet + #!+sunos system-area-pointer ;struct os_wtimer * + #!+(or linux bsd) int) + +#!+sb-wtimer +(progn + (define-alien-routine "os_create_wtimer" wtimer) + (define-alien-routine "os_wait_for_wtimer" int (wt wtimer)) + (define-alien-routine "os_close_wtimer" void (wt wtimer)) + (define-alien-routine "os_cancel_wtimer" void (wt wtimer)) + (define-alien-routine "os_set_wtimer" void (wt wtimer) (sec int) (nsec int)) + + ;; scheduler lock already protects us + + (defvar *waitable-timer-handle* nil) + + (defvar *timer-thread* nil) + + (defun get-waitable-timer () + (assert (under-scheduler-lock-p)) + (or *waitable-timer-handle* + (prog1 + (setf *waitable-timer-handle* (os-create-wtimer)) + (setf *timer-thread* + (sb!thread:make-thread + (lambda () + (loop while + (or (zerop + (os-wait-for-wtimer *waitable-timer-handle*)) + *waitable-timer-handle*) + doing (run-expired-timers))) + :ephemeral t + :name "System timer watchdog thread"))))) + + (defun itimer-emulation-deinit () + (with-scheduler-lock () + (when *timer-thread* + (sb!thread:terminate-thread *timer-thread*) + (sb!thread:join-thread *timer-thread* :default nil)) + (when *waitable-timer-handle* + (os-close-wtimer *waitable-timer-handle*) + (setf *waitable-timer-handle* nil)))) + + (defun %clear-system-timer () + (os-cancel-wtimer (get-waitable-timer))) + + (defun %set-system-timer (sec nsec) + (os-set-wtimer (get-waitable-timer) sec nsec))) + ;;; Expiring timers -(defun real-time->sec-and-usec (time) +(defun real-time->sec-and-nsec (time) ;; KLUDGE: Always leave 0.0001 second for other stuff in order to ;; avoid starvation. - (let ((min-usec 100)) + (let ((min-nsec 100000)) (if (minusp time) - (list 0 min-usec) + (values 0 min-nsec) (multiple-value-bind (s u) (floor time internal-time-units-per-second) - (setf u (floor (* (/ u internal-time-units-per-second) 1000000))) - (if (and (= 0 s) (< u min-usec)) + (setf u (floor (* (/ u internal-time-units-per-second) + #.(expt 10 9)))) + (if (and (= 0 s) (< u min-nsec)) ;; 0 0 means "shut down the timer" for setitimer - (list 0 min-usec) - (list s u)))))) + (values 0 min-nsec) + (values s u)))))) + +#!-(or sb-wtimer win32) +(progn + (defun %set-system-timer (sec nsec) + (sb!unix:unix-setitimer :real 0 0 sec (ceiling nsec 1000))) + + (defun %clear-system-timer () + (sb!unix:unix-setitimer :real 0 0 0 0))) (defun set-system-timer () (assert (under-scheduler-lock-p)) @@ -337,9 +404,9 @@ triggers." (if next-timer (let ((delta (- (%timer-expire-time next-timer) (get-internal-real-time)))) - (apply #'sb!unix:unix-setitimer - :real 0 0 (real-time->sec-and-usec delta))) - (sb!unix:unix-setitimer :real 0 0 0 0)))) + (multiple-value-call #'%set-system-timer + (real-time->sec-and-nsec delta))) + (%clear-system-timer)))) (defun run-timer (timer) (let ((function (%timer-interrupt-function timer)) diff --git a/src/runtime/bsd-os.c b/src/runtime/bsd-os.c index c2829b3..4c08b97 100644 --- a/src/runtime/bsd-os.c +++ b/src/runtime/bsd-os.c @@ -45,6 +45,11 @@ #if defined LISP_FEATURE_GENCGC #include "gencgc-internal.h" #endif + +#if defined(LISP_FEATURE_SB_WTIMER) && !defined(LISP_FEATURE_DARWIN) +# include +#endif + os_vm_size_t os_vm_page_size; @@ -567,3 +572,60 @@ os_dlsym(void *handle, const char *symbol) } #endif + +#if defined(LISP_FEATURE_SB_WTIMER) && !defined(LISP_FEATURE_DARWIN) +/* + * Waitable timer implementation for the safepoint-based (SIGALRM-free) + * timer facility using kqueue. + */ +int +os_create_wtimer() +{ + int kq = kqueue(); + if (kq == -1) + lose("os_create_wtimer: kqueue"); + return kq; +} + +int +os_wait_for_wtimer(int kq) +{ + struct kevent ev; + int n; + if ( (n = kevent(kq, 0, 0, &ev, 1, 0)) == -1) { + if (errno != EINTR) + lose("os_wtimer_listen failed"); + n = 0; + } + return n != 1; +} + +void +os_close_wtimer(int kq) +{ + if (close(kq) == -1) + lose("os_close_wtimer failed"); +} + +void +os_set_wtimer(int kq, int sec, int nsec) +{ + long long msec + = ((long long) sec) * 1000 + (long long) (nsec+999999) / 1000000; + if (msec > INT_MAX) msec = INT_MAX; + + struct kevent ev; + EV_SET(&ev, 1, EVFILT_TIMER, EV_ADD|EV_ENABLE|EV_ONESHOT, 0, (int)msec, 0); + if (kevent(kq, &ev, 1, 0, 0, 0) == -1) + perror("os_set_wtimer: kevent"); +} + +void +os_cancel_wtimer(int kq) +{ + struct kevent ev; + EV_SET(&ev, 1, EVFILT_TIMER, EV_DISABLE, 0, 0, 0); + if (kevent(kq, &ev, 1, 0, 0, 0) == -1 && errno != ENOENT) + perror("os_cancel_wtimer: kevent"); +} +#endif diff --git a/src/runtime/darwin-os.c b/src/runtime/darwin-os.c index 1103d8f..415745e 100644 --- a/src/runtime/darwin-os.c +++ b/src/runtime/darwin-os.c @@ -31,6 +31,12 @@ #include #endif +#if defined(LISP_FEATURE_SB_WTIMER) +# include +# include +# include +#endif + char * os_get_runtime_executable_path(int external) { @@ -301,3 +307,71 @@ os_sem_destroy(os_sem_t *sem) } #endif + +#if defined(LISP_FEATURE_SB_WTIMER) + +# error Completely untested. Go ahead! Remove this line, try your luck! + +/* + * Waitable timer implementation for the safepoint-based (SIGALRM-free) + * timer facility using kqueue. + * + * Unlike FreeBSD with its ms (!) timer resolution, Darwin supports ns + * timer resolution -- or at least it pretends to do so on the API + * level (?). To use it, we need the *64 versions of the functions and + * structures. + * + * Unfortunately, I don't run Darwin, and can't test this code, so it's + * just a hopeful translation from FreeBSD. + */ + +int +os_create_wtimer() +{ + int kq = kqueue(); + if (kq == -1) + lose("os_create_wtimer: kqueue"); + return kq; +} + +int +os_wait_for_wtimer(int kq) +{ + struct kevent64_s ev; + int n; + if ( (n = kevent64(kq, 0, 0, &ev, 1, 0, 0)) == -1) { + if (errno != EINTR) + lose("os_wtimer_listen failed"); + n = 0; + } + return n != 1; +} + +void +os_close_wtimer(int kq) +{ + if (close(kq) == -1) + lose("os_close_wtimer failed"); +} + +void +os_set_wtimer(int kq, int sec, int nsec) +{ + int64_t nsec = ((int64_t) sec) * 1000000000 + (int64_t) nsec; + + struct kevent64_s ev; + EV_SET64(&ev, 1, EVFILT_TIMER, EV_ADD|EV_ENABLE|EV_ONESHOT, NOTE_NSECONDS, + nsec, 0, 0, 0); + if (kevent64(kq, &ev, 1, 0, 0, 0, 0) == -1) + perror("os_set_wtimer: kevent"); +} + +void +os_cancel_wtimer(int kq) +{ + struct kevent64_s ev; + EV_SET64(&ev, 1, EVFILT_TIMER, EV_DISABLE, 0, 0, 0, 0, 0); + if (kevent64(kq, &ev, 1, 0, 0, 0, 0) == -1 && errno != ENOENT) + perror("os_cancel_wtimer: kevent"); +} +#endif diff --git a/src/runtime/linux-os.c b/src/runtime/linux-os.c index bc87f2a..b7ceca1 100644 --- a/src/runtime/linux-os.c +++ b/src/runtime/linux-os.c @@ -55,6 +55,10 @@ #else #include "cheneygc-internal.h" #endif +#include +#ifdef LISP_FEATURE_SB_WTIMER +# include +#endif #ifdef LISP_FEATURE_X86 /* Prototype for personality(2). Done inline here since the header file @@ -482,3 +486,61 @@ os_get_runtime_executable_path(int external) return copied_string(path); } + +#ifdef LISP_FEATURE_SB_WTIMER +/* + * Waitable timer implementation for the safepoint-based (SIGALRM-free) + * timer facility using timerfd_create(). + */ +int +os_create_wtimer() +{ + int fd = timerfd_create(CLOCK_MONOTONIC, 0); + if (fd == -1) + lose("os_create_wtimer: timerfd_create"); + + /* Cannot count on TFD_CLOEXEC availability, so do it manually: */ + if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) + lose("os_create_wtimer: fcntl"); + + return fd; +} + +int +os_wait_for_wtimer(int fd) +{ + unsigned char buf[8]; + int n = read(fd, buf, sizeof(buf)); + if (n == -1) { + if (errno == EINTR) + return -1; + lose("os_wtimer_listen failed"); + } + if (n != sizeof(buf)) + lose("os_wtimer_listen read too little"); + return 0; +} + +void +os_close_wtimer(int fd) +{ + if (close(fd) == -1) + lose("os_close_wtimer failed"); +} + +void +os_set_wtimer(int fd, int sec, int nsec) +{ + struct itimerspec spec = { {0,0}, {0,0} }; + spec.it_value.tv_sec = sec; + spec.it_value.tv_nsec = nsec; + if (timerfd_settime(fd, 0, &spec, 0) == -1) + lose("timerfd_settime"); +} + +void +os_cancel_wtimer(int fd) +{ + os_set_wtimer(fd, 0, 0); +} +#endif diff --git a/src/runtime/sunos-os.c b/src/runtime/sunos-os.c index cab00ba..eb84223 100644 --- a/src/runtime/sunos-os.c +++ b/src/runtime/sunos-os.c @@ -26,6 +26,12 @@ #include "gencgc-internal.h" #endif +#ifdef LISP_FEATURE_SB_WTIMER +# include +# include +# include +#endif + os_vm_size_t os_vm_page_size=0; void @@ -200,3 +206,98 @@ os_get_runtime_executable_path(int external) return copied_string(path); } +#ifdef LISP_FEATURE_SB_WTIMER +/* + * Waitable timer implementation for the safepoint-based (SIGALRM-free) + * timer facility using SunOS completion ports. + */ + +struct os_wtimer { + int port; + int timer; +}; + +struct os_wtimer * +os_create_wtimer() +{ + int port = port_create(); + if (port == -1) { + perror("port_create"); + lose("os_create_wtimer"); + } + + port_notify_t pn; + pn.portnfy_port = port; + pn.portnfy_user = 0; + + struct sigevent ev; + memset(&ev, 0, sizeof(ev)); + ev.sigev_notify = SIGEV_PORT; + ev.sigev_value.sival_ptr = &pn; + + timer_t timer; + if (timer_create(CLOCK_HIGHRES, &ev, &timer) == -1 + && (errno != EPERM || timer_create(CLOCK_REALTIME, &ev, &timer) == -1)) + { + perror("timer_create"); + lose("os_create_wtimer"); + } + + struct os_wtimer *wt = malloc(sizeof(struct os_wtimer)); + if (!wt) + lose("os_create_wtimer: malloc"); + + wt->port = port; + wt->timer = timer; + return wt; +} + +int +os_wait_for_wtimer(struct os_wtimer *wt) +{ + port_event_t pe; + if (port_get(wt->port, &pe, 0) == -1) { + if (errno == EINTR) + return 1; + perror("port_get"); + lose("os_wtimer_listen failed"); + } + return 0; +} + +void +os_close_wtimer(struct os_wtimer *wt) +{ + if (close(wt->port) == -1) { + perror("close"); + lose("os_close_wtimer"); + } + if (timer_delete(wt->timer) == -1) { + perror("timer_delete"); + lose("os_close_wtimer"); + } + free(wt); +} + +void +os_set_wtimer(struct os_wtimer *wt, int sec, int nsec) +{ + struct itimerspec spec; + spec.it_value.tv_sec = sec; + spec.it_value.tv_nsec = nsec; + spec.it_interval.tv_sec = 0; + spec.it_interval.tv_nsec = 0; + if (timer_settime(wt->timer, 0, &spec, 0) == -1) { + int x = errno; + perror("timer_settime"); + if (x != EINVAL) + lose("os_set_wtimer"); + } +} + +void +os_cancel_wtimer(struct os_wtimer *wt) +{ + os_set_wtimer(wt, 0, 0); +} +#endif -- 1.7.10.4