From dcf5978d9d33098e868ae6eea28e1b310038c03d Mon Sep 17 00:00:00 2001 From: Daniel Barlow Date: Thu, 27 Nov 2003 06:21:04 +0000 Subject: [PATCH] 0.8.6.5 "Well, the hours are pretty good" Merged the resistance-is-futex branch: see commit messages on branch for scary details "... but now I come to think about it, most of the actual minutes are pretty lousy" --- CREDITS | 2 +- base-target-features.lisp-expr | 9 ++ contrib/sb-posix/README | 13 +- package-data-list.lisp-expr | 3 +- src/code/cold-init.lisp | 1 + src/code/cross-thread.lisp | 2 + src/code/gc.lisp | 35 ++-- src/code/target-thread.lisp | 327 +++++++++++++++++++------------------- src/code/target-unithread.lisp | 7 +- src/code/thread.lisp | 18 --- src/code/toplevel.lisp | 2 +- src/runtime/gencgc.c | 4 +- src/runtime/interrupt.c | 4 +- src/runtime/interrupt.h | 2 +- src/runtime/linux-os.c | 91 +++++++---- src/runtime/linux-os.h | 4 +- src/runtime/thread.c | 68 ++++---- src/runtime/x86-arch.c | 7 +- tests/threads.impure.lisp | 61 +++++-- tools-for-build/grovel-headers.c | 6 +- version.lisp-expr | 2 +- 21 files changed, 359 insertions(+), 309 deletions(-) diff --git a/CREDITS b/CREDITS index 4968cbb..d2a45a1 100644 --- a/CREDITS +++ b/CREDITS @@ -512,7 +512,7 @@ Daniel Barlow: and PPC ports (from CMUCL), control stack exhaustion checking (new) and native threads support for x86 Linux (new). He also refactored the garbage collectors for understandability, wrote code - (e.g. grovel_headers.c and stat_wrapper stuff) to find + (e.g. grovel-headers.c and stat_wrapper stuff) to find machine-dependent and OS-dependent constants automatically, and was original author of the asdf, asdf-install, sb-bsd-sockets, sb-executable, sb-grovel and sb-posix contrib packages. diff --git a/base-target-features.lisp-expr b/base-target-features.lisp-expr index 6d10856..5b97f72 100644 --- a/base-target-features.lisp-expr +++ b/base-target-features.lisp-expr @@ -159,6 +159,15 @@ ;; Note that no consistent effort to audit the SBCL library code for ;; thread safety has been performed, so caveat executor. ; :sb-thread + + ;; Kernel support for futexes (so-called "fast userspace mutexes") is + ;; available in Linux 2.6 and some versions of 2.4 (Red Hat vendor + ;; kernels, possibly other vendors too). We can take advantage of + ;; these to do faster and probably more reliable mutex and condition + ;; variable support. An SBCL built with this feature will fall back + ;; to the old system if the futex() syscall is not available at + ;; runtime + ; :sb-futex ;; This affects the definition of a lot of things in bignum.lisp. It ;; doesn't seem to be documented anywhere what systems it might apply diff --git a/contrib/sb-posix/README b/contrib/sb-posix/README index e62ea00..832eea1 100644 --- a/contrib/sb-posix/README +++ b/contrib/sb-posix/README @@ -80,9 +80,9 @@ results if the stream is buffered. A filename is a string. -A pathname is a designator for a file-descriptor: the filename is -computed using the same mechanism as the implementation would -use to map pathnames to OS filenames internally. +A pathname is a designator for a filename: the filename is computed +using the same mechanism as the implementation would use to map +pathnames to OS filenames internally. In an implementation that supports pathnames to files on other hosts, using mechanisms not available to the underlying OS (for example, @@ -166,11 +166,12 @@ is obvious. For example, (read fd buffer &optional (length (length buffer))) => bytes-read b) where C simulates "out" parameters using pointers (for instance, in -pipe() or socketpair()) we may use multiple return values instead. -This doesn't apply to data transfer functions that fill buffers. +pipe() or socketpair()) these may be optional or omitted in the Lisp +interface: if not provided, appropriate objects will be allocated and +returned (using multiple return values if necessary). c) some functions accept objects such as filenames or file -descriptors. Wherver these are specified as such in the C bindings, +descriptors. Wherever these are specified as such in the C bindings, the Lisp interface accepts designators for them as specified in the 'Types' section above diff --git a/package-data-list.lisp-expr b/package-data-list.lisp-expr index 5adc2ea..ec6ed72 100644 --- a/package-data-list.lisp-expr +++ b/package-data-list.lisp-expr @@ -1470,7 +1470,8 @@ is a good idea, but see SB-SYS re. blurring of boundaries." "MAKE-LISTENER-THREAD" "DESTROY-THREAD" "TERMINATE-THREAD" "INTERRUPT-THREAD" "WITH-RECURSIVE-LOCK" "MUTEX" "MAKE-MUTEX" "GET-MUTEX" "RELEASE-MUTEX" "WITH-MUTEX" - "WAITQUEUE" "MAKE-WAITQUEUE" "CONDITION-WAIT" "CONDITION-NOTIFY" + "MUTEX-VALUE" "WAITQUEUE" "MAKE-WAITQUEUE" + "CONDITION-WAIT" "CONDITION-NOTIFY" "CONDITION-BROADCAST" "WITH-RECURSIVE-LOCK" "RELEASE-FOREGROUND" "CURRENT-THREAD-ID")) #s(sb-cold:package-data diff --git a/src/code/cold-init.lisp b/src/code/cold-init.lisp index bc7d9f5..bc37148 100644 --- a/src/code/cold-init.lisp +++ b/src/code/cold-init.lisp @@ -289,6 +289,7 @@ instead (which is another name for the same thing).")) ;; disabled by default. Joe User can explicitly enable them if ;; desired. (set-floating-point-modes :traps '(:overflow :invalid :divide-by-zero)) + (sb!thread::maybe-install-futex-functions) ;; Clear pseudo atomic in case this core wasn't compiled with ;; support. diff --git a/src/code/cross-thread.lisp b/src/code/cross-thread.lisp index eafb3fb..eb71d1f 100644 --- a/src/code/cross-thread.lisp +++ b/src/code/cross-thread.lisp @@ -5,3 +5,5 @@ (defmacro with-recursive-lock ((mutex) &body body) `(progn ,@body)) + + diff --git a/src/code/gc.lisp b/src/code/gc.lisp index eabb4b4..10f4bce 100644 --- a/src/code/gc.lisp +++ b/src/code/gc.lisp @@ -230,28 +230,27 @@ and submit it as a patch." ;;; For GENCGC all generations < GEN will be GC'ed. -(defvar *already-in-gc* nil "System is running SUB-GC") -(defvar *gc-mutex* (sb!thread:make-mutex :name "GC Mutex")) +(defvar *already-in-gc* + (sb!thread:make-mutex :name "GC lock") "ID of thread running SUB-GC") (defun sub-gc (&key (gen 0) &aux (pre-gc-dynamic-usage (dynamic-usage))) - ;; catch attempts to gc recursively or during post-hooks and ignore them - (when (sb!thread::mutex-value *gc-mutex*) (return-from sub-gc nil)) - (sb!thread:with-mutex (*gc-mutex* :wait-p nil) + (let ((me (sb!thread:current-thread-id))) + (when (eql (sb!thread::mutex-value *already-in-gc*) me) + (return-from sub-gc nil)) (setf *need-to-collect-garbage* t) (when (zerop *gc-inhibit*) - (without-interrupts - (gc-stop-the-world) - (collect-garbage gen) - (incf *n-bytes-freed-or-purified* - (max 0 (- pre-gc-dynamic-usage (dynamic-usage)))) - (setf *need-to-collect-garbage* nil) - (gc-start-the-world)) - (scrub-control-stack) - (setf *need-to-collect-garbage* nil) - (dolist (h *after-gc-hooks*) (carefully-funcall h)))) - (values)) - - + (loop + (sb!thread:with-mutex (*already-in-gc*) + (unless *need-to-collect-garbage* (return-from sub-gc nil)) + (without-interrupts + (gc-stop-the-world) + (collect-garbage gen) + (incf *n-bytes-freed-or-purified* + (max 0 (- pre-gc-dynamic-usage (dynamic-usage)))) + (scrub-control-stack) + (setf *need-to-collect-garbage* nil) + (dolist (h *after-gc-hooks*) (carefully-funcall h)) + (gc-start-the-world))))))) ;;; This is the user-advertised garbage collection function. (defun gc (&key (gen 0) (full nil) &allow-other-keys) diff --git a/src/code/target-thread.lisp b/src/code/target-thread.lisp index 2601a46..9b69d91 100644 --- a/src/code/target-thread.lisp +++ b/src/code/target-thread.lisp @@ -1,9 +1,18 @@ (in-package "SB!THREAD") +;;; FIXME it would be good to define what a thread id is or isn't (our +;;; current assumption is that it's a fixnum). It so happens that on +;;; Linux it's a pid, but it might not be on posix thread implementations + (sb!alien::define-alien-routine ("create_thread" %create-thread) sb!alien:unsigned-long (lisp-fun-address sb!alien:unsigned-long)) +(sb!alien::define-alien-routine "signal_thread_to_dequeue" + sb!alien:unsigned-int + (thread-pid sb!alien:unsigned-long)) + + (defun make-thread (function) (let ((real-function (coerce function 'function))) (%create-thread @@ -106,17 +115,43 @@ SB-EXT:QUIT - the usual cleanup forms will be evaluated" ;;;; the higher-level locking operations are based on waitqueues +(declaim (inline waitqueue-data-address mutex-value-address)) + (defstruct waitqueue (name nil :type (or null simple-base-string)) (lock 0) (data nil)) +;;; The bare 4 here and 5 below are offsets of the slots in the struct. +;;; There ought to be some better way to get these numbers +(defun waitqueue-data-address (lock) + (declare (optimize (speed 3))) + (sb!ext:truly-the + (unsigned-byte 32) + (+ (sb!kernel:get-lisp-obj-address lock) + (- (* 4 sb!vm:n-word-bytes) sb!vm:instance-pointer-lowtag)))) + (defstruct (mutex (:include waitqueue)) (value nil)) +(defun mutex-value-address (lock) + (declare (optimize (speed 3))) + (sb!ext:truly-the + (unsigned-byte 32) + (+ (sb!kernel:get-lisp-obj-address lock) + (- (* 5 sb!vm:n-word-bytes) sb!vm:instance-pointer-lowtag)))) + (sb!alien:define-alien-routine "block_sigcont" void) (sb!alien:define-alien-routine "unblock_sigcont_and_sleep" void) +#!+sb-futex +(declaim (inline futex-wait futex-wake)) +#!+sb-futex +(sb!alien:define-alien-routine + "futex_wait" int (word unsigned-long) (old-value unsigned-long)) +#!+sb-futex +(sb!alien:define-alien-routine + "futex_wake" int (word unsigned-long) (n unsigned-long)) ;;; this should only be called while holding the queue spinlock. ;;; it releases the spinlock before sleeping @@ -140,13 +175,14 @@ SB-EXT:QUIT - the usual cleanup forms will be evaluated" ;;; this should only be called while holding the queue spinlock. (defun signal-queue-head (queue) (let ((p (car (waitqueue-data queue)))) - (when p (sb!unix:unix-kill p sb!unix::sig-dequeue)))) + (when p (signal-thread-to-dequeue p)))) ;;;; mutex +;;; i suspect there may be a race still in this: the futex version requires +;;; the old mutex value before sleeping, so how do we get away without it (defun get-mutex (lock &optional new-value (wait-p t)) - (declare (type mutex lock) - (optimize (speed 3))) + (declare (type mutex lock) (optimize (speed 3))) (let ((pid (current-thread-id))) (unless new-value (setf new-value pid)) (assert (not (eql new-value (mutex-value lock)))) @@ -163,6 +199,21 @@ SB-EXT:QUIT - the usual cleanup forms will be evaluated" (return nil)) (wait-on-queue lock nil)))) +#!+sb-futex +(defun get-mutex/futex (lock &optional new-value (wait-p t)) + (declare (type mutex lock) (optimize (speed 3))) + (let ((pid (current-thread-id)) + old) + (unless new-value (setf new-value pid)) + (assert (not (eql new-value (mutex-value lock)))) + (loop + (unless + (setf old (sb!vm::%instance-set-conditional lock 4 nil new-value)) + (return t)) + (unless wait-p (return nil)) + (futex-wait (mutex-value-address lock) + (sb!kernel:get-lisp-obj-address old))))) + (defun release-mutex (lock &optional (new-value nil)) (declare (type mutex lock)) ;; we assume the lock is ours to release @@ -170,6 +221,12 @@ SB-EXT:QUIT - the usual cleanup forms will be evaluated" (setf (mutex-value lock) new-value) (signal-queue-head lock))) +#!+sb-futex +(defun release-mutex/futex (lock) + (declare (type mutex lock)) + (setf (mutex-value lock) nil) + (futex-wake (mutex-value-address lock) 1)) + (defmacro with-mutex ((mutex &key value (wait-p t)) &body body) (with-unique-names (got) @@ -200,10 +257,68 @@ time we reacquire LOCK and return to the caller." (dequeue queue)) (get-mutex lock value)))) +#!+sb-futex +(defun condition-wait/futex (queue lock) + (assert lock) + (let ((value (mutex-value lock))) + (unwind-protect + (let ((me (current-thread-id))) + ;; XXX we should do something to ensure that the result of this setf + ;; is visible to all CPUs + (setf (waitqueue-data queue) me) + (release-mutex lock) + ;; Now we go to sleep using futex-wait. If anyone else + ;; manages to grab LOCK and call CONDITION-NOTIFY during + ;; this comment, it will change queue->data, and so + ;; futex-wait returns immediately instead of sleeping. + ;; Ergo, no lost wakeup + (futex-wait (waitqueue-data-address queue) + (sb!kernel:get-lisp-obj-address me))) + ;; If we are interrupted while waiting, we should do these things + ;; before returning. Ideally, in the case of an unhandled signal, + ;; we should do them before entering the debugger, but this is + ;; better than nothing. + (get-mutex lock value)))) + + (defun condition-notify (queue) "Notify one of the processes waiting on QUEUE" (with-spinlock (queue) (signal-queue-head queue))) +#!+sb-futex +(defun condition-notify/futex (queue) + "Notify one of the processes waiting on QUEUE." + (let ((me (current-thread-id))) + ;; no problem if >1 thread notifies during the comment in + ;; condition-wait: as long as the value in queue-data isn't the + ;; waiting thread's id, it matters not what it is + ;; XXX we should do something to ensure that the result of this setf + ;; is visible to all CPUs + (setf (waitqueue-data queue) me) + (futex-wake (waitqueue-data-address queue) 1))) + +#!+sb-futex +(defun condition-broadcast/futex (queue) + (let ((me (current-thread-id))) + (setf (waitqueue-data queue) me) + (futex-wake (waitqueue-data-address queue) (ash 1 30)))) + +(defun condition-broadcast (queue) + "Notify all of the processes waiting on QUEUE." + (with-spinlock (queue) + (map nil #'signal-thread-to-dequeue (waitqueue-data queue)))) + +;;; Futexes may be available at compile time but not runtime, so we +;;; default to not using them unless os_init says they're available +(defun maybe-install-futex-functions () + #!+sb-futex + (unless (zerop (extern-alien "linux_supports_futex" int)) + (setf (fdefinition 'get-mutex) #'get-mutex/futex + (fdefinition 'release-mutex) #'release-mutex/futex + (fdefinition 'condition-wait) #'condition-wait/futex + (fdefinition 'condition-broadcast) #'condition-broadcast/futex + (fdefinition 'condition-notify) #'condition-notify/futex) + t)) ;;;; multiple independent listeners @@ -239,170 +354,52 @@ time we reacquire LOCK and return to the caller." ;;;; job control -(defvar *background-threads-wait-for-debugger* t) -;;; may be T, NIL, or a function called with a stream and thread id -;;; as its two arguments, returning NIl or T + +(defvar *interactive-threads-lock* + (make-mutex :name "*interactive-threads* lock")) +(defvar *interactive-threads* nil) +(defvar *interactive-threads-queue* + (make-waitqueue :name "All threads that need the terminal. First ID on this list is running, the others are waiting")) + +(defun init-job-control () + (with-mutex (*interactive-threads-lock*) + (setf *interactive-threads* (list (current-thread-id))) + (return-from init-job-control t))) ;;; called from top of invoke-debugger (defun debugger-wait-until-foreground-thread (stream) "Returns T if thread had been running in background, NIL if it was -already the foreground thread, or transfers control to the first applicable -restart if *BACKGROUND-THREADS-WAIT-FOR-DEBUGGER* says to do that instead" - (let* ((wait-p *background-threads-wait-for-debugger*) - (*background-threads-wait-for-debugger* nil) - (lock *session-lock*)) - (when (not (eql (mutex-value lock) (CURRENT-THREAD-ID))) - (when (functionp wait-p) - (setf wait-p - (funcall wait-p stream (CURRENT-THREAD-ID)))) - (cond (wait-p (get-foreground)) - (t (invoke-restart (car (compute-restarts)))))))) - -;;; install this with -;;; (setf SB-INT:*REPL-PROMPT-FUN* #'sb-thread::thread-repl-prompt-fun) -;;; One day it will be default -(defun thread-repl-prompt-fun (out-stream) - (let ((lock *session-lock*)) - (get-foreground) - (let ((stopped-threads (waitqueue-data lock))) - (when stopped-threads - (format out-stream "~{~&Thread ~A suspended~}~%" stopped-threads)) - (sb!impl::repl-prompt-fun out-stream)))) - -(defun resume-stopped-thread (id) - (let ((lock *session-lock*)) - (with-spinlock (lock) - (setf (waitqueue-data lock) - (cons id (delete id (waitqueue-data lock))))) - (release-foreground))) - -(defstruct rwlock - (name nil :type (or null simple-base-string)) - (value 0 :type fixnum) - (max-readers nil :type (or fixnum null)) - (max-writers 1 :type fixnum)) -#+nil -(macrolet - ((make-rwlocking-function (lock-fn unlock-fn increment limit test) - (let ((do-update '(when (eql old-value - (sb!vm::%instance-set-conditional - lock 2 old-value new-value)) - (return (values t old-value)))) - (vars `((timeout (and timeout (+ (get-internal-real-time) timeout))) - old-value - new-value - (limit ,limit)))) - (labels ((do-setfs (v) `(setf old-value (rwlock-value lock) - new-value (,v old-value ,increment)))) - `(progn - (defun ,lock-fn (lock timeout) - (declare (type rwlock lock)) - (let ,vars - (loop - ,(do-setfs '+) - (when ,test - ,do-update) - (when (sleep-a-bit timeout) (return nil)) ;expired - ))) - ;; unlock doesn't need timeout or test-in-range - (defun ,unlock-fn (lock) - (declare (type rwlock lock)) - (declare (ignorable limit)) - (let ,(cdr vars) - (loop - ,(do-setfs '-) - ,do-update)))))))) - - (make-rwlocking-function %lock-for-reading %unlock-for-reading 1 - (rwlock-max-readers lock) - (and (>= old-value 0) - (or (null limit) (<= new-value limit)))) - (make-rwlocking-function %lock-for-writing %unlock-for-writing -1 - (- (rwlock-max-writers lock)) - (and (<= old-value 0) - (>= new-value limit)))) -#+nil -(defun get-rwlock (lock direction &optional timeout) - (ecase direction - (:read (%lock-for-reading lock timeout)) - (:write (%lock-for-writing lock timeout)))) -#+nil -(defun free-rwlock (lock direction) - (ecase direction - (:read (%unlock-for-reading lock)) - (:write (%unlock-for-writing lock)))) - -;;;; beyond this point all is commented. - -;;; Lock-Wait-With-Timeout -- Internal -;;; -;;; Wait with a timeout for the lock to be free and acquire it for the -;;; *current-process*. -;;; -#+nil -(defun lock-wait-with-timeout (lock whostate timeout) - (declare (type lock lock)) - (process-wait-with-timeout - whostate timeout - #'(lambda () - (declare (optimize (speed 3))) - #-i486 - (unless (lock-process lock) - (setf (lock-process lock) *current-process*)) - #+i486 - (null (kernel:%instance-set-conditional - lock 2 nil *current-process*))))) - -;;; With-Lock-Held -- Public -;;; -#+nil -(defmacro with-lock-held ((lock &optional (whostate "Lock Wait") - &key (wait t) timeout) - &body body) - "Execute the body with the lock held. If the lock is held by another - process then the current process waits until the lock is released or - an optional timeout is reached. The optional wait timeout is a time in - seconds acceptable to process-wait-with-timeout. The results of the - body are return upon success and NIL is return if the timeout is - reached. When the wait key is NIL and the lock is held by another - process then NIL is return immediately without processing the body." - (let ((have-lock (gensym))) - `(let ((,have-lock (eq (lock-process ,lock) *current-process*))) - (unwind-protect - ,(cond ((and timeout wait) - `(progn - (when (and (error-check-lock-p ,lock) ,have-lock) - (error "Dead lock")) - (when (or ,have-lock - #+i486 (null (kernel:%instance-set-conditional - ,lock 2 nil *current-process*)) - #-i486 (seize-lock ,lock) - (if ,timeout - (lock-wait-with-timeout - ,lock ,whostate ,timeout) - (lock-wait ,lock ,whostate))) - ,@body))) - (wait - `(progn - (when (and (error-check-lock-p ,lock) ,have-lock) - (error "Dead lock")) - (unless (or ,have-lock - #+i486 (null (kernel:%instance-set-conditional - ,lock 2 nil *current-process*)) - #-i486 (seize-lock ,lock)) - (lock-wait ,lock ,whostate)) - ,@body)) - (t - `(when (or (and (recursive-lock-p ,lock) ,have-lock) - #+i486 (null (kernel:%instance-set-conditional - ,lock 2 nil *current-process*)) - #-i486 (seize-lock ,lock)) - ,@body))) - (unless ,have-lock - #+i486 (kernel:%instance-set-conditional - ,lock 2 *current-process* nil) - #-i486 (when (eq (lock-process ,lock) *current-process*) - (setf (lock-process ,lock) nil))))))) - - +interactive." + (prog1 + (with-mutex (*interactive-threads-lock*) + (not (member (current-thread-id) *interactive-threads*))) + (get-foreground))) +(defun thread-repl-prompt-fun (out-stream) + (get-foreground) + (let ((stopped-threads (cdr *interactive-threads*))) + (when stopped-threads + (format out-stream "~{~&Thread ~A suspended~}~%" stopped-threads)) + (sb!impl::repl-prompt-fun out-stream))) + +(defun get-foreground () + (loop + (with-mutex (*interactive-threads-lock*) + (let ((tid (current-thread-id))) + (when (eql (car *interactive-threads*) tid) + (sb!sys:enable-interrupt sb!unix:sigint #'sb!unix::sigint-handler) + (return-from get-foreground t)) + (unless (member tid *interactive-threads*) + (setf (cdr (last *interactive-threads*)) (list tid))) + (condition-wait + *interactive-threads-queue* *interactive-threads-lock* ))))) + +(defun release-foreground (&optional next) + "Background this thread. If NEXT is supplied, arrange for it to have the foreground next" + (with-mutex (*interactive-threads-lock*) + (let ((tid (current-thread-id))) + (setf *interactive-threads* (delete tid *interactive-threads*)) + (sb!sys:enable-interrupt sb!unix:sigint :ignore) + (when next (setf *interactive-threads* + (list* next (delete next *interactive-threads*)))) + (condition-broadcast *interactive-threads-queue*)))) \ No newline at end of file diff --git a/src/code/target-unithread.lisp b/src/code/target-unithread.lisp index 33f4b68..fde8f13 100644 --- a/src/code/target-unithread.lisp +++ b/src/code/target-unithread.lisp @@ -122,10 +122,9 @@ time we reacquire LOCK and return to the caller." (signal-queue-head queue)) -;;;; multiple independent listeners - -(defvar *session-lock* nil) - ;;;; job control (defun debugger-wait-until-foreground-thread (stream) t) +(defun get-foreground () t) +(defun release-foreground (&optional next) t) + diff --git a/src/code/thread.lisp b/src/code/thread.lisp index 50be15f..e1e0417 100644 --- a/src/code/thread.lisp +++ b/src/code/thread.lisp @@ -1,7 +1,5 @@ (in-package "SB!THREAD") -(defvar *session-lock*) - (sb!xc:defmacro with-recursive-lock ((mutex) &body body) #!+sb-thread (with-unique-names (cfp) @@ -28,19 +26,3 @@ #!-sb-thread `(progn ,@body)) -#!+sb-thread -(defun get-foreground () - (when (not (eql (mutex-value *session-lock*) (current-thread-id))) - (get-mutex *session-lock*)) - (sb!sys:enable-interrupt sb!unix:sigint #'sb!unix::sigint-handler) - t) -#!-sb-thread -(defun get-foreground () t) - -#!+sb-thread -(defun release-foreground () - (sb!sys:enable-interrupt sb!unix:sigint :ignore) - (release-mutex *session-lock*) - t) -#!-sb-thread -(defun release-foreground () t) diff --git a/src/code/toplevel.lisp b/src/code/toplevel.lisp index 5aadeec..91bd1cb 100644 --- a/src/code/toplevel.lisp +++ b/src/code/toplevel.lisp @@ -296,7 +296,7 @@ (defun toplevel-init () (/show0 "entering TOPLEVEL-INIT") - (setf sb!thread::*session-lock* (sb!thread:make-mutex :name "the terminal")) + (sb!thread::init-job-control) (sb!thread::get-foreground) (let (;; value of --sysinit option (sysinit nil) diff --git a/src/runtime/gencgc.c b/src/runtime/gencgc.c index a83af66..3d14487 100644 --- a/src/runtime/gencgc.c +++ b/src/runtime/gencgc.c @@ -2114,7 +2114,7 @@ search_space(lispobj *start, size_t words, lispobj *pointer) return (NULL); } -static lispobj* +lispobj* search_read_only_space(lispobj *pointer) { lispobj* start = (lispobj*)READ_ONLY_SPACE_START; @@ -2124,7 +2124,7 @@ search_read_only_space(lispobj *pointer) return (search_space(start, (pointer+2)-start, pointer)); } -static lispobj * +lispobj * search_static_space(lispobj *pointer) { lispobj* start = (lispobj*)STATIC_SPACE_START; diff --git a/src/runtime/interrupt.c b/src/runtime/interrupt.c index 1c9a1b5..d7f4a42 100644 --- a/src/runtime/interrupt.c +++ b/src/runtime/interrupt.c @@ -655,13 +655,13 @@ void arrange_return_to_lisp_function(os_context_t *context, lispobj function) } #ifdef LISP_FEATURE_SB_THREAD -void handle_rt_signal(int num, siginfo_t *info, void *v_context) +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); struct thread *th=arch_os_get_current_thread(); struct interrupt_data *data= th ? th->interrupt_data : global_interrupt_data; - if(maybe_defer_handler(handle_rt_signal,data,num,info,context)){ + if(maybe_defer_handler(interrupt_thread_handler,data,num,info,context)){ return ; } arrange_return_to_lisp_function(context,info->si_value.sival_int); diff --git a/src/runtime/interrupt.h b/src/runtime/interrupt.h index 35e221d..11eca74 100644 --- a/src/runtime/interrupt.h +++ b/src/runtime/interrupt.h @@ -51,7 +51,7 @@ extern void interrupt_internal_error(int, siginfo_t*, os_context_t*, extern boolean handle_control_stack_guard_triggered(os_context_t *,void *); extern boolean interrupt_maybe_gc(int, siginfo_t*, void*); #ifdef LISP_FEATURE_SB_THREAD -extern void handle_rt_signal(int, siginfo_t*, void*); +extern void interrupt_thread_handler(int, siginfo_t*, void*); extern void sig_stop_for_gc_handler(int, siginfo_t*, void*); #endif extern void undoably_install_low_level_interrupt_handler (int signal, diff --git a/src/runtime/linux-os.c b/src/runtime/linux-os.c index 6e391f2..03d950f 100644 --- a/src/runtime/linux-os.c +++ b/src/runtime/linux-os.c @@ -47,51 +47,64 @@ #include "thread.h" size_t os_vm_page_size; +#ifdef LISP_FEATURE_SB_FUTEX +#include +#include + +/* values taken from the kernel's linux/futex.h. This header file + doesn't exist in userspace, which is our excuse for not grovelling + them automatically */ +#define FUTEX_WAIT (0) +#define FUTEX_WAKE (1) +#define FUTEX_FD (2) +#define FUTEX_REQUEUE (3) + +#define __NR_sys_futex __NR_futex + +_syscall4(int,sys_futex, + int *, futex, + int, op, + int, val, + struct timespec *, rel); +#endif + #include "gc.h" int linux_sparc_siginfo_bug = 0; +int linux_supports_futex=0; void os_init(void) { /* Conduct various version checks: do we have enough mmap(), is * this a sparc running 2.2, can we do threads? */ - { - struct utsname name; - int major_version; - int minor_version; - char *p; - uname(&name); - p=name.release; - major_version = atoi(p); - p=strchr(p,'.')+1; - minor_version = atoi(p); - if (major_version<2) { - lose("linux kernel version too old: major version=%d (can't run in version < 2.0.0)", - major_version); - } + int *futex=0; + struct utsname name; + int major_version; + int minor_version; + char *p; + uname(&name); + p=name.release; + major_version = atoi(p); + p=strchr(p,'.')+1; + minor_version = atoi(p); + if (major_version<2) { + lose("linux kernel version too old: major version=%d (can't run in version < 2.0.0)", + major_version); + } + if (!(major_version>2 || minor_version >= 4)) { #ifdef LISP_FEATURE_SB_THREAD - if ((major_version <2) || (major_version==2 && minor_version < 4)) { - lose("linux kernel 2.4 required for thread-enabled SBCL"); - } + lose("linux kernel 2.4 required for thread-enabled SBCL"); #endif #ifdef LISP_FEATURE_SPARC - if ((major_version <2) || (major_version==2 && minor_version < 4)) { - FSHOW((stderr,"linux kernel %d.%d predates 2.4;\n enabling workarounds for SPARC kernel bugs in signal handling.\n", major_version,minor_version)); - linux_sparc_siginfo_bug = 1; - } + FSHOW((stderr,"linux kernel %d.%d predates 2.4;\n enabling workarounds for SPARC kernel bugs in signal handling.\n", major_version,minor_version)); + linux_sparc_siginfo_bug = 1; #endif } - - os_vm_page_size = getpagesize(); - /* This could just as well be in arch_init(), but it's not. */ -#ifdef LISP_FEATURE_X86 - /* FIXME: This used to be here. However, I have just removed it - with no apparent ill effects (it may be that earlier kernels - started up a process with a different set of traps, or - something?) Find out what this was meant to do, and reenable it - or delete it if possible. -- CSR, 2002-07-15 */ - /* SET_FPU_CONTROL_WORD(0x1372|4|8|16|32); no interrupts */ +#ifdef LISP_FEATURE_SB_FUTEX + futex_wait(futex,-1); + if(errno!=ENOSYS) linux_supports_futex=1; #endif + os_vm_page_size = getpagesize(); } @@ -264,11 +277,21 @@ os_install_interrupt_handlers(void) sigsegv_handler); #ifdef LISP_FEATURE_SB_THREAD undoably_install_low_level_interrupt_handler(SIG_INTERRUPT_THREAD, - handle_rt_signal); + interrupt_thread_handler); undoably_install_low_level_interrupt_handler(SIG_STOP_FOR_GC, sig_stop_for_gc_handler); + if(!linux_supports_futex) + undoably_install_low_level_interrupt_handler(SIG_DEQUEUE, + sigcont_handler); #endif - undoably_install_low_level_interrupt_handler(SIG_DEQUEUE, - sigcont_handler); } +#ifdef LISP_FEATURE_SB_FUTEX +int futex_wait(int *lock_word, int oldval) { + int t= sys_futex(lock_word,FUTEX_WAIT,oldval, 0); + return t; +} +int futex_wake(int *lock_word, int n){ + return sys_futex(lock_word,FUTEX_WAKE,n,0); +} +#endif diff --git a/src/runtime/linux-os.h b/src/runtime/linux-os.h index 1055b2d..05729ae 100644 --- a/src/runtime/linux-os.h +++ b/src/runtime/linux-os.h @@ -37,8 +37,8 @@ typedef int os_vm_prot_t; #define OS_VM_PROT_EXECUTE PROT_EXEC #define SIG_MEMORY_FAULT SIGSEGV -#define SIG_INTERRUPT_THREAD SIGRTMIN + +#define SIG_INTERRUPT_THREAD (SIGRTMIN) #define SIG_STOP_FOR_GC (SIGRTMIN+1) #define SIG_DEQUEUE (SIGRTMIN+2) - diff --git a/src/runtime/thread.c b/src/runtime/thread.c index 3092fc0..8ac0519 100644 --- a/src/runtime/thread.c +++ b/src/runtime/thread.c @@ -50,6 +50,8 @@ new_thread_trampoline(struct thread *th) fprintf(stderr, "/continue\n"); } th->unbound_marker = UNBOUND_MARKER_WIDETAG; + if(arch_os_thread_init(th)==0) + return 1; /* failure. no, really */ #ifdef LISP_FEATURE_SB_THREAD /* wait here until our thread is linked into all_threads: see below */ while(th->pid<1) sched_yield(); @@ -58,8 +60,7 @@ new_thread_trampoline(struct thread *th) lose("th->pid not set up right"); #endif - if(arch_os_thread_init(th)==0) - return 1; /* failure. no, really */ + th->state=STATE_RUNNING; #if !defined(LISP_FEATURE_SB_THREAD) && defined(LISP_FEATURE_X86) return call_into_lisp_first_time(function,args,0); #else @@ -136,7 +137,7 @@ pid_t create_thread(lispobj initial_function) { th->binding_stack_pointer=th->binding_stack_start; th->this=th; th->pid=0; - th->state=STATE_RUNNING; + th->state=STATE_STOPPED; #ifdef LISP_FEATURE_STACK_GROWS_DOWNWARD_NOT_UPWARD th->alien_stack_pointer=((void *)th->alien_stack_start + ALIEN_STACK_SIZE-4); /* naked 4. FIXME */ @@ -145,9 +146,7 @@ pid_t create_thread(lispobj initial_function) { #endif #ifdef LISP_FEATURE_X86 th->pseudo_atomic_interrupted=0; - /* runtime.c used to set PSEUDO_ATOMIC_ATOMIC =1 globally. I'm not - * sure why, but it appears to help */ - th->pseudo_atomic_atomic=make_fixnum(1); + th->pseudo_atomic_atomic=0; #endif #ifdef LISP_FEATURE_GENCGC gc_set_region_empty(&th->alloc_region); @@ -297,6 +296,12 @@ int interrupt_thread(pid_t pid, lispobj function) return sigqueue(pid, SIG_INTERRUPT_THREAD, sigval); } +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 thread does * the usual pseudo-atomic checks (we don't want to stop a thread while @@ -309,39 +314,36 @@ void gc_stop_the_world() { /* stop all other threads by sending them SIG_STOP_FOR_GC */ struct thread *p,*th=arch_os_get_current_thread(); - struct thread *tail=0; + pid_t old_pid; int finished=0; do { get_spinlock(&all_threads_lock,th->pid); - if(tail!=all_threads) { - /* new threads always get consed onto the front of all_threads, - * and may be created by any thread that we haven't signalled - * yet or hasn't received our signal and stopped yet. So, check - * for them on each time around */ - for(p=all_threads;p!=tail;p=p->next) { - if(p==th) continue; - /* if the head of all_threads is removed during - * gc_stop_the_world, we may take a second trip through the - * list and end up counting twice as many threads to wait for - * as actually exist */ - if(p->state!=STATE_RUNNING) continue; - countdown_to_gc++; - p->state=STATE_STOPPING; - /* Note no return value check from kill(). If the - * thread had been reaped already, we kill it and - * increment countdown_to_gc anyway. This is to avoid - * complicating the logic in destroy_thread, which would - * otherwise have to know whether the thread died before or - * after it was killed - */ - kill(p->pid,SIG_STOP_FOR_GC); - } - tail=all_threads; - } else { - finished=(countdown_to_gc==0); + for(p=all_threads,old_pid=p->pid; p; p=p->next) { + if(p==th) continue; + if(p->state!=STATE_RUNNING) continue; + countdown_to_gc++; + p->state=STATE_STOPPING; + /* Note no return value check from kill(). If the + * thread had been reaped already, we kill it and + * increment countdown_to_gc anyway. This is to avoid + * complicating the logic in destroy_thread, which would + * otherwise have to know whether the thread died before or + * after it was killed + */ + kill(p->pid,SIG_STOP_FOR_GC); } release_spinlock(&all_threads_lock); sched_yield(); + /* if everything has stopped, and there is no possibility that + * a new thread has been created, we're done. Otherwise go + * round again and signal anything that sprang up since last + * time */ + if(old_pid==all_threads->pid) { + finished=1; + for_each_thread(p) + finished = finished && + ((p==th) || (p->state==STATE_STOPPED)); + } } while(!finished); } diff --git a/src/runtime/x86-arch.c b/src/runtime/x86-arch.c index 9b51cc0..543c7e0 100644 --- a/src/runtime/x86-arch.c +++ b/src/runtime/x86-arch.c @@ -72,8 +72,7 @@ void arch_skip_instruction(os_context_t *context) int vlen; int code; - FSHOW((stderr, "/[arch_skip_inst at %x]\n", *os_context_pc_addr(context))); - + /* Get and skip the Lisp interrupt code. */ code = *(char*)(*os_context_pc_addr(context))++; switch (code) @@ -192,6 +191,7 @@ sigtrap_handler(int signal, siginfo_t *info, void *void_context) int code = info->si_code; os_context_t *context = (os_context_t*)void_context; unsigned int trap; + sigset_t ss; if (single_stepping && (signal==SIGTRAP)) { @@ -242,6 +242,9 @@ sigtrap_handler(int signal, siginfo_t *info, void *void_context) case trap_PendingInterrupt: FSHOW((stderr, "/\n")); arch_skip_instruction(context); + sigemptyset(&ss); + sigaddset(&ss,SIGTRAP); + sigprocmask(SIG_UNBLOCK,&ss,0); interrupt_handle_pending(context); break; diff --git a/tests/threads.impure.lisp b/tests/threads.impure.lisp index e0973ff..4b59604 100644 --- a/tests/threads.impure.lisp +++ b/tests/threads.impure.lisp @@ -18,7 +18,6 @@ ;;; For one of the interupt-thread tests, we want a foreign function ;;; that does not make syscalls -(setf SB-INT:*REPL-PROMPT-FUN* #'sb-thread::thread-repl-prompt-fun) (with-open-file (o "threads-foreign.c" :direction :output) (format o "void loop_forever() { while(1) ; }~%")) (sb-ext:run-program @@ -33,14 +32,15 @@ ;;; elementary "can we get a lock and release it again" (let ((l (make-mutex :name "foo")) (p (current-thread-id))) - (assert (eql (mutex-value l) nil)) - (assert (eql (mutex-lock l) 0)) + (assert (eql (mutex-value l) nil) nil "1") + (assert (eql (mutex-lock l) 0) nil "2") (sb-thread:get-mutex l) - (assert (eql (mutex-value l) p)) - (assert (eql (mutex-lock l) 0)) + (assert (eql (mutex-value l) p) nil "3") + (assert (eql (mutex-lock l) 0) nil "4") (sb-thread:release-mutex l) - (assert (eql (mutex-value l) nil)) - (assert (eql (mutex-lock l) 0))) + (assert (eql (mutex-value l) nil) nil "5") + (assert (eql (mutex-lock l) 0) nil "6") + (describe l)) (let ((queue (make-waitqueue :name "queue")) (lock (make-mutex :name "lock"))) @@ -86,6 +86,18 @@ (condition-notify queue)) (sleep 1))) +(let ((mutex (make-mutex :name "contended"))) + (labels ((run () + (let ((me (current-thread-id))) + (dotimes (i 100) + (with-mutex (mutex) + (sleep .1) + (assert (eql (mutex-value mutex) me))) + (assert (not (eql (mutex-value mutex) me)))) + (format t "done ~A~%" (current-thread-id))))) + (let ((kid1 (make-thread #'run)) + (kid2 (make-thread #'run))) + (format t "contention ~A ~A~%" kid1 kid2)))) (defun test-interrupt (function-to-interrupt &optional quit-p) (let ((child (make-thread function-to-interrupt))) @@ -130,9 +142,11 @@ (terminate-thread child)) (defun alloc-stuff () (copy-list '(1 2 3 4 5))) + (let ((c (test-interrupt (lambda () (loop (alloc-stuff)))))) ;; NB this only works on x86: other ports don't have a symbol for ;; pseudo-atomic atomicity + (format t "new thread ~A~%" c) (dotimes (i 100) (sleep (random 1d0)) (interrupt-thread c @@ -141,14 +155,35 @@ (assert (zerop SB-KERNEL:*PSEUDO-ATOMIC-ATOMIC*))))) (terminate-thread c)) -;; I'm not sure that this one is always successful. Note race potential: -;; I haven't checked if decf is atomic here -(let ((done 2)) - (make-thread (lambda () (dotimes (i 100) (sb-ext:gc)) (decf done))) - (make-thread (lambda () (dotimes (i 25) (sb-ext:gc :full t)) (decf done))) +(format t "~&interrupt test done~%") + +(let (a-done b-done) + (make-thread (lambda () + (dotimes (i 100) + (sb-ext:gc) (princ "\\") (force-output) ) + (setf a-done t))) + (make-thread (lambda () + (dotimes (i 25) + (sb-ext:gc :full t) + (princ "/") (force-output)) + (setf b-done t))) (loop - (when (zerop done) (return)) + (when (and a-done b-done) (return)) (sleep 1))) +(format t "~&gc test done~%") + +#| ;; a cll post from eric marsden +| (defun crash () +| (setq *debugger-hook* +| (lambda (condition old-debugger-hook) +| (debug:backtrace 10) +| (unix:unix-exit 2))) +| #+live-dangerously +| (mp::start-sigalrm-yield) +| (flet ((roomy () (loop (with-output-to-string (*standard-output*) (room))))) +| (mp:make-process #'roomy) +| (mp:make-process #'roomy))) +|# ;; give the other thread time to die before we leave, otherwise the ;; overall exit status is 0, not 104 diff --git a/tools-for-build/grovel-headers.c b/tools-for-build/grovel-headers.c index 2254aa3..facc3f8 100644 --- a/tools-for-build/grovel-headers.c +++ b/tools-for-build/grovel-headers.c @@ -55,7 +55,7 @@ main(int argc, char *argv[]) /* don't need no steenking hand-editing */ printf( ";;;; This is an automatically generated file, please do not hand-edit it.\n\ -;;;; See the program \"grovel_headers.c\".\n\ +;;;; See the program \"grovel-headers.c\".\n\ \n\ "); @@ -188,9 +188,5 @@ main(int argc, char *argv[]) DEFSIGNAL(SIGXCPU); DEFSIGNAL(SIGXFSZ); #endif -#ifdef LISP_FEATURE_SB_THREAD - /* FIXME OAOOM alert: this information is duplicated in linux-os.h */ - defconstant("sig-dequeue",SIGRTMIN+2); -#endif return 0; } diff --git a/version.lisp-expr b/version.lisp-expr index 7d641d8..7360bd3 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.8.6.4" +"0.8.6.5" -- 1.7.10.4