Add a safepoint-based mechanism to avoid SIGALRM for the TIMER facility
authorDavid Lichteblau <david@lichteblau.com>
Thu, 13 Sep 2012 16:26:19 +0000 (18:26 +0200)
committerDavid Lichteblau <david@lichteblau.com>
Wed, 19 Sep 2012 15:10:28 +0000 (17:10 +0200)
  - 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
src/code/save.lisp
src/code/target-signal.lisp
src/code/timer.lisp
src/runtime/bsd-os.c
src/runtime/darwin-os.c
src/runtime/linux-os.c
src/runtime/sunos-os.c

index 680e29d..22530df 100644 (file)
  ;; 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
  ;; 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
index a05a851..c2dd588 100644 (file)
@@ -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)
index e62f87a..feea1cf 100644 (file)
     (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))
   (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)
index 88b9c1d..29dc0a8 100644 (file)
@@ -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))
index c2829b3..4c08b97 100644 (file)
 #if defined LISP_FEATURE_GENCGC
 #include "gencgc-internal.h"
 #endif
+
+#if defined(LISP_FEATURE_SB_WTIMER) && !defined(LISP_FEATURE_DARWIN)
+# include <sys/event.h>
+#endif
+
 \f
 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
index 1103d8f..415745e 100644 (file)
 #include <stdlib.h>
 #endif
 
+#if defined(LISP_FEATURE_SB_WTIMER)
+# include <sys/types.h>
+# include <sys/event.h>
+# include <sys/time.h>
+#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
index bc87f2a..b7ceca1 100644 (file)
 #else
 #include "cheneygc-internal.h"
 #endif
+#include <fcntl.h>
+#ifdef LISP_FEATURE_SB_WTIMER
+# include <sys/timerfd.h>
+#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
index cab00ba..eb84223 100644 (file)
 #include "gencgc-internal.h"
 #endif
 
+#ifdef LISP_FEATURE_SB_WTIMER
+# include <port.h>
+# include <time.h>
+# include <errno.h>
+#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