0.9.6.50: stability before creativity
authorGabor Melis <mega@hotpop.com>
Fri, 18 Nov 2005 12:28:40 +0000 (12:28 +0000)
committerGabor Melis <mega@hotpop.com>
Fri, 18 Nov 2005 12:28:40 +0000 (12:28 +0000)
  * protect hash table operations with spinlocks on threaded builds =>
    ** no more memory corruption due to SHRINK-VECTOR in hash table code
    ** no more hangs caused by a corrupted NEXT-VECTOR that is cyclic
    ** as a side effect hash tables are thread safe (but it's not part of
       the contract, do your own locking)
    ** slower hash tables

src/code/cold-init.lisp
src/code/cross-thread.lisp
src/code/gc.lisp
src/code/hash-table.lisp
src/code/sysmacs.lisp
src/code/target-hash-table.lisp
src/code/target-thread.lisp
src/code/thread.lisp
tests/threads.impure.lisp
version.lisp-expr

index 202a479..cf82264 100644 (file)
         *cold-init-complete-p* nil
         *type-system-initialized* nil)
 
+  (show-and-call thread-init-or-reinit)
   (show-and-call !typecheckfuns-cold-init)
 
   ;; Anyone might call RANDOM to initialize a hash value or something;
 
   (show-and-call os-cold-init-or-reinit)
 
-  (show-and-call thread-init-or-reinit)
   (show-and-call stream-cold-init-or-reset)
   (show-and-call !loader-cold-init)
   (show-and-call !foreign-cold-init)
index 1c63565..aede661 100644 (file)
   (declare (ignore mutex))
   `(locally ,@body))
 
+(defun make-spinlock (&key name value)
+  (declare (ignore name value))
+  nil)
+
+(defun get-spinlock (spinlock)
+  (declare (ignore spinlock))
+  nil)
 
+(defun release-spinlock (spinlock)
+  (declare (ignore spinlock))
+  nil)
 
+(defmacro with-spinlock ((spinlock) &body body)
+  (declare (ignore spinlock))
+  `(locally ,@body))
index b9d7fb1..9bc64ee 100644 (file)
@@ -250,16 +250,20 @@ environment these hooks may run in any thread.")
                                (sb!alien:unsigned 32))
         val))
 
