1.0.6.38: thread and interrupt safe ADD/REMOVE-METHOD
[sbcl.git] / src / pcl / std-class.lisp
index 502fe7c..8b9b939 100644 (file)
 ;;; In each case, we maintain one value which is a cons. The car is the list
 ;;; methods. The cdr is a list of the generic functions. The cdr is always
 ;;; computed lazily.
+
+;;; This needs to be used recursively, in case a non-trivial user
+;;; defined ADD/REMOVE-DIRECT-METHOD method ends up calling another
+;;; function using the same lock.
+(defvar *specializer-lock* (sb-thread::make-spinlock :name "Specializer lock"))
+
+(defmethod add-direct-method :around ((specializer specializer) method)
+  ;; All the actions done under this lock are done in an order
+  ;; that is safe to unwind at any point.
+  (sb-thread::with-recursive-spinlock (*specializer-lock*)
+    (call-next-method)))
+
+(defmethod remove-direct-method :around ((specializer specializer) method)
+  ;; All the actions done under this lock are done in an order
+  ;; that is safe to unwind at any point.
+  (sb-thread::with-recursive-spinlock (*specializer-lock*)
+    (call-next-method)))
+
 (defmethod add-direct-method ((specializer class) (method method))
-  (with-slots (direct-methods) specializer
-    (setf (car direct-methods) (adjoin method (car direct-methods))     ;PUSH
-          (cdr direct-methods) ()))
+  (let ((cell (slot-value specializer 'direct-methods)))
+    ;; We need to first smash the CDR, because a parallel read may
+    ;; be in progress, and because if an interrupt catches us we
+    ;; need to have a consistent state.
+    (setf (cdr cell) ()
+          (car cell) (adjoin method (car cell))))
   method)
+
 (defmethod remove-direct-method ((specializer class) (method method))
-  (with-slots (direct-methods) specializer
-    (setf (car direct-methods) (remove method (car direct-methods))
-          (cdr direct-methods) ()))
+  (let ((cell (slot-value specializer 'direct-methods)))
+    ;; We need to first smash the CDR, because a parallel read may
+    ;; be in progress, and because if an interrupt catches us we
+    ;; need to have a consistent state.
+    (setf (cdr cell) ()
+          (car cell) (remove method (car cell))))
   method)
 
 (defmethod specializer-direct-methods ((specializer class))
     (car direct-methods)))
 
 (defmethod specializer-direct-generic-functions ((specializer class))
-  (with-slots (direct-methods) specializer
-    (or (cdr direct-methods)
-        (setf (cdr direct-methods)
-              (let (collect)
-                (dolist (m (car direct-methods))
-                  ;; the old PCL code used COLLECTING-ONCE which used
-                  ;; #'EQ to check for newness
-                  (pushnew (method-generic-function m) collect :test #'eq))
-                (nreverse collect))))))
+  (let ((cell (slot-value specializer 'direct-methods)))
+    ;; If an ADD/REMOVE-METHOD is in progress, no matter: either
+    ;; we behave as if we got just first or just after -- it's just
+    ;; for update that we need to lock.
+    (or (cdr cell)
+        (sb-thread::with-spinlock (*specializer-lock*)
+          (setf (cdr cell)
+                (let (collect)
+                  (dolist (m (car cell))
+                    ;; the old PCL code used COLLECTING-ONCE which used
+                    ;; #'EQ to check for newness
+                    (pushnew (method-generic-function m) collect :test #'eq))
+                  (nreverse collect)))))))
 \f
 ;;; This hash table is used to store the direct methods and direct generic
 ;;; functions of EQL specializers. Each value in the table is the cons.
   (let* ((object (specializer-object specializer))
          (table (specializer-method-table specializer))
          (entry (gethash object table)))
+    ;; This table is shared between multiple specializers, but
+    ;; no worries as (at least for the time being) our hash-tables
+    ;; are thread safe.
     (unless entry
-      (setq entry
-            (setf (gethash object table)
-                  (cons nil nil))))
-    (setf (car entry) (adjoin method (car entry))
-          (cdr entry) ())
+      (setf entry
+            (setf (gethash object table) (cons nil nil))))
+    ;; We need to first smash the CDR, because a parallel read may
+    ;; be in progress, and because if an interrupt catches us we
+    ;; need to have a consistent state.
+    (setf (cdr entry) ()
+          (car entry) (adjoin method (car entry)))
     method))
 
 (defmethod remove-direct-method ((specializer specializer-with-object)
   (let* ((object (specializer-object specializer))
          (entry (gethash object (specializer-method-table specializer))))
     (when entry
-      (setf (car entry) (remove method (car entry))
-            (cdr entry) ()))
+      ;; We need to first smash the CDR, because a parallel read may
+      ;; be in progress, and because if an interrupt catches us we
+      ;; need to have a consistent state.
+      (setf (cdr entry) ()
+            (car entry) (remove method (car entry))))
     method))
 
 (defmethod specializer-direct-methods ((specializer specializer-with-object))
          (entry (gethash object (specializer-method-table specializer))))
     (when entry
       (or (cdr entry)
-          (setf (cdr entry)
-                (let (collect)
-                  (dolist (m (car entry))
-                    (pushnew (method-generic-function m) collect :test #'eq))
-                  (nreverse collect)))))))
+          (sb-thread::with-spinlock (*specializer-lock*)
+            (setf (cdr entry)
+                  (let (collect)
+                    (dolist (m (car entry))
+                      (pushnew (method-generic-function m) collect :test #'eq))
+                    (nreverse collect))))))))
 
 (defun map-specializers (function)
   (map-all-classes (lambda (class)
     (values defstruct-form constructor reader-names writer-names)))
 
 (defun make-defstruct-allocation-function (class)
-  (let ((dd (get-structure-dd (class-name class))))
+  ;; FIXME: Why don't we go class->layout->info == dd
+  (let ((dd (find-defstruct-description (class-name class))))
     (lambda ()
       (sb-kernel::%make-instance-with-layout
        (sb-kernel::compiler-layout-or-lose (dd-name dd))))))
         (setf slots eslotds
               (wrapper-instance-slots-layout nwrapper) nlayout
               (wrapper-class-slots nwrapper) nwrapper-class-slots
-              (wrapper-no-of-instance-slots nwrapper) nslots
+              (layout-length nwrapper) nslots
               wrapper nwrapper)
         (do* ((slots (slot-value class 'slots) (cdr slots))
               (dupes nil))
               ;; good style.  There has to be a better way!  -- CSR,
               ;; 2002-10-29
               (eq (layout-invalid owrapper) t))
-      (let ((nwrapper (make-wrapper (wrapper-no-of-instance-slots owrapper)
+      (let ((nwrapper (make-wrapper (layout-length owrapper)
                                     class)))
         (setf (wrapper-instance-slots-layout nwrapper)
               (wrapper-instance-slots-layout owrapper))
 ;;; through the UPDATE-INSTANCE-FOR-REDEFINED-CLASS mechanism.
 (defmethod make-instances-obsolete ((class std-class))
   (let* ((owrapper (class-wrapper class))
-         (nwrapper (make-wrapper (wrapper-no-of-instance-slots owrapper)
+         (nwrapper (make-wrapper (layout-length owrapper)
                                  class)))
     (unless (class-finalized-p class)
       (if (class-has-a-forward-referenced-superclass-p class)
       ;; layout-depthoid).  Is there any way we can provide a useful
       ;; error message?  -- CSR, 2005-05-03
       (eq s *the-class-file-stream*) (eq s *the-class-string-stream*)
-      ;; TODO
+      ;; This probably shouldn't be mixed in with certain other
+      ;; classes, too, but it seems to work both with STANDARD-OBJECT
+      ;; and FUNCALLABLE-STANDARD-OBJECT
       (eq s *the-class-sequence*)))
 \f
 ;;; Some necessary methods for FORWARD-REFERENCED-CLASS