1.0.28.37: resignal signals received in foreign threads
authorGabor Melis <mega@hotpop.com>
Mon, 11 May 2009 13:24:34 +0000 (13:24 +0000)
committerGabor Melis <mega@hotpop.com>
Mon, 11 May 2009 13:24:34 +0000 (13:24 +0000)
Signals delivered to threads started from foreign land (read: directly
by pthread_create, not by MAKE-THREAD) are redirected to a Lisp thread
by blocking all signals and resignalling.

NEWS
doc/manual/threading.texinfo
src/runtime/interrupt.c
src/runtime/monitor.c
src/runtime/thread.c
src/runtime/thread.h
tests/kill-non-lisp-thread.c [new file with mode: 0644]
tests/kill-non-lisp-thread.impure.lisp [new file with mode: 0644]
version.lisp-expr

diff --git a/NEWS b/NEWS
index c332724..505e518 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -37,6 +37,9 @@
   * bug fix: the value of CL:- in the inspector was the previous expression
     evaluated rather than the expression being evaluated.
   * bug fix: constants can no longer be locally declared special.
+  * bug fix: signals delivered to threads started from foreign land (read:
+    directly by pthread_create, not by MAKE-THREAD) are redirected to a Lisp
+    thread by blocking all signals and resignalling.
 
 changes in sbcl-1.0.28 relative to 1.0.27:
   * a number of bugs in cross-compilation have been fixed, with the ultimate
index 6219f67..d1185e7 100644 (file)
@@ -20,7 +20,8 @@ threading on Darwin (Mac OS X) and FreeBSD on the x86 is experimental.
 * Semaphores::
 * Waitqueue/condition variables::
 * Sessions/Debugging::
-* Implementation (Linux x86)::
+* Foreign threads::
+* Implementation (Linux x86/x86-64)::
 @end menu
 
 @node Threading basics
@@ -238,7 +239,28 @@ input stream is managed by calls to @code{sb-thread:get-foreground}
 @code{sb-ext:quit} terminates all threads in the current session, but
 leaves other sessions running.
 
-@node Implementation (Linux x86)
+@node Foreign threads
+@comment  node-name,  next,  previous,  up
+@section Foreign threads
+
+Direct calls to @code{pthread_create} (instead of @code{MAKE-THREAD})
+create threads that SBCL is not aware of, these are called foreign
+threads. Currently, it is not possible to run Lisp code in such
+threads. This means that the Lisp side signal handlers cannot work.
+The best solution is to start foreign threads with signals blocked,
+but since third party libraries may create threads, it is not always
+feasible to do so. As a workaround, upon receiving a signal in a
+foreign thread, SBCL changes the thread's sigmask to block all signals
+that it wants to handle and resends the signal to the current process
+which should land in a thread that does not block it, that is, a Lisp
+thread.
+
+The resignalling trick cannot work for synchronously triggered signals
+(SIGSEGV and co), take care not to trigger any. Resignalling for
+synchronously triggered signals in foreign threads is subject to
+@code{--lose-on-corruption}, see @ref{Runtime Options}.
+
+@node Implementation (Linux x86/x86-64)
 @comment  node-name,  next,  previous,  up
 @section Implementation (Linux x86/x86-64)
 
index e469d8f..4765645 100644 (file)
 #include "genesis/simple-fun.h"
 #include "genesis/cons.h"
 
+/* When we catch an internal error, should we pass it back to Lisp to
+ * be handled in a high-level way? (Early in cold init, the answer is
+ * 'no', because Lisp is still too brain-dead to handle anything.
+ * After sufficient initialization has been completed, the answer
+ * becomes 'yes'.) */
+boolean internal_errors_enabled = 0;
+
+#ifndef LISP_FEATURE_WIN32
+static
+void (*interrupt_low_level_handlers[NSIG]) (int, siginfo_t*, os_context_t*);
+#endif
+union interrupt_handler interrupt_handlers[NSIG];
+
 /* Under Linux on some architectures, we appear to have to restore the
  * FPU control word from the context, as after the signal is delivered
  * we appear to have a null FPU control word. */
     os_context_t *context = arch_os_get_context(&void_context);
 #endif
 
