From: Gabor Melis Date: Mon, 11 May 2009 13:24:34 +0000 (+0000) Subject: 1.0.28.37: resignal signals received in foreign threads X-Git-Url: http://repo.macrolet.net/gitweb/?a=commitdiff_plain;h=9ee246f59019b776b38f6c09b2ce730cd0b32844;p=sbcl.git 1.0.28.37: resignal signals received in foreign threads 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. --- diff --git a/NEWS b/NEWS index c332724..505e518 100644 --- 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 diff --git a/doc/manual/threading.texinfo b/doc/manual/threading.texinfo index 6219f67..d1185e7 100644 --- a/doc/manual/threading.texinfo +++ b/doc/manual/threading.texinfo @@ -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) diff --git a/src/runtime/interrupt.c b/src/runtime/interrupt.c index e469d8f..4765645 100644 --- a/src/runtime/interrupt.c +++ b/src/runtime/interrupt.c @@ -68,6 +68,19 @@ #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. */ @@ -80,6 +93,54 @@ 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: * @@ -98,10 +159,11 @@ * 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]; - /* * 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; } diff --git a/src/runtime/monitor.c b/src/runtime/monitor.c index 9d17508..6920df4 100644 --- a/src/runtime/monitor.c +++ b/src/runtime/monitor.c @@ -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 diff --git a/src/runtime/thread.c b/src/runtime/thread.c index 71a976e..f594d56 100644 --- a/src/runtime/thread.c +++ b/src/runtime/thread.c @@ -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(); diff --git a/src/runtime/thread.h b/src/runtime/thread.h index 1872483..dbca3db 100644 --- a/src/runtime/thread.h +++ b/src/runtime/thread.h @@ -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 index 0000000..3af7f17 --- /dev/null +++ b/tests/kill-non-lisp-thread.c @@ -0,0 +1,19 @@ +#include +#include + +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 index 0000000..8a4b661 --- /dev/null +++ b/tests/kill-non-lisp-thread.impure.lisp @@ -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") diff --git a/version.lisp-expr b/version.lisp-expr index f4bcad0..94219e9 100644 --- a/version.lisp-expr +++ b/version.lisp-expr @@ -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"