+(declaim (inline maybe-handle-pending-gc))
+(defun maybe-handle-pending-gc ()
+  (when (and (not *gc-inhibit*)
+             (or #!+sb-thread *stop-for-gc-pending*
+                 *gc-pending*))
+    (sb!unix::receive-pending-interrupt)))
+
 ;;; These work both regardless of whether we're inside WITHOUT-GCING
 ;;; or not.
 (defun gc-on ()
   #!+sb-doc
   "Enable the garbage collector."
   (setq *gc-inhibit* nil)
-  (when (and (not *gc-inhibit*)
-             (or #!+sb-thread *stop-for-gc-pending*
-                 *gc-pending*))
-    (sb!unix::receive-pending-interrupt))
+  (maybe-handle-pending-gc)
   nil)
 
 (defun gc-off ()
index b17a17a..1811acf 100644 (file)
@@ -70,7 +70,9 @@
   ;; +MAGIC-HASH-VECTOR-VALUE+ represents EQ-based hashing on the
   ;; respective key.
   (hash-vector nil :type (or null (simple-array (unsigned-byte
-                                                 #.sb!vm:n-word-bits) (*)))))
+                                                 #.sb!vm:n-word-bits) (*))))
+  ;; This lock is acquired by %PUTHASH, REMHASH, CLRHASH and GETHASH.
+  (spinlock (sb!thread::make-spinlock)))
 
 ;; as explained by pmai on openprojects #lisp IRC 2002-07-30: #x80000000
 ;; is bigger than any possible nonEQ hash value, and thus indicates an
index a7e98dd..b61e1b3 100644 (file)
@@ -38,13 +38,10 @@ gcs. Finally, upon leaving the BODY if gc is not inhibited it runs the
 pending gc. Similarly, if gc is triggered in another thread then it
 waits until gc is enabled in this thread."
   `(unwind-protect
-    (let ((*gc-inhibit* t))
-      ,@body)
-    ;; the test is racy, but it can err only on the overeager side
-    (when (and (not *gc-inhibit*)
-               (or #!+sb-thread *stop-for-gc-pending*
-                   *gc-pending*))
-      (sb!unix::receive-pending-interrupt))))
+        (let ((*gc-inhibit* t))
+          ,@body)
+     ;; the test is racy, but it can err only on the overeager side
+     (sb!kernel::maybe-handle-pending-gc)))
 
 \f
 ;;; EOF-OR-LOSE is a useful macro that handles EOF.
index b98c33d..b0cf137 100644 (file)
 \f
 ;;;; utilities
 
+;; This stuff is performance critical and unwind-protect is too
+;; slow. And without the locking the next vector can get cyclic
+;; causing looping in a WITHOUT-GCING form, SHRINK-VECTOR can corrupt
+;; memory and who knows what else.
+(defmacro with-spinlock-and-without-gcing ((spinlock) &body body)
+  #!-sb-thread
+  (declare (ignore spinlock))
+  `(unwind-protect
+        (let ((*gc-inhibit* t))
+          #!+sb-thread
+          (sb!thread::get-spinlock ,spinlock)
+          ,@body)
+     #!+sb-thread
+     (sb!thread::release-spinlock ,spinlock)
+     ;; the test is racy, but it can err only on the overeager side
+     (sb!kernel::maybe-handle-pending-gc)))
+
 (eval-when (:compile-toplevel :load-toplevel :execute)
   (defconstant max-hash sb!xc:most-positive-fixnum))
 
                    :hash-vector (unless (eq test 'eq)
                                   (make-array size+1
                                               :element-type '(unsigned-byte #.sb!vm:n-word-bits)
-                                              :initial-element +magic-hash-vector-value+)))))
+                                              :initial-element +magic-hash-vector-value+))
+                   :spinlock (sb!thread::make-spinlock))))
       (declare (type index size+1 scaled-size length))
       ;; Set up the free list, all free. These lists are 0 terminated.
       (do ((i 1 (1+ i)))
   "Three argument version of GETHASH"
   (declare (type hash-table hash-table)
            (values t (member t nil)))
-  (without-gcing
+  (with-spinlock-and-without-gcing ((hash-table-spinlock hash-table))
    (cond ((= (get-header-data (hash-table-table hash-table))
              sb!vm:vector-must-rehash-subtype)
           (rehash-without-growing hash-table))
 (defun %puthash (key hash-table value)
   (declare (type hash-table hash-table))
   (aver (hash-table-index-vector hash-table))
-  (without-gcing
+  (with-spinlock-and-without-gcing ((hash-table-spinlock hash-table))
    ;; We need to rehash here so that a current key can be found if it
    ;; exists. Check that there is room for one more entry. May not be
    ;; needed if the key is already present.
              (when hash-vector
                (if (not eq-based)
                    (setf (aref hash-vector free-kv-slot) hashing)
-                 (aver (= (aref hash-vector free-kv-slot) +magic-hash-vector-value+))))
+                   (aver (= (aref hash-vector free-kv-slot)
+                            +magic-hash-vector-value+))))
 
              ;; Push this slot into the next chain.
              (setf (aref next-vector free-kv-slot) next)
    was such an entry, or NIL if not."
   (declare (type hash-table hash-table)
            (values (member t nil)))
-  (without-gcing
+  (with-spinlock-and-without-gcing ((hash-table-spinlock hash-table))
    ;; We need to rehash here so that a current key can be found if it
    ;; exists.
    (cond ((= (get-header-data (hash-table-table hash-table))
                       (hash-table-next-free-kv hash-table))
                 (setf (hash-table-next-free-kv hash-table) slot-location)
                 (when hash-vector
-                  (setf (aref hash-vector slot-location) +magic-hash-vector-value+))
+                  (setf (aref hash-vector slot-location)
+                        +magic-hash-vector-value+))
                 (decf (hash-table-number-entries hash-table))
                 t))
          (cond ((zerop next)
   "This removes all the entries from HASH-TABLE and returns the hash table
    itself."
   (declare (optimize speed))
-  (let* ((kv-vector (hash-table-table hash-table))
-         (next-vector (hash-table-next-vector hash-table))
-         (hash-vector (hash-table-hash-vector hash-table))
-         (size (length next-vector))
-         (index-vector (hash-table-index-vector hash-table)))
-    ;; Disable GC tricks.
-    (set-header-data kv-vector sb!vm:vector-normal-subtype)
-    ;; Mark all slots as empty by setting all keys and values to magic
-    ;; tag.
-    (aver (eq (aref kv-vector 0) hash-table))
-    (fill kv-vector +empty-ht-slot+ :start 2)
-    ;; Set up the free list, all free.
-    (do ((i 1 (1+ i)))
-        ((>= i (1- size)))
-      (setf (aref next-vector i) (1+ i)))
-    (setf (aref next-vector (1- size)) 0)
-    (setf (hash-table-next-free-kv hash-table) 1)
-    (setf (hash-table-needing-rehash hash-table) 0)
-    ;; Clear the index-vector.
-    (fill index-vector 0)
-    ;; Clear the hash-vector.
-    (when hash-vector
-      (fill hash-vector +magic-hash-vector-value+)))
-  (setf (hash-table-cache hash-table) nil)
-  (setf (hash-table-number-entries hash-table) 0)
+  (with-spinlock-and-without-gcing ((hash-table-spinlock hash-table))
+    (let* ((kv-vector (hash-table-table hash-table))
+           (next-vector (hash-table-next-vector hash-table))
+           (hash-vector (hash-table-hash-vector hash-table))
+           (size (length next-vector))
+           (index-vector (hash-table-index-vector hash-table)))
+      ;; Disable GC tricks.
+      (set-header-data kv-vector sb!vm:vector-normal-subtype)
+      ;; Mark all slots as empty by setting all keys and values to magic
+      ;; tag.
+      (aver (eq (aref kv-vector 0) hash-table))
+      (fill kv-vector +empty-ht-slot+ :start 2)
+      ;; Set up the free list, all free.
+      (do ((i 1 (1+ i)))
+          ((>= i (1- size)))
+          (setf (aref next-vector i) (1+ i)))
+      (setf (aref next-vector (1- size)) 0)
+      (setf (hash-table-next-free-kv hash-table) 1)
+      (setf (hash-table-needing-rehash hash-table) 0)
+      ;; Clear the index-vector.
+      (fill index-vector 0)
+      ;; Clear the hash-vector.
+      (when hash-vector
+        (fill hash-vector +magic-hash-vector-value+)))
+    (setf (hash-table-cache hash-table) nil)
+    (setf (hash-table-number-entries hash-table) 0))
   hash-table)
 \f
 ;;;; MAPHASH
index 7b80ee0..3edfb7d 100644 (file)
@@ -117,17 +117,11 @@ in future versions."
 
 ;;;; spinlocks
 
-(defstruct spinlock
-  #!+sb-doc
-  "Spinlock type."
-  (name nil :type (or null simple-string))
-  (value 0))
-
 (declaim (inline get-spinlock release-spinlock))
 
 ;;; The bare 2 here and below are offsets of the slots in the struct.
 ;;; There ought to be some better way to get these numbers
-(defun get-spinlock (spinlock new-value)
+(defun get-spinlock (spinlock)
   (declare (optimize (speed 3) (safety 0))
            #!-sb-thread
            (ignore spinlock new-value))
@@ -135,7 +129,7 @@ in future versions."
   ;; store any value
   #!+sb-thread
   (loop until
-        (eql (sb!vm::%instance-set-conditional spinlock 2 0 new-value) 0)))
+        (eql (sb!vm::%instance-set-conditional spinlock 2 0 1) 0)))
 
 (defun release-spinlock (spinlock)
   (declare (optimize (speed 3) (safety 0))
@@ -150,19 +144,13 @@ in future versions."
 (defmacro with-spinlock ((spinlock) &body body)
   (sb!int:with-unique-names (lock)
     `(let ((,lock ,spinlock))
-      (get-spinlock ,lock *current-thread*)
+      (get-spinlock ,lock)
       (unwind-protect
            (progn ,@body)
         (release-spinlock ,lock)))))
 
 ;;;; mutexes
 
-(defstruct mutex
-  #!+sb-doc
-  "Mutex type."
-  (name nil :type (or null simple-string))
-  (value nil))
-
 #!+sb-doc
 (setf (sb!kernel:fdocumentation 'make-mutex 'function)
       "Create a mutex."
index ab393f5..378fb4b 100644 (file)
 
 (in-package "SB!THREAD")
 
+(def!struct mutex
+  #!+sb-doc
+  "Mutex type."
+  (name nil :type (or null simple-string))
+  (value nil))
+
+(def!struct spinlock
+  #!+sb-doc
+  "Spinlock type."
+  (name nil :type (or null simple-string))
+  (value 0))
+
 (sb!xc:defmacro with-mutex ((mutex &key (value '*current-thread*) (wait-p t))
                             &body body)
   #!+sb-doc
@@ -43,7 +55,8 @@ further recursive lock attempts for the same mutex succeed. It is
 allowed to mix WITH-MUTEX and WITH-RECURSIVE-LOCK for the same mutex
 provided the default value is used for the mutex."
   #!-sb-thread
-  (declare (ignore mutex)) #!+sb-thread
+  (declare (ignore mutex))
+  #!+sb-thread
   (with-unique-names (mutex1 inner-lock-p)
     `(let* ((,mutex1 ,mutex)
             (,inner-lock-p (eq (mutex-value ,mutex1) *current-thread*)))
index d9d6f02..5d1a380 100644 (file)
     (with-mutex (l)
       (with-recursive-lock (l)))))
 
-(let ((l (make-spinlock :name "spinlock"))
-      (p *current-thread*))
+(let ((l (make-spinlock :name "spinlock")))
   (assert (eql (spinlock-value l) 0) nil "1")
   (with-spinlock (l)
-    (assert (eql (spinlock-value l) p) nil "2"))
+    (assert (eql (spinlock-value l) 1) nil "2"))
   (assert (eql (spinlock-value l) 0) nil "3"))
 
 ;; test that SLEEP actually sleeps for at least the given time, even
 
 (format t "~&binding test done~%")
 
+;; Try to corrupt the NEXT-VECTOR. Operations on a hash table with a
+;; cyclic NEXT-VECTOR can loop endlessly in a WITHOUT-GCING form
+;; causing the next gc hang SBCL.
+(with-test (:name (:hash-table-thread-safety))
+  (let* ((hash (make-hash-table))
+         (threads (list (sb-thread:make-thread
+                         (lambda ()
+                           (loop
+                            ;;(princ "1") (force-output)
+                            (setf (gethash (random 100) hash) 'h))))
+                        (sb-thread:make-thread
+                         (lambda ()
+                           (loop
+                            ;;(princ "2") (force-output)
+                            (remhash (random 100) hash))))
+                        (sb-thread:make-thread
+                         (lambda ()
+                           (loop
+                            (sleep (random 1.0))
+                            (sb-ext:gc :full t)))))))
+    (unwind-protect
+         (sleep 5)
+      (mapc #'sb-thread:terminate-thread threads))))
 
+(format t "~&hash table test done~%")
 #|  ;; a cll post from eric marsden
 | (defun crash ()
 |   (setq *debugger-hook*
index c9fcf38..8349969 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.9.6.49"
+"0.9.6.50"