+/* Foreign code may want to start some threads on its own.
+ * Non-targetted, truly asynchronous signals can be delivered to
+ * basically any thread, but invoking Lisp handlers in such foregign
+ * threads is really bad, so let's resignal it.
+ *
+ * This should at least bring attention to the problem, but it cannot
+ * work for SIGSEGV and similar. It is good enough for timers, and
+ * maybe all deferrables. */
+
+static void
+add_handled_signals(sigset_t *sigset)
+{
+    int i;
+    for(i = 1; i < NSIG; i++) {
+        if (!(ARE_SAME_HANDLER(interrupt_low_level_handlers[i], SIG_DFL)) ||
+            !(ARE_SAME_HANDLER(interrupt_handlers[i].c, SIG_DFL))) {
+            sigaddset(sigset, i);
+        }
+    }
+}
+
+void block_signals(sigset_t *what, sigset_t *where, sigset_t *old);
+
+static boolean
+maybe_resignal_to_lisp_thread(int signal, os_context_t *context)
+{
+#ifdef LISP_FEATURE_SB_THREAD
+    if (!pthread_getspecific(lisp_thread)) {
+        if (!(sigismember(&deferrable_sigset,signal))) {
+            corruption_warning_and_maybe_lose
+                ("Received signal %d in non-lisp thread %lu, resignalling to a lisp thread.",
+                 signal,
+                 pthread_self());
+        }
+        {
+            sigset_t sigset;
+            sigemptyset(&sigset);
+            add_handled_signals(&sigset);
+            block_signals(&sigset, 0, 0);
+            block_signals(&sigset, os_context_sigmask_addr(context), 0);
+            kill(getpid(), signal);
+        }
+        return 1;
+    } else
+#endif
+        return 0;
+}
+
 /* These are to be used in signal handlers. Currently all handlers are
  * called from one of:
  *
  * kernel properly, so we fix it up ourselves in the
  * arch_os_get_context(..) function. -- CSR, 2002-07-23
  */
-#define SAVE_ERRNO(context,void_context)                        \
+#define SAVE_ERRNO(signal,context,void_context)                 \
     {                                                           \
         int _saved_errno = errno;                               \
         RESTORE_FP_CONTROL_WORD(context,void_context);          \
+        if (!maybe_resignal_to_lisp_thread(signal, context))    \
         {
 
 #define RESTORE_ERRNO                                           \
@@ -531,20 +593,6 @@ check_interrupt_context_or_lose(os_context_t *context)
     }
 #endif
 }
