0.8.6.5
authorDaniel Barlow <dan@telent.net>
Thu, 27 Nov 2003 06:21:04 +0000 (06:21 +0000)
committerDaniel Barlow <dan@telent.net>
Thu, 27 Nov 2003 06:21:04 +0000 (06:21 +0000)
"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"

21 files changed:
CREDITS
base-target-features.lisp-expr
contrib/sb-posix/README
package-data-list.lisp-expr
src/code/cold-init.lisp
src/code/cross-thread.lisp
src/code/gc.lisp
src/code/target-thread.lisp
src/code/target-unithread.lisp
src/code/thread.lisp
src/code/toplevel.lisp
src/runtime/gencgc.c
src/runtime/interrupt.c
src/runtime/interrupt.h
src/runtime/linux-os.c
src/runtime/linux-os.h
src/runtime/thread.c
src/runtime/x86-arch.c
tests/threads.impure.lisp
tools-for-build/grovel-headers.c
version.lisp-expr

diff --git a/CREDITS b/CREDITS
index 4968cbb..d2a45a1 100644 (file)
--- 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.
index 6d10856..5b97f72 100644 (file)
  ;; 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
index e62ea00..832eea1 100644 (file)
@@ -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
 
index 5adc2ea..ec6ed72 100644 (file)
@@ -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
index bc7d9f5..bc37148 100644 (file)
@@ -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.
index eafb3fb..eb71d1f 100644 (file)
@@ -5,3 +5,5 @@
 (defmacro with-recursive-lock ((mutex) &body body)
   `(progn ,@body))
 
+
+
index eabb4b4..10f4bce 100644 (file)
@@ -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)
index 2601a46..9b69d91 100644 (file)
@@ -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
index 33f4b68..fde8f13 100644 (file)
@@ -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)
+
index 50be15f..e1e0417 100644 (file)
@@ -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)
   #!-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)
index 5aadeec..91bd1cb 100644 (file)
 (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)
index a83af66..3d14487 100644 (file)
@@ -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;
index 1c9a1b5..d7f4a42 100644 (file)
@@ -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);
index 35e221d..11eca74 100644 (file)
@@ -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,
index 6e391f2..03d950f 100644 (file)
 #include "thread.h"
 size_t os_vm_page_size;
 
+#ifdef LISP_FEATURE_SB_FUTEX
+#include <asm/unistd.h>
+#include <errno.h>
+
+/* 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"
 \f
 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
index 1055b2d..05729ae 100644 (file)
@@ -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)
 
-
index 3092fc0..8ac0519 100644 (file)
@@ -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);
 }
 
index 9b51cc0..543c7e0 100644 (file)
@@ -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, "/<trap pending interrupt>\n"));
        arch_skip_instruction(context);
+       sigemptyset(&ss);
+       sigaddset(&ss,SIGTRAP);
+       sigprocmask(SIG_UNBLOCK,&ss,0);
        interrupt_handle_pending(context);
        break;
 
index e0973ff..4b59604 100644 (file)
@@ -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    
 ;;; 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")))
       (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)))
   (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
                        (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
index 2254aa3..facc3f8 100644 (file)
@@ -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;
 }
index 7d641d8..7360bd3 100644 (file)
@@ -17,4 +17,4 @@
 ;;; checkins which aren't released. (And occasionally for internal
 ;;; versions, especially for internal versions off the main CVS
 ;;; branch, it gets hairier, e.g. "0.pre7.14.flaky4.13".)
-"0.8.6.4"
+"0.8.6.5"