From d8e682fdfb7e8ba067e15aea0f3d1f8d37ca9eb1 Mon Sep 17 00:00:00 2001 From: Gabor Melis Date: Sun, 19 Jun 2005 19:35:41 +0000 Subject: [PATCH] 0.9.1.59: merged most of the pthreads stuff from amd64-pthread-branch except the amd64 part, plus: * in the runtime thread_kill, thread_sigmask, thread_self stand for pthread_kill or kill, sigprocmaks or pthread_sigmask, and pthread_self or getpid respectively controlled by the sb-thread feature * fixed recursive get on session-lock that happened when a gc interrupting get-foreground reaped a thread * fixed sigint handling: removed broken (by pthread signal handling semantics) sigint enable/disable machinery in favor of sigint-%break looking up the foreground thread and interrupting it, which is itself racy :-(. * numerous fixes for interrupt-thread * threads block signals until they are set up properly * removed suspend-thread, resume-thread * destroy-thread is now equivalent to terminate-thread. --- NEWS | 8 +- contrib/sb-aclrepl/repl.lisp | 2 +- src/code/exhaust.lisp | 5 +- src/code/gc.lisp | 2 +- src/code/target-signal.lisp | 8 ++ src/code/target-thread.lisp | 85 +++++++---------- src/code/target-unithread.lisp | 3 +- src/compiler/generic/objdef.lisp | 5 +- src/runtime/Config.x86-linux | 2 +- src/runtime/arch.h | 5 + src/runtime/breakpoint.c | 6 +- src/runtime/cheneygc.c | 4 +- src/runtime/gencgc.c | 10 +- src/runtime/globals.c | 7 ++ src/runtime/globals.h | 4 + src/runtime/interr.c | 12 +-- src/runtime/interrupt.c | 70 ++++++++------ src/runtime/interrupt.h | 1 - src/runtime/linux-os.c | 2 - src/runtime/linux-os.h | 2 - src/runtime/runtime.h | 9 ++ src/runtime/thread.c | 191 +++++++++++++++++++++++--------------- src/runtime/thread.h | 20 +++- src/runtime/validate.c | 8 +- src/runtime/validate.h | 5 +- src/runtime/x86-linux-os.c | 7 +- tests/threads.impure.lisp | 65 +++++++++++-- version.lisp-expr | 2 +- 28 files changed, 343 insertions(+), 207 deletions(-) diff --git a/NEWS b/NEWS index 9b337a8..fb8d08c 100644 --- a/NEWS +++ b/NEWS @@ -1,7 +1,6 @@ changes in sbcl-0.9.2 relative to sbcl-0.9.1: * numerous signal handling fixes to increase stability * Support for EUC-JP external format. (thanks to NIIMI Satoshi) - * bug fix: interrupt-thread restores the eflags register on x86 * minor incompatible change: we now correctly canonize default initargs, making them be a list of (INITARG INITFORM INITFUNCTION) as per the MOP, rather than the historical (INITARG INITFUNCTION @@ -12,7 +11,6 @@ changes in sbcl-0.9.2 relative to sbcl-0.9.1: * TRUENAME and PROBE-FILE now correctly resolve symlinks even if the pathname is a directory pathname. * SB-SPROF now works (more) reliably on non-GENCGC platforms. - * fixed some lockups due to gc/thread interaction * dynamic space size on PPC has been increased to 768Mb. (thanks to Cyrus Harmon) * SB-MOP:ENSURE-CLASS-USING-CLASS now accepts a class as the @@ -40,6 +38,12 @@ changes in sbcl-0.9.2 relative to sbcl-0.9.1: * contrib improvement: it's harder to cause SOCKET-CLOSE to close() the wrong file descriptor; implementation of SOCKET-OPEN-P. (thanks to Tony Martinez) + * threads + ** gcing a dead thread can no longer lead to lockups + ** threads block signals until they are set up properly + ** errno is no longer shared by threads + ** interrupt-thread restores the eflags register on x86 + ** fixed some lockups due to gc/thread interaction * fixed some bugs revealed by Paul Dietz' test suite: ** invalid dotted lists no longer raise a read error when *READ-SUPPRESS* is T diff --git a/contrib/sb-aclrepl/repl.lisp b/contrib/sb-aclrepl/repl.lisp index 41c685d..e2d3f82 100644 --- a/contrib/sb-aclrepl/repl.lisp +++ b/contrib/sb-aclrepl/repl.lisp @@ -303,7 +303,7 @@ #+sb-thread (defun thread-pids () "Return a list of the pids for all threads" - (let ((offset (* 4 sb-vm::thread-pid-slot))) + (let ((offset (* 4 sb-vm::thread-os-thread-slot))) (sb-thread::mapcar-threads #'(lambda (sap) (sb-sys:sap-ref-32 sap offset))))) diff --git a/src/code/exhaust.lisp b/src/code/exhaust.lisp index 7de2f00..6724c66 100644 --- a/src/code/exhaust.lisp +++ b/src/code/exhaust.lisp @@ -13,7 +13,10 @@ (in-package "SB!KERNEL") (define-alien-routine ("protect_control_stack_guard_page" %protect-control-stack-guard-page) - sb!alien:int (thread-id sb!alien:int) (protect-p sb!alien:int)) + sb!alien:void + (thread-id #!+sb-thread sb!alien:unsigned-long + #!-sb-thread sb!alien:int) + (protect-p sb!alien:int)) (defun protect-control-stack-guard-page (n) (%protect-control-stack-guard-page (sb!thread:current-thread-id) (if n 1 0))) diff --git a/src/code/gc.lisp b/src/code/gc.lisp index f90150f..43c2b07 100644 --- a/src/code/gc.lisp +++ b/src/code/gc.lisp @@ -236,7 +236,7 @@ environment these hooks may run in any thread.") ;; of things and not a bug. (when (plusp freed) (incf *n-bytes-freed-or-purified* freed))) - (sb!thread::reap-dead-threads))) + (sb!thread::reap-dead-threads))) ;; Outside the mutex, these may cause another GC. FIXME: it can ;; potentially exceed maximum interrupt nesting by triggering ;; GCs. diff --git a/src/code/target-signal.lisp b/src/code/target-signal.lisp index f0f98d7..d358486 100644 --- a/src/code/target-signal.lisp +++ b/src/code/target-signal.lisp @@ -83,6 +83,14 @@ ;;; SIGINT in --disable-debugger mode will cleanly terminate the system ;;; (by respecting the *DEBUGGER-HOOK* established in that mode). (defun sigint-%break (format-string &rest format-arguments) + #!+sb-thread + (let ((foreground-thread (sb!thread::foreground-thread))) + (if (eql foreground-thread (sb!thread:current-thread-id)) + (apply #'%break 'sigint format-string format-arguments) + (sb!thread:interrupt-thread + foreground-thread + (lambda () (apply #'%break 'sigint format-string format-arguments))))) + #!-sb-thread (apply #'%break 'sigint format-string format-arguments)) (eval-when (:compile-toplevel :execute) diff --git a/src/code/target-thread.lisp b/src/code/target-thread.lisp index 8a42b39..afe54b7 100644 --- a/src/code/target-thread.lisp +++ b/src/code/target-thread.lisp @@ -19,10 +19,6 @@ unsigned-long (lisp-fun-address unsigned-long)) -(define-alien-routine "signal_thread_to_dequeue" - unsigned-int - (thread-id unsigned-long)) - (define-alien-routine reap-dead-threads void) (defvar *session* nil) @@ -173,12 +169,15 @@ time we reacquire LOCK and return to the caller." ;; can't use handling-end-of-the-world, because that flushes ;; output streams, and we don't necessarily have any (or we ;; could be sharing them) - (sb!sys:enable-interrupt sb!unix:sigint :ignore) (catch 'sb!impl::%end-of-the-world (with-simple-restart - (destroy-thread - (format nil "~~@" + (terminate-thread + (format nil "~~@" (current-thread-id))) + ;; now that most things have a chance to work + ;; properly without messing up other threads, it's + ;; time to enable signals + (sb!unix::reset-signal-mask) (funcall real-function)) 0)) (values)))))) @@ -187,22 +186,9 @@ time we reacquire LOCK and return to the caller." (pushnew tid (session-threads *session*))) tid)) -;;; Really, you don't want to use these: they'll get into trouble with -;;; garbage collection. Use a lock or a waitqueue instead -(defun suspend-thread (thread-id) - (sb!unix:unix-kill thread-id sb!unix:sigstop)) -(defun resume-thread (thread-id) - (sb!unix:unix-kill thread-id sb!unix:sigcont)) -;;; Note warning about cleanup forms (defun destroy-thread (thread-id) - "Destroy the thread identified by THREAD-ID abruptly, without running cleanup forms" - (sb!unix:unix-kill thread-id sb!unix:sigterm) - ;; may have been stopped for some reason, so now wake it up to - ;; deliver the TERM - (sb!unix:unix-kill thread-id sb!unix:sigcont)) - - - + "Deprecated. Soon to be removed or reimplemented using pthread_cancel." + (terminate-thread thread-id)) ;;; a moderate degree of care is expected for use of interrupt-thread, ;;; due to its nature: if you interrupt a thread that was holding @@ -222,18 +208,14 @@ time we reacquire LOCK and return to the caller." (defun interrupt-thread (thread function) "Interrupt THREAD and make it run FUNCTION." (let ((function (coerce function 'function))) - ;; FIXME: FUNCTION is pinned only for the signalling of the - ;; SIG_INTERRUPT_THREAD signal. - (sb!sys:with-pinned-objects - (function) - (multiple-value-bind (res err) - (sb!unix::syscall ("interrupt_thread" - sb!alien:unsigned-long sb!alien:unsigned-long) - thread - thread - (sb!kernel:get-lisp-obj-address function)) - (unless res - (error 'interrupt-thread-error :thread thread :errno err)))))) + (multiple-value-bind (res err) + (sb!unix::syscall ("interrupt_thread" + sb!alien:unsigned-long sb!alien:unsigned-long) + thread + thread + (sb!kernel:get-lisp-obj-address function)) + (unless res + (error 'interrupt-thread-error :thread thread :errno err))))) (defun terminate-thread (thread-id) @@ -243,11 +225,8 @@ SB-EXT:QUIT - the usual cleanup forms will be evaluated" (declaim (inline current-thread-id)) (defun current-thread-id () - (logand - (sb!sys:sap-int - (sb!vm::current-thread-offset-sap sb!vm::thread-pid-slot)) - ;; KLUDGE pids are 16 bit really. Avoid boxing the return value - (1- (ash 1 16)))) + (sb!sys:sap-int + (sb!vm::current-thread-offset-sap sb!vm::thread-os-thread-slot))) ;;;; iterate over the in-memory threads @@ -264,8 +243,9 @@ SB-EXT:QUIT - the usual cleanup forms will be evaluated" (let ((thread (alien-sap (extern-alien "all_threads" (* t))))) (loop (when (sb!sys:sap= thread (sb!sys:int-sap 0)) (return nil)) + ;; FIXME: 32/64 bit (let ((pid (sb!sys:sap-ref-32 thread (* sb!vm:n-word-bytes - sb!vm::thread-pid-slot)))) + sb!vm::thread-os-thread-slot)))) (when (= pid id) (return thread)) (setf thread (sb!sys:sap-ref-sap thread (* sb!vm:n-word-bytes sb!vm::thread-next-slot))))))) @@ -287,7 +267,7 @@ SB-EXT:QUIT - the usual cleanup forms will be evaluated" ;;;; job control, independent listeners (defstruct session - (lock (make-mutex)) + (lock (make-mutex :name "session lock")) (threads nil) (interactive-threads nil) (interactive-threads-queue (make-waitqueue))) @@ -318,17 +298,16 @@ SB-EXT:QUIT - the usual cleanup forms will be evaluated" (call-with-new-session (function ,fb-name))))) ;;; Remove thread id TID from its session, if it has one. This is -;;; called from C reap_dead_threads() so is run in the context of -;;; whichever thread called that (usually after a GC), which may not have -;;; any meaningful parent/child/sibling relationship with the dead thread +;;; called from C mark_thread_dead(). (defun handle-thread-exit (tid) - (let ((session (symbol-value-in-thread '*session* tid))) - (and session (%delete-thread-from-session tid session)))) - + (when *session* + (%delete-thread-from-session tid *session*))) + (defun terminate-session () "Kill all threads in session except for this one. Does nothing if current thread is not the foreground thread" (reap-dead-threads) + ;; FIXME: threads created in other threads may escape termination (let* ((tid (current-thread-id)) (to-kill (with-mutex ((session-lock *session*)) @@ -337,7 +316,11 @@ thread is not the foreground thread" ;; do the kill after dropping the mutex; unwind forms in dying ;; threads may want to do session things (dolist (p to-kill) - (unless (eql p tid) (terminate-thread p))))) + (unless (eql p tid) + ;; terminate the thread but don't be surprised if it has + ;; exited in the meantime + (handler-case (terminate-thread p) + (interrupt-thread-error ())))))) ;;; called from top of invoke-debugger (defun debugger-wait-until-foreground-thread (stream) @@ -360,7 +343,6 @@ interactive." (when (eql (car int-t) tid) (unless was-foreground (format *query-io* "Resuming thread ~A~%" tid)) - (sb!sys:enable-interrupt sb!unix:sigint #'sb!unix::sigint-handler) (return-from get-foreground t)) (setf was-foreground nil) (unless (member tid int-t) @@ -376,13 +358,15 @@ interactive." (let ((tid (current-thread-id))) (setf (session-interactive-threads *session*) (delete tid (session-interactive-threads *session*))) - (sb!sys:enable-interrupt sb!unix:sigint :ignore) (when next (setf (session-interactive-threads *session*) (list* next (delete next (session-interactive-threads *session*))))) (condition-broadcast (session-interactive-threads-queue *session*))))) +(defun foreground-thread () + (car (session-interactive-threads *session*))) + (defun make-listener-thread (tty-name) (assert (probe-file tty-name)) (let* ((in (sb!unix:unix-open (namestring tty-name) sb!unix:o_rdwr #o666)) @@ -400,7 +384,6 @@ interactive." (sb!sys:make-fd-stream err :input t :output t :buffering :line :dual-channel-p t)) (sb!impl::*descriptor-handlers* nil)) (with-new-session () - (sb!sys:enable-interrupt sb!unix:sigint #'sb!unix::sigint-handler) (unwind-protect (sb!impl::toplevel-repl nil) (sb!int:flush-standard-output-streams)))))) diff --git a/src/code/target-unithread.lisp b/src/code/target-unithread.lisp index e161937..3005dee 100644 --- a/src/code/target-unithread.lisp +++ b/src/code/target-unithread.lisp @@ -19,8 +19,9 @@ (* n sb!vm:n-word-bytes))) (defun current-thread-id () + ;; FIXME: 32/64 (sb!sys:sap-ref-32 (alien-sap (extern-alien "all_threads" (* t))) - (* sb!vm::thread-pid-slot sb!vm:n-word-bytes))) + (* sb!vm::thread-os-thread-slot sb!vm:n-word-bytes))) (defun reap-dead-threads ()) diff --git a/src/compiler/generic/objdef.lisp b/src/compiler/generic/objdef.lisp index 760f8ca..eeab36c 100644 --- a/src/compiler/generic/objdef.lisp +++ b/src/compiler/generic/objdef.lisp @@ -365,7 +365,7 @@ ;; unbound_marker is borrowed very briefly at thread startup to ;; pass the address of initial-function into new_thread_trampoline (unbound-marker :init :unbound) ; tls[0] = UNBOUND_MARKER_WIDETAG - (pid :c-type "pid_t") + (os-thread :c-type "os_thread_t") (binding-stack-start :c-type "lispobj *" :length #!+alpha 2 #!-alpha 1) (binding-stack-pointer :c-type "lispobj *" :length #!+alpha 2 #!-alpha 1) (control-stack-start :c-type "lispobj *" :length #!+alpha 2 #!-alpha 1) @@ -375,10 +375,13 @@ #!+gencgc (alloc-region :c-type "struct alloc_region" :length 5) (tls-cookie) ; on x86, the LDT index (this :c-type "struct thread *" :length #!+alpha 2 #!-alpha 1) + (prev :c-type "struct thread *" :length #!+alpha 2 #!-alpha 1) (next :c-type "struct thread *" :length #!+alpha 2 #!-alpha 1) (state) ; running, stopping, stopped, dead #!+(or x86 x86-64) (pseudo-atomic-atomic) #!+(or x86 x86-64) (pseudo-atomic-interrupted) + (interrupt-fun) + (interrupt-fun-lock) (interrupt-data :c-type "struct interrupt_data *" :length #!+alpha 2 #!-alpha 1) (interrupt-contexts :c-type "os_context_t *" :rest-p t)) diff --git a/src/runtime/Config.x86-linux b/src/runtime/Config.x86-linux index e4a1f74..80ba64e 100644 --- a/src/runtime/Config.x86-linux +++ b/src/runtime/Config.x86-linux @@ -28,7 +28,7 @@ OS_SRC = linux-os.c x86-linux-os.c # interface, though.:-| As far as I (WHN 2002-05-19) know, no one is # working on one and it would be a nice thing to have.) LINKFLAGS += -Wl,--export-dynamic -OS_LIBS = -ldl +OS_LIBS = -lpthread -ldl GC_SRC = gencgc.c diff --git a/src/runtime/arch.h b/src/runtime/arch.h index 7e745ab..ade8d92 100644 --- a/src/runtime/arch.h +++ b/src/runtime/arch.h @@ -14,6 +14,7 @@ #include "os.h" #include "signal.h" +#include "thread.h" /* Do anything we need to do when starting up the runtime environment * on this architecture. */ @@ -30,6 +31,10 @@ extern void arch_remove_breakpoint(void *pc, unsigned long orig_inst); extern void arch_install_interrupt_handlers(void); extern void arch_do_displaced_inst(os_context_t *context, unsigned int orig_inst); + +extern int arch_os_thread_init(struct thread *thread); +extern int arch_os_thread_cleanup(struct thread *thread); + extern lispobj funcall0(lispobj function); extern lispobj funcall1(lispobj function, lispobj arg0); extern lispobj funcall2(lispobj function, lispobj arg0, lispobj arg1); diff --git a/src/runtime/breakpoint.c b/src/runtime/breakpoint.c index c90bd0e..6bdd249 100644 --- a/src/runtime/breakpoint.c +++ b/src/runtime/breakpoint.c @@ -142,7 +142,7 @@ void handle_breakpoint(int signal, siginfo_t* info, os_context_t *context) /* Don't disallow recursive breakpoint traps. Otherwise, we can't * use debugger breakpoints anywhere in here. */ - sigprocmask(SIG_SETMASK, os_context_sigmask_addr(context), 0); + thread_sigmask(SIG_SETMASK, os_context_sigmask_addr(context), 0); funcall3(SymbolFunction(HANDLE_BREAKPOINT), compute_offset(context, code), @@ -166,7 +166,7 @@ void *handle_fun_end_breakpoint(int signal, siginfo_t *info, /* Don't disallow recursive breakpoint traps. Otherwise, we can't * use debugger breakpoints anywhere in here. */ - sigprocmask(SIG_SETMASK, os_context_sigmask_addr(context), 0); + thread_sigmask(SIG_SETMASK, os_context_sigmask_addr(context), 0); funcall3(SymbolFunction(HANDLE_BREAKPOINT), compute_offset(context, code), @@ -196,7 +196,7 @@ void *handle_fun_end_breakpoint(int signal, siginfo_t *info, /* Don't disallow recursive breakpoint traps. Otherwise, we can't * use debugger breakpoints anywhere in here. */ - sigprocmask(SIG_SETMASK, os_context_sigmask_addr(context), 0); + thread_sigmask(SIG_SETMASK, os_context_sigmask_addr(context), 0); funcall3(SymbolFunction(HANDLE_BREAKPOINT), compute_offset(context, code), diff --git a/src/runtime/cheneygc.c b/src/runtime/cheneygc.c index 4948da8..412183d 100644 --- a/src/runtime/cheneygc.c +++ b/src/runtime/cheneygc.c @@ -140,7 +140,7 @@ collect_garbage(unsigned ignore) * from a signal handler (e.g. with the sigsegv gc_trigger stuff) */ sigemptyset(&tmp); sigaddset_blockable(&tmp); - sigprocmask(SIG_BLOCK, &tmp, &old); + thread_sigmask(SIG_BLOCK, &tmp, &old); current_static_space_free_pointer = (lispobj *) ((unsigned long) @@ -259,7 +259,7 @@ collect_garbage(unsigned ignore) #endif zero_stack(); set_auto_gc_trigger(size_retained+bytes_consed_between_gcs); - sigprocmask(SIG_SETMASK, &old, 0); + thread_sigmask(SIG_SETMASK, &old, 0); #ifdef PRINTNOISE diff --git a/src/runtime/gencgc.c b/src/runtime/gencgc.c index 6f59ffe..0c0c98f 100644 --- a/src/runtime/gencgc.c +++ b/src/runtime/gencgc.c @@ -428,7 +428,7 @@ print_generation_stats(int verbose) /* FIXME: should take FILE argument */ * e.g. boxed/unboxed, generation, ages; there may need to be many * allocation regions. * - * Each allocation region may be start within a partly used page. Many + * Each allocation region may start within a partly used page. Many * features of memory use are noted on a page wise basis, e.g. the * generation; so if a region starts within an existing allocated page * it must be consistent with this page. @@ -4113,8 +4113,8 @@ alloc(long nbytes) #ifdef LISP_FEATURE_SB_THREAD if(!SymbolValue(PSEUDO_ATOMIC_ATOMIC,th)) { register u32 fs; - fprintf(stderr, "fatal error in thread 0x%x, pid=%d\n", - th,getpid()); + fprintf(stderr, "fatal error in thread 0x%x, tid=%ld\n", + th,th->os_thread); __asm__("movl %fs,%0" : "=r" (fs) : ); fprintf(stderr, "fs is %x, th->tls_cookie=%x \n", debug_get_fs(),th->tls_cookie); @@ -4152,7 +4152,7 @@ alloc(long nbytes) sigset_t new_mask,old_mask; sigemptyset(&new_mask); sigaddset_blockable(&new_mask); - sigprocmask(SIG_BLOCK,&new_mask,&old_mask); + thread_sigmask(SIG_BLOCK,&new_mask,&old_mask); if((!data->pending_handler) && maybe_defer_handler(interrupt_maybe_gc_int,data,0,0,0)) { @@ -4162,7 +4162,7 @@ alloc(long nbytes) sigcopyset(&(data->pending_mask),&old_mask); SetSymbolValue(NEED_TO_COLLECT_GARBAGE,T,thread); } else { - sigprocmask(SIG_SETMASK,&old_mask,0); + thread_sigmask(SIG_SETMASK,&old_mask,0); } } } diff --git a/src/runtime/globals.c b/src/runtime/globals.c index 5cd53b2..9c1010e 100644 --- a/src/runtime/globals.c +++ b/src/runtime/globals.c @@ -50,6 +50,10 @@ boolean stop_the_world=0; * is done). For the GENCGC, it always points to DYNAMIC_SPACE_START. */ lispobj *current_dynamic_space; +#if defined(LISP_FEATURE_SB_THREAD) +pthread_key_t specials=0; +#endif + void globals_init(void) { /* Space, stack, and free pointer vars are initialized by @@ -63,4 +67,7 @@ void globals_init(void) /* Set foreign function call active. */ foreign_function_call_active = 1; +#if defined(LISP_FEATURE_SB_THREAD) + pthread_key_create(&specials,0); +#endif } diff --git a/src/runtime/globals.h b/src/runtime/globals.h index 03e991b..7859014 100644 --- a/src/runtime/globals.h +++ b/src/runtime/globals.h @@ -24,6 +24,10 @@ extern int foreign_function_call_active; extern boolean stop_the_world; +#if defined(LISP_FEATURE_SB_THREAD) +extern pthread_key_t specials; +#endif + extern lispobj *current_control_stack_pointer; extern lispobj *current_control_frame_pointer; # if !defined(LISP_FEATURE_X86) && !defined(LISP_FEATURE_X86_64) diff --git a/src/runtime/interr.c b/src/runtime/interr.c index fc66ac9..8d3b3b0 100644 --- a/src/runtime/interr.c +++ b/src/runtime/interr.c @@ -48,15 +48,9 @@ lose(char *fmt, ...) { va_list ap; fprintf(stderr, "fatal error encountered in SBCL pid %d",getpid()); - /* freeze all the other threads, so we have a chance of debugging them - */ - if(all_threads) { - struct thread *th1,*th=arch_os_get_current_thread(); - for_each_thread(th1) { - if(th1!=th) kill(th1->pid,SIGSTOP); - } - } - +#if defined(LISP_FEATURE_SB_THREAD) + fprintf(stderr, "(tid %ld)",thread_self()); +#endif if (fmt) { fprintf(stderr, ":\n"); va_start(ap, fmt); diff --git a/src/runtime/interrupt.c b/src/runtime/interrupt.c index aa2a23f..ac1abcf 100644 --- a/src/runtime/interrupt.c +++ b/src/runtime/interrupt.c @@ -47,6 +47,7 @@ #include #include #include +#include #include "sbcl.h" #include "runtime.h" @@ -63,6 +64,7 @@ #include "interr.h" #include "genesis/fdefn.h" #include "genesis/simple-fun.h" +#include "genesis/cons.h" @@ -108,7 +110,7 @@ inline static void check_blockables_blocked_or_lose() sigset_t empty,current; int i; sigemptyset(&empty); - sigprocmask(SIG_BLOCK, &empty, ¤t); + thread_sigmask(SIG_BLOCK, &empty, ¤t); for(i=0;ipid)); + "/maybe_defer_handler(%x,%d),thread=%ld: deferred\n", + (unsigned int)handler,signal,thread->os_thread)); #endif return 1; } @@ -522,15 +524,15 @@ maybe_defer_handler(void *handler, struct interrupt_data *data, arch_set_pseudo_atomic_interrupted(context); #ifdef QSHOW_SIGNALS FSHOW((stderr, - "/maybe_defer_handler(%x,%d),thread=%d: deferred(PA)\n", - (unsigned int)handler,signal,thread->pid)); + "/maybe_defer_handler(%x,%d),thread=%ld: deferred(PA)\n", + (unsigned int)handler,signal,thread->os_thread)); #endif return 1; } #ifdef QSHOW_SIGNALS FSHOW((stderr, - "/maybe_defer_handler(%x,%d),thread=%d: not deferred\n", - (unsigned int)handler,signal,thread->pid)); + "/maybe_defer_handler(%x,%d),thread=%ld: not deferred\n", + (unsigned int)handler,signal,thread->os_thread)); #endif return 0; } @@ -631,13 +633,12 @@ sig_stop_for_gc_handler(int signal, siginfo_t *info, void *void_context) sigemptyset(&ss); for(i=1;istate!=STATE_STOPPING) { lose("sig_stop_for_gc_handler: wrong thread state: %ld\n", fixnum_value(thread->state)); @@ -707,6 +708,7 @@ void arrange_return_to_lisp_function(os_context_t *context, lispobj function) * user's backtrace makes (as much) sense (as usual) */ /* FIXME: what about restoring fp state? */ + /* FIXME: what about restoring errno? */ #ifdef LISP_FEATURE_X86 /* Suppose the existence of some function that saved all * registers, called call_into_lisp, then restored GP registers and @@ -714,13 +716,15 @@ void arrange_return_to_lisp_function(os_context_t *context, lispobj function) push ebp mov ebp esp - pushad + pushfl + pushal push $0 push $0 pushl {address of function to call} call 0x8058db0 addl $12,%esp - popa + popal + popfl leave ret @@ -826,14 +830,22 @@ void arrange_return_to_lisp_function(os_context_t *context, lispobj function) void interrupt_thread_handler(int num, siginfo_t *info, void *v_context) { os_context_t *context = (os_context_t*)arch_os_get_context(&v_context); - arrange_return_to_lisp_function(context,info->si_value.sival_int); + /* The order of interrupt execution is peculiar. If thread A + * interrupts thread B with I1, I2 and B for some reason recieves + * I1 when FUN2 is already on the list, then it is FUN2 that gets + * to run first. But when FUN2 is run SIG_INTERRUPT_THREAD is + * enabled again and I2 hits pretty soon in FUN2 and run + * FUN1. This is of course just one scenario, and the order of + * thread interrupt execution is undefined. */ + struct thread *th=arch_os_get_current_thread(); + struct cons *c; + get_spinlock(&th->interrupt_fun_lock,(long)th); + c=((struct cons *)native_pointer(th->interrupt_fun)); + arrange_return_to_lisp_function(context,c->car); + th->interrupt_fun=(lispobj *)(c->cdr); + release_spinlock(&th->interrupt_fun_lock); } -void thread_exit_handler(int num, siginfo_t *info, void *v_context) -{ /* called when a child thread exits */ - mark_dead_threads(); -} - #endif /* KLUDGE: Theoretically the approach we use for undefined alien @@ -857,8 +869,8 @@ boolean handle_guard_page_triggered(os_context_t *context,void *addr){ * protection so the error handler has some headroom, protect the * previous page so that we can catch returns from the guard page * and restore it. */ - protect_control_stack_guard_page(th->pid,0); - protect_control_stack_return_guard_page(th->pid,1); + protect_control_stack_guard_page(th->os_thread,0); + protect_control_stack_return_guard_page(th->os_thread,1); arrange_return_to_lisp_function (context, SymbolFunction(CONTROL_STACK_EXHAUSTED_ERROR)); @@ -870,8 +882,8 @@ boolean handle_guard_page_triggered(os_context_t *context,void *addr){ * unprotect this one. This works even if we somehow missed * the return-guard-page, and hit it on our way to new * exhaustion instead. */ - protect_control_stack_guard_page(th->pid,1); - protect_control_stack_return_guard_page(th->pid,0); + protect_control_stack_guard_page(th->os_thread,1); + protect_control_stack_return_guard_page(th->os_thread,0); return 1; } else if (addr >= undefined_alien_address && @@ -932,7 +944,7 @@ interrupt_maybe_gc_int(int signal, siginfo_t *info, void *void_context) /* restore the signal mask from the interrupted context before * calling into Lisp */ if (context) - sigprocmask(SIG_SETMASK, os_context_sigmask_addr(context), 0); + thread_sigmask(SIG_SETMASK, os_context_sigmask_addr(context), 0); funcall0(SymbolFunction(SUB_GC)); @@ -997,7 +1009,7 @@ install_handler(int signal, void handler(int, siginfo_t*, void*)) sigemptyset(&new); sigaddset(&new, signal); - sigprocmask(SIG_BLOCK, &new, &old); + thread_sigmask(SIG_BLOCK, &new, &old); sigemptyset(&new); sigaddset_blockable(&new); @@ -1023,7 +1035,7 @@ install_handler(int signal, void handler(int, siginfo_t*, void*)) oldhandler = data->interrupt_handlers[signal]; data->interrupt_handlers[signal].c = handler; - sigprocmask(SIG_SETMASK, &old, 0); + thread_sigmask(SIG_SETMASK, &old, 0); FSHOW((stderr, "/leaving POSIX install_handler(%d, ..)\n", signal)); diff --git a/src/runtime/interrupt.h b/src/runtime/interrupt.h index 1730ac8..acf80ac 100644 --- a/src/runtime/interrupt.h +++ b/src/runtime/interrupt.h @@ -80,7 +80,6 @@ extern void do_pending_interrupt(void); #ifdef LISP_FEATURE_SB_THREAD extern void interrupt_thread_handler(int, siginfo_t*, void*); extern void sig_stop_for_gc_handler(int, siginfo_t*, void*); -extern void thread_exit_handler(int, siginfo_t*, void*); #endif extern void undoably_install_low_level_interrupt_handler (int signal, void diff --git a/src/runtime/linux-os.c b/src/runtime/linux-os.c index 5233c1d..108bf74 100644 --- a/src/runtime/linux-os.c +++ b/src/runtime/linux-os.c @@ -273,8 +273,6 @@ os_install_interrupt_handlers(void) interrupt_thread_handler); undoably_install_low_level_interrupt_handler(SIG_STOP_FOR_GC, sig_stop_for_gc_handler); - undoably_install_low_level_interrupt_handler(SIG_THREAD_EXIT, - thread_exit_handler); #endif } diff --git a/src/runtime/linux-os.h b/src/runtime/linux-os.h index d0f1781..411ade6 100644 --- a/src/runtime/linux-os.h +++ b/src/runtime/linux-os.h @@ -40,6 +40,4 @@ typedef int os_vm_prot_t; #define SIG_INTERRUPT_THREAD (SIGRTMIN) #define SIG_STOP_FOR_GC (SIGRTMIN+1) -#define SIG_DEQUEUE (SIGRTMIN+2) -#define SIG_THREAD_EXIT (SIGRTMIN+3) diff --git a/src/runtime/runtime.h b/src/runtime/runtime.h index 69560b8..e208a52 100644 --- a/src/runtime/runtime.h +++ b/src/runtime/runtime.h @@ -53,6 +53,15 @@ typedef signed int s32; /* this is an integral type the same length as a machine pointer */ typedef unsigned long pointer_sized_uint_t ; +#include + +#if defined(LISP_FEATURE_SB_THREAD) +#include +typedef pthread_t os_thread_t; +#else +typedef pid_t os_thread_t; +#endif + /* FIXME: we do things this way because of the alpha32 port. once alpha64 has arrived, all this nastiness can go away */ #if 64 == N_WORD_BITS diff --git a/src/runtime/thread.c b/src/runtime/thread.c index 759a1c6..a6b462f 100644 --- a/src/runtime/thread.c +++ b/src/runtime/thread.c @@ -11,6 +11,7 @@ #include "sbcl.h" #include "runtime.h" #include "validate.h" /* for CONTROL_STACK_SIZE etc */ +#include "alloc.h" #include "thread.h" #include "arch.h" #include "target-arch-os.h" @@ -43,7 +44,7 @@ initial_thread_trampoline(struct thread *th) th->unbound_marker = UNBOUND_MARKER_WIDETAG; if(arch_os_thread_init(th)==0) return 1; - if(th->pid < 1) lose("th->pid not set up right"); + if(th->os_thread < 1) lose("th->os_thread not set up right"); th->state=STATE_RUNNING; #if defined(LISP_FEATURE_X86) || defined(LISP_FEATURE_X86_64) return call_into_lisp_first_time(function,args,0); @@ -52,26 +53,38 @@ initial_thread_trampoline(struct thread *th) #endif } -/* this is the first thing that clone() runs in the child (which is - * why the silly calling convention). Basically it calls the user's - * requested lisp function after doing arch_os_thread_init and - * whatever other bookkeeping needs to be done - */ - #ifdef LISP_FEATURE_SB_THREAD +void mark_thread_dead(struct thread *th) { + funcall1(SymbolFunction(HANDLE_THREAD_EXIT),alloc_number(th->os_thread)); + /* I hope it's safe for a thread to detach itself inside a + * cancellation cleanup */ + pthread_detach(th->os_thread); + th->state=STATE_DEAD; + /* FIXME: if gc hits here it will rip the stack from under us */ +} + +/* this is the first thing that runs in the child (which is why the + * silly calling convention). Basically it calls the user's requested + * lisp function after doing arch_os_thread_init and whatever other + * bookkeeping needs to be done + */ int new_thread_trampoline(struct thread *th) { - lispobj function; + lispobj function,ret; function = th->unbound_marker; th->unbound_marker = UNBOUND_MARKER_WIDETAG; + pthread_cleanup_push((void (*) (void *))mark_thread_dead,th); if(arch_os_thread_init(th)==0) return 1; /* wait here until our thread is linked into all_threads: see below */ - while(th->pid<1) sched_yield(); + while(th->os_thread<1) sched_yield(); th->state=STATE_RUNNING; - return funcall0(function); + ret = funcall0(function); + /* execute cleanup */ + pthread_cleanup_pop(1); + return ret; } #endif /* LISP_FEATURE_SB_THREAD */ @@ -142,7 +155,9 @@ struct thread * create_thread_struct(lispobj initial_function) { (lispobj*)((void*)th->binding_stack_start+BINDING_STACK_SIZE); th->binding_stack_pointer=th->binding_stack_start; th->this=th; - th->pid=0; + th->os_thread=0; + th->interrupt_fun=NIL; + th->interrupt_fun_lock=0; th->state=STATE_STARTING; #ifdef LISP_FEATURE_STACK_GROWS_DOWNWARD_NOT_UPWARD th->alien_stack_pointer=((void *)th->alien_stack_start @@ -184,8 +199,8 @@ struct thread * create_thread_struct(lispobj initial_function) { bind_variable(INTERRUPT_PENDING, NIL,th); bind_variable(INTERRUPTS_ENABLED,T,th); - th->interrupt_data = - os_validate(0,(sizeof (struct interrupt_data))); + th->interrupt_data = (struct interrupt_data *) + os_validate(0,(sizeof (struct interrupt_data))); if(all_threads) memcpy(th->interrupt_data, arch_os_get_current_thread()->interrupt_data, @@ -198,39 +213,44 @@ struct thread * create_thread_struct(lispobj initial_function) { return th; } -void link_thread(struct thread *th,pid_t kid_pid) +void link_thread(struct thread *th,os_thread_t kid_tid) { sigset_t newset,oldset; sigemptyset(&newset); sigaddset_blockable(&newset); - sigprocmask(SIG_BLOCK, &newset, &oldset); + thread_sigmask(SIG_BLOCK, &newset, &oldset); - get_spinlock(&all_threads_lock,kid_pid); + get_spinlock(&all_threads_lock,kid_tid); + if (all_threads) all_threads->prev=th; th->next=all_threads; + th->prev=0; all_threads=th; - /* note that th->pid is 0 at this time. We rely on all_threads_lock - * to ensure that we don't have >1 thread with pid=0 on the list at once + /* note that th->os_thread is 0 at this time. We rely on + * all_threads_lock to ensure that we don't have >1 thread with + * os_thread=0 on the list at once */ - protect_control_stack_guard_page(th->pid,1); - th->pid=kid_pid; /* child will not start until this is set */ + protect_control_stack_guard_page(th->os_thread,1); + /* child will not start until this is set */ + th->os_thread=kid_tid; release_spinlock(&all_threads_lock); - sigprocmask(SIG_SETMASK,&oldset,0); + thread_sigmask(SIG_SETMASK,&oldset,0); } void create_initial_thread(lispobj initial_function) { struct thread *th=create_thread_struct(initial_function); - pid_t kid_pid=getpid(); - if(th && kid_pid>0) { - link_thread(th,kid_pid); + os_thread_t kid_tid=thread_self(); + if(th && kid_tid>0) { + link_thread(th,kid_tid); initial_thread_trampoline(all_threads); /* no return */ } else lose("can't create initial thread"); } #ifdef LISP_FEATURE_SB_THREAD -pid_t create_thread(lispobj initial_function) { +os_thread_t create_thread(lispobj initial_function) { struct thread *th; - pid_t kid_pid=0; + os_thread_t kid_tid=0; + pthread_attr_t attr; if(linux_no_threads_p) return 0; th=create_thread_struct(initial_function); @@ -238,25 +258,34 @@ pid_t create_thread(lispobj initial_function) { #ifdef QSHOW_SIGNALS SHOW("create_thread:waiting on lock"); #endif - get_spinlock(&thread_start_lock,arch_os_get_current_thread()->pid); + get_spinlock(&thread_start_lock,arch_os_get_current_thread()->os_thread); #ifdef QSHOW_SIGNALS SHOW("create_thread:got lock"); #endif - kid_pid=clone(new_thread_trampoline, - (((void*)th->control_stack_start)+ - THREAD_CONTROL_STACK_SIZE-16), - CLONE_FILES|SIG_THREAD_EXIT|CLONE_VM,th); - - if(kid_pid>0) { - link_thread(th,kid_pid); - /* wait here until our thread is started: see new_thread_trampoline */ - while(th->state==STATE_STARTING) sched_yield(); + /* The new thread inherits the restrictive signal mask set here, + * and enables signals again when it is set up properly. */ + { + sigset_t newset,oldset; + sigemptyset(&newset); + sigaddset_blockable(&newset); + thread_sigmask(SIG_BLOCK, &newset, &oldset); + if((pthread_attr_init(&attr)) || + (pthread_attr_setstack(&attr,th->control_stack_start, + THREAD_CONTROL_STACK_SIZE-16)) || + (pthread_create + (&kid_tid,&attr,(void *(*)(void *))new_thread_trampoline,th))) + kid_tid=0; + thread_sigmask(SIG_SETMASK,&oldset,0); + } + if(kid_tid>0) { + link_thread(th,kid_tid); /* it's started and initialized, it's safe to gc */ release_spinlock(&thread_start_lock); #ifdef QSHOW_SIGNALS SHOW("create_thread:released lock"); #endif - return th->pid; + /* by now the kid might have already exited */ + return kid_tid; } else { release_spinlock(&thread_start_lock); #ifdef QSHOW_SIGNALS @@ -272,11 +301,11 @@ pid_t create_thread(lispobj initial_function) { } #endif -struct thread *find_thread_by_pid(pid_t pid) +struct thread *find_thread_by_os_thread(os_thread_t tid) { struct thread *th; for_each_thread(th) - if(th->pid==pid) return th; + if(th->os_thread==tid) return th; return 0; } @@ -284,20 +313,6 @@ struct thread *find_thread_by_pid(pid_t pid) /* This is not needed unless #+SB-THREAD, as there's a trivial null * unithread definition. */ -void mark_dead_threads() -{ - pid_t kid; - int status; - while(1) { - kid=waitpid(-1,&status,__WALL|WNOHANG); - if(kid<=0) break; - if(WIFEXITED(status) || WIFSIGNALED(status)) { - struct thread *th=find_thread_by_pid(kid); - if(th) th->state=STATE_DEAD; - } - } -} - void reap_dead_threads() { struct thread *th,*next,*prev=0; @@ -305,11 +320,10 @@ void reap_dead_threads() while(th) { next=th->next; if(th->state==STATE_DEAD) { - funcall1(SymbolFunction(HANDLE_THREAD_EXIT),make_fixnum(th->pid)); #ifdef LISP_FEATURE_GENCGC gc_alloc_update_page_tables(0, &th->alloc_region); #endif - get_spinlock(&all_threads_lock,th->pid); + get_spinlock(&all_threads_lock,th->os_thread); if(prev) prev->next=next; else all_threads=next; release_spinlock(&all_threads_lock); @@ -325,23 +339,46 @@ void reap_dead_threads() } } -int interrupt_thread(pid_t pid, lispobj function) +int interrupt_thread(os_thread_t tid, lispobj function) { - union sigval sigval; struct thread *th; - sigval.sival_int=function; for_each_thread(th) - if((th->pid==pid) && (th->state != STATE_DEAD)) - return sigqueue(pid, SIG_INTERRUPT_THREAD, sigval); + if((th->os_thread==tid) && (th->state != STATE_DEAD)) { + /* In clone_threads, if A and B both interrupt C at approximately + * the same time, it does not matter: the second signal will be + * masked until the handler has returned from the first one. + * In pthreads though, we can't put the knowledge of what function + * to call into the siginfo, so we have to store it in the + * destination thread, and do it in such a way that A won't + * clobber B's interrupt. Hence this stupid linked list. + * + * This does depend on SIG_INTERRUPT_THREAD being queued + * (as POSIX RT signals are): we need to keep + * interrupt_fun data for exactly as many signals as are + * going to be received by the destination thread. + */ + struct cons *c; + int kill_status; + /* mask the signals in case this thread is being interrupted */ + sigset_t newset,oldset; + sigemptyset(&newset); + sigaddset_blockable(&newset); + thread_sigmask(SIG_BLOCK, &newset, &oldset); + + get_spinlock(&th->interrupt_fun_lock, + (int)arch_os_get_current_thread()); + kill_status=thread_kill(th->os_thread,SIG_INTERRUPT_THREAD); + if(kill_status==0) { + c=alloc_cons(function,th->interrupt_fun); + th->interrupt_fun=c; + } + release_spinlock(&th->interrupt_fun_lock); + thread_sigmask(SIG_SETMASK,&oldset,0); + return (kill_status ? -1 : 0); + } errno=EPERM; return -1; } -int signal_thread_to_dequeue (pid_t pid) -{ - return kill (pid, SIG_DEQUEUE); -} - - /* stopping the world is a two-stage process. From this thread we signal * all the others with SIG_STOP_FOR_GC. The handler for this signal does * the usual pseudo-atomic checks (we don't want to stop a thread while @@ -350,23 +387,23 @@ int signal_thread_to_dequeue (pid_t pid) void gc_stop_the_world() { + struct thread *p,*th=arch_os_get_current_thread(); #ifdef QSHOW_SIGNALS SHOW("gc_stop_the_world:begin"); #endif - struct thread *p,*th=arch_os_get_current_thread(); /* keep threads from starting while the world is stopped. */ - get_spinlock(&thread_start_lock,th->pid); + get_spinlock(&thread_start_lock,th->os_thread); #ifdef QSHOW_SIGNALS SHOW("gc_stop_the_world:locked"); #endif /* stop all other threads by sending them SIG_STOP_FOR_GC */ for(p=all_threads; p; p=p->next) { - if((p!=th) && (p->pid!=0) && (p->state==STATE_RUNNING)) { + while(p->state==STATE_STARTING) sched_yield(); + if((p!=th) && (p->os_thread!=0) && (p->state==STATE_RUNNING)) { p->state=STATE_STOPPING; - if(kill(p->pid,SIG_STOP_FOR_GC)==-1) { - /* we can't kill the process; assume because it - * died already (and its parent is dead so never - * saw the SIGCHLD) */ + if(thread_kill(p->os_thread,SIG_STOP_FOR_GC)==-1) { + /* FIXME: we can't kill the thread; assume because it died + * already */ p->state=STATE_DEAD; } } @@ -376,7 +413,7 @@ void gc_stop_the_world() #endif /* wait for the running threads to stop */ for(p=all_threads;p;) { - if((p==th) || (p->pid==0) || (p->state==STATE_STARTING) || + if((p==th) || (p->os_thread==0) || (p->state==STATE_STARTING) || (p->state==STATE_DEAD) || (p->state==STATE_STOPPED)) { p=p->next; } @@ -398,13 +435,13 @@ void gc_start_the_world() SHOW("gc_start_the_world:begin"); #endif for(p=all_threads;p;p=p->next) { - if((p!=th) && (p->pid!=0) && (p->state!=STATE_STARTING) && + if((p!=th) && (p->os_thread!=0) && (p->state!=STATE_STARTING) && (p->state!=STATE_DEAD)) { if(p->state!=STATE_STOPPED) { lose("gc_start_the_world: wrong thread state is %ld\n", fixnum_value(p->state)); } - kill(p->pid,SIG_STOP_FOR_GC); + thread_kill(p->os_thread,SIG_STOP_FOR_GC); } } /* we must wait for all threads to leave stopped state else we @@ -412,7 +449,7 @@ void gc_start_the_world() * thread->state */ for(p=all_threads;p;) { gc_assert(p->state!=STATE_STOPPING); - if((p==th) || (p->pid==0) || (p->state!=STATE_STOPPED)) { + if((p==th) || (p->os_thread==0) || (p->state!=STATE_STOPPED)) { p=p->next; } } diff --git a/src/runtime/thread.h b/src/runtime/thread.h index 499a02c..870411c 100644 --- a/src/runtime/thread.h +++ b/src/runtime/thread.h @@ -34,7 +34,7 @@ union per_thread_data { extern struct thread *all_threads; extern int dynamic_values_bytes; -extern struct thread *find_thread_by_pid(pid_t pid); +extern struct thread *find_thread_by_os_thread(os_thread_t tid); #ifdef LISP_FEATURE_SB_THREAD #define for_each_thread(th) for(th=all_threads;th;th=th->next) @@ -107,19 +107,31 @@ static inline os_context_t *get_interrupt_context_for_thread(struct thread *th) * usually aren't by that time. So, it's here instead. Sorry */ static inline struct thread *arch_os_get_current_thread() { -#if defined(LISP_FEATURE_SB_THREAD) && defined (LISP_FEATURE_X86) +#if defined(LISP_FEATURE_SB_THREAD) +#if defined(LISP_FEATURE_X86) register struct thread *me=0; if(all_threads) __asm__ __volatile__ ("movl %%fs:%c1,%0" : "=r" (me) : "i" (offsetof (struct thread,this))); return me; #else - return all_threads; + return pthread_getspecific(specials); +#endif /* x86 */ +#else + return all_threads; #endif } +#if defined(LISP_FEATURE_SB_THREAD) +#define thread_self pthread_self +#define thread_kill pthread_kill +#define thread_sigmask pthread_sigmask +#else +#define thread_self getpid +#define thread_kill kill +#define thread_sigmask sigprocmask +#endif -int arch_os_thread_init(struct thread *thread); extern void create_initial_thread(lispobj); #endif /* _INCLUDE_THREAD_H_ */ diff --git a/src/runtime/validate.c b/src/runtime/validate.c index f4c8bfe..536e262 100644 --- a/src/runtime/validate.c +++ b/src/runtime/validate.c @@ -80,16 +80,16 @@ validate(void) } void -protect_control_stack_guard_page(pid_t t_id, int protect_p) { - struct thread *th = find_thread_by_pid(t_id); +protect_control_stack_guard_page(os_thread_t t_id, int protect_p) { + struct thread *th = find_thread_by_os_thread(t_id); os_protect(CONTROL_STACK_GUARD_PAGE(th), os_vm_page_size,protect_p ? (OS_VM_PROT_READ|OS_VM_PROT_EXECUTE) : OS_VM_PROT_ALL); } void -protect_control_stack_return_guard_page(pid_t t_id, int protect_p) { - struct thread *th = find_thread_by_pid(t_id); +protect_control_stack_return_guard_page(os_thread_t t_id, int protect_p) { + struct thread *th = find_thread_by_os_thread(t_id); os_protect(CONTROL_STACK_RETURN_GUARD_PAGE(th), os_vm_page_size,protect_p ? (OS_VM_PROT_READ|OS_VM_PROT_EXECUTE) : OS_VM_PROT_ALL); diff --git a/src/runtime/validate.h b/src/runtime/validate.h index d0a1a8c..1037f2f 100644 --- a/src/runtime/validate.h +++ b/src/runtime/validate.h @@ -42,8 +42,9 @@ #endif extern void validate(void); -extern void protect_control_stack_guard_page(pid_t t_id, int protect_p); -extern void protect_control_stack_return_guard_page(pid_t t_id, int protect_p); +extern void protect_control_stack_guard_page(os_thread_t t_id, int protect_p); +extern void protect_control_stack_return_guard_page(os_thread_t t_id, + int protect_p); extern os_vm_address_t undefined_alien_address; #endif diff --git a/src/runtime/x86-linux-os.c b/src/runtime/x86-linux-os.c index 15eb762..4ddacdf 100644 --- a/src/runtime/x86-linux-os.c +++ b/src/runtime/x86-linux-os.c @@ -78,7 +78,8 @@ int arch_os_thread_init(struct thread *thread) { 1, MODIFY_LDT_CONTENTS_DATA, 0, 0, 0, 1 }; int n; - get_spinlock(&modify_ldt_lock,thread); + /* thread->os_thread is not set yet*/ + get_spinlock(&modify_ldt_lock,(int)thread); n=modify_ldt(0,local_ldt_copy,sizeof local_ldt_copy); /* get next free ldt entry */ @@ -104,6 +105,7 @@ int arch_os_thread_init(struct thread *thread) { modify_ldt_lock=0; if(n<0) return 0; + pthread_setspecific(specials,thread); #endif #ifdef LISP_FEATURE_C_STACK_IS_CONTROL_STACK /* Signal handlers are run on the control stack, so if it is exhausted @@ -113,6 +115,9 @@ int arch_os_thread_init(struct thread *thread) { sigstack.ss_flags=0; sigstack.ss_size = 32*SIGSTKSZ; sigaltstack(&sigstack,0); + if(sigaltstack(&sigstack,0)<0) { + lose("Cannot sigaltstack: %s\n",strerror(errno)); + } #endif return 1; } diff --git a/tests/threads.impure.lisp b/tests/threads.impure.lisp index a312f3b..858d3da 100644 --- a/tests/threads.impure.lisp +++ b/tests/threads.impure.lisp @@ -152,10 +152,6 @@ (test-interrupt #'loop-forever :quit) (let ((child (test-interrupt (lambda () (loop (sleep 2000)))))) - ;; Interrupting a sleep form causes it to return early. Welcome to Unix. - ;; Just to be sure our LOOP form works, let's check the child is still - ;; there - (assert (zerop (sb-unix:unix-kill child 0))) (terminate-thread child)) (let ((lock (make-mutex :name "loctite")) @@ -186,6 +182,7 @@ (princ ".") (force-output) (assert (zerop SB-KERNEL:*PSEUDO-ATOMIC-ATOMIC*))))) (terminate-thread c)) +(terpri) (defparameter *interrupt-count* 0) @@ -232,11 +229,13 @@ (when (and a-done b-done) (return)) (sleep 1))) +(terpri) + (defun waste (&optional (n 100000)) (loop repeat n do (make-string 16384))) (loop for i below 100 do - (format t "LOOP:~A~%" i) + (princ "!") (force-output) (sb-thread:make-thread #'(lambda () @@ -244,9 +243,11 @@ (waste) (sb-ext:gc)) +(terpri) + (defparameter *aaa* nil) (loop for i below 100 do - (format t "LOOP:~A~%" i) + (princ "!") (force-output) (sb-thread:make-thread #'(lambda () @@ -258,6 +259,58 @@ (format t "~&gc test done~%") +;; this used to deadlock on session-lock +(sb-thread:make-thread (lambda () (sb-ext:gc))) +;; expose thread creation races by exiting quickly +(sb-thread:make-thread (lambda ())) + +(defun exercise-syscall (fn reference-errno) + (sb-thread:make-thread + (lambda () + (loop do + (funcall fn) + (let ((errno (sb-unix::get-errno))) + (sleep (random 1.0)) + (unless (eql errno reference-errno) + (format t "Got errno: ~A (~A) instead of ~A~%" + errno + (sb-unix::strerror) + reference-errno) + (force-output) + (sb-ext:quit :unix-status 1))))))) + +(let* ((nanosleep-errno (progn + (sb-unix:nanosleep -1 0) + (sb-unix::get-errno))) + (open-errno (progn + (open "no-such-file" + :if-does-not-exist nil) + (sb-unix::get-errno))) + (threads + (list + (exercise-syscall (lambda () (sb-unix:nanosleep -1 0)) nanosleep-errno) + (exercise-syscall (lambda () (open "no-such-file" + :if-does-not-exist nil)) + open-errno) + (sb-thread:make-thread (lambda () (loop (sb-ext:gc) (sleep 1))))))) + (sleep 10) + (princ "terminating threads") + (dolist (thread threads) + (sb-thread:terminate-thread thread))) + +(format t "~&errno test done~%") + +(loop repeat 100 do + (let ((thread (sb-thread:make-thread (lambda () (sleep 0.1))))) + (sb-thread:interrupt-thread + thread + (lambda () + (assert (find-restart 'sb-thread:terminate-thread)))))) + +(sb-ext:gc :full t) + +(format t "~&thread startup sigmask test done~%") + #| ;; a cll post from eric marsden | (defun crash () | (setq *debugger-hook* diff --git a/version.lisp-expr b/version.lisp-expr index 1d83649..d3995be 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".) -"0.9.1.58" +"0.9.1.59" -- 1.7.10.4