-
-/* When we catch an internal error, should we pass it back to Lisp to
- * be handled in a high-level way? (Early in cold init, the answer is
- * 'no', because Lisp is still too brain-dead to handle anything.
- * After sufficient initialization has been completed, the answer
- * becomes 'yes'.) */
-boolean internal_errors_enabled = 0;
-
-#ifndef LISP_FEATURE_WIN32
-static
-void (*interrupt_low_level_handlers[NSIG]) (int, siginfo_t*, os_context_t*);
-#endif
-union interrupt_handler interrupt_handlers[NSIG];
-
 \f
 /*
  * utility routines used by various signal handlers
@@ -1100,10 +1148,9 @@ store_signal_data_for_later (struct interrupt_data *data, void *handler,
 static void
 maybe_now_maybe_later(int signal, siginfo_t *info, void *void_context)
 {
-    SAVE_ERRNO(context,void_context);
+    SAVE_ERRNO(signal,context,void_context);
     struct thread *thread = arch_os_get_current_thread();
     struct interrupt_data *data = thread->interrupt_data;
-
     if(!maybe_defer_handler(interrupt_handle_now,data,signal,info,context))
         interrupt_handle_now(signal, info, context);
     RESTORE_ERRNO;
@@ -1123,7 +1170,7 @@ low_level_interrupt_handle_now(int signal, siginfo_t *info,
 static void
 low_level_maybe_now_maybe_later(int signal, siginfo_t *info, void *void_context)
 {
-    SAVE_ERRNO(context,void_context);
+    SAVE_ERRNO(signal,context,void_context);
     struct thread *thread = arch_os_get_current_thread();
     struct interrupt_data *data = thread->interrupt_data;
 
@@ -1209,7 +1256,7 @@ sig_stop_for_gc_handler(int signal, siginfo_t *info, os_context_t *context)
 void
 interrupt_handle_now_handler(int signal, siginfo_t *info, void *void_context)
 {
-    SAVE_ERRNO(context,void_context);
+    SAVE_ERRNO(signal,context,void_context);
 #ifndef LISP_FEATURE_WIN32
     if ((signal == SIGILL) || (signal == SIGBUS)
 #ifndef LISP_FEATURE_LINUX
@@ -1598,7 +1645,7 @@ see_if_sigaction_nodefer_works(void)
 static void
 unblock_me_trampoline(int signal, siginfo_t *info, void *void_context)
 {
-    SAVE_ERRNO(context,void_context);
+    SAVE_ERRNO(signal,context,void_context);
     sigset_t unblock;
 
     sigemptyset(&unblock);
@@ -1611,7 +1658,7 @@ unblock_me_trampoline(int signal, siginfo_t *info, void *void_context)
 static void
 low_level_unblock_me_trampoline(int signal, siginfo_t *info, void *void_context)
 {
-    SAVE_ERRNO(context,void_context);
+    SAVE_ERRNO(signal,context,void_context);
     sigset_t unblock;
 
     sigemptyset(&unblock);
@@ -1624,7 +1671,7 @@ low_level_unblock_me_trampoline(int signal, siginfo_t *info, void *void_context)
 static void
 low_level_handle_now_handler(int signal, siginfo_t *info, void *void_context)
 {
-    SAVE_ERRNO(context,void_context);
+    SAVE_ERRNO(signal,context,void_context);
     (*interrupt_low_level_handlers[signal])(signal, info, context);
     RESTORE_ERRNO;
 }
index 9d17508..6920df4 100644 (file)
@@ -454,6 +454,10 @@ sub_monitor(void)
     if (!ldb_in) {
 #ifndef LISP_FEATURE_WIN32
         ldb_in = fopen("/dev/tty","r+");
+        if (ldb_in == NULL) {
+            perror("Error opening /dev/tty");
+            ldb_in = stdin;
+        }
 #else
         ldb_in = stdin;
 #endif
index 71a976e..f594d56 100644 (file)
@@ -92,6 +92,7 @@ static pthread_mutex_t create_thread_lock = PTHREAD_MUTEX_INITIALIZER;
 #ifdef LISP_FEATURE_GCC_TLS
 __thread struct thread *current_thread;
 #endif
+pthread_key_t lisp_thread = 0;
 #endif
 
 #if defined(LISP_FEATURE_X86) || defined(LISP_FEATURE_X86_64)
@@ -127,6 +128,9 @@ initial_thread_trampoline(struct thread *th)
 #if defined(LISP_FEATURE_X86) || defined(LISP_FEATURE_X86_64)
     lispobj *args = NULL;
 #endif
+#ifdef LISP_FEATURE_SB_THREAD
+    pthread_setspecific(lisp_thread, (void *)1);
+#endif
     function = th->no_tls_value_marker;
     th->no_tls_value_marker = NO_TLS_VALUE_MARKER_WIDETAG;
     if(arch_os_thread_init(th)==0) return 1;
@@ -263,6 +267,7 @@ new_thread_trampoline(struct thread *th)
     FSHOW((stderr,"/creating thread %lu\n", thread_self()));
     check_deferrables_blocked_or_lose(0);
     check_gc_signals_unblocked_or_lose(0);
+    pthread_setspecific(lisp_thread, (void *)1);
     function = th->no_tls_value_marker;
     th->no_tls_value_marker = NO_TLS_VALUE_MARKER_WIDETAG;
     if(arch_os_thread_init(th)==0) {
@@ -492,6 +497,9 @@ kern_return_t mach_thread_init(mach_port_t thread_exception_port);
 
 void create_initial_thread(lispobj initial_function) {
     struct thread *th=create_thread_struct(initial_function);
+#ifdef LISP_FEATURE_SB_THREAD
+    pthread_key_create(&lisp_thread, 0);
+#endif
     if(th) {
 #ifdef LISP_FEATURE_MACH_EXCEPTION_HANDLER
         setup_mach_exception_handling_thread();
index 1872483..dbca3db 100644 (file)
@@ -57,6 +57,7 @@ wait_for_thread_state_change(struct thread *thread, lispobj state)
 #endif
 
 extern int kill_safely(os_thread_t os_thread, int signal);
+extern void kill_a_lisp_thread(int signal);
 
 #define THREAD_SLOT_OFFSET_WORDS(c) \
  (offsetof(struct thread,c)/(sizeof (struct thread *)))
@@ -68,6 +69,7 @@ union per_thread_data {
 
 extern struct thread *all_threads;
 extern int dynamic_values_bytes;
+extern pthread_key_t lisp_thread;
 
 #if defined(LISP_FEATURE_DARWIN)
 #define CONTROL_STACK_ALIGNMENT_BYTES 8192 /* darwin wants page-aligned stacks */
diff --git a/tests/kill-non-lisp-thread.c b/tests/kill-non-lisp-thread.c
new file mode 100644 (file)
index 0000000..3af7f17
--- /dev/null
@@ -0,0 +1,19 @@
+#include <pthread.h>
+#include <signal.h>
+
+void
+wait_a_bit(void)
+{
+    sleep(5);
+}
+
+void
+kill_non_lisp_thread(void)
+{
+    pthread_t kid;
+    if (pthread_create(&kid, 0, (void *(*)(void *))wait_a_bit, 0) < 0) {
+        perror("pthread_create");
+        exit(1);
+    }
+    pthread_kill(kid, SIGPIPE);
+}
diff --git a/tests/kill-non-lisp-thread.impure.lisp b/tests/kill-non-lisp-thread.impure.lisp
new file mode 100644 (file)
index 0000000..8a4b661
--- /dev/null
@@ -0,0 +1,53 @@
+;;;; Testing signal handling in non-lisp threads.
+
+;;;; This software is part of the SBCL system. See the README file for
+;;;; more information.
+;;;;
+;;;; While most of SBCL is derived from the CMU CL system, the test
+;;;; files (like this one) were written from scratch after the fork
+;;;; from CMU CL.
+;;;;
+;;;; This software is in the public domain and is provided with
+;;;; absolutely no warranty. See the COPYING and CREDITS files for
+;;;; more information.
+
+#-sb-thread
+(sb-ext:quit :unix-status 104)
+
+(use-package :sb-alien)
+
+(defun run (program &rest arguments)
+  (let* ((proc nil)
+         (output
+          (with-output-to-string (s)
+            (setf proc (run-program program arguments
+                                    :search (not (eql #\. (char program 0)))
+                                    :output s)))))
+    (unless (zerop (process-exit-code proc))
+      (error "Bad exit code: ~S~%Output:~% ~S"
+             (process-exit-code proc)
+             output))
+    output))
+
+(run "cc" "-O3"
+     "-I" "../src/runtime/"
+     "kill-non-lisp-thread.c"
+     #+(and (or linux freebsd) (or x86-64 ppc mips)) "-fPIC"
+     #+(and x86-64 darwin) "-arch" #+(and x86-64 darwin) "x86_64"
+     #+darwin "-bundle" #-darwin "-shared"
+     "-o" "kill-non-lisp-thread.so")
+
+(load-shared-object (truename "kill-non-lisp-thread.so"))
+
+(define-alien-routine kill-non-lisp-thread void)
+
+(with-test (:name :kill-non-lisp-thread)
+  (let ((receivedp nil))
+    (push (lambda ()
+            (setq receivedp t))
+          (sb-thread::thread-interruptions sb-thread:*current-thread*))
+    (kill-non-lisp-thread)
+    (sleep 1)
+    (assert receivedp)))
+
+(delete-file "kill-non-lisp-thread.so")
index f4bcad0..94219e9 100644 (file)
@@ -17,4 +17,4 @@
 ;;; checkins which aren't released. (And occasionally for internal
 ;;; versions, especially for internal versions off the main CVS
 ;;; branch, it gets hairier, e.g. "0.pre7.14.flaky4.13".)
-"1.0.28.36"
+"1.0.28.37"