William Bland ([info]abstractstuff) wrote,
@ 2007-07-17 09:05:00
Previous Entry  Add to memories!  Tell a Friend!  Next Entry
Entry tags:computers, lisp

Using the MOP to detect non-threadsafe code
I don't like threads much. They aren't a good match for the way my brain works, for some reason. In the past I've written servers for dealing with large numbers of connections (several tens of thousands), but those were single-threaded servers using non-blocking i/o - an approach that sits more easily in my mind.

Recently though, I've been working with Hunchentoot, which is an awesome web server and, of course, is threaded.

So, I've had to try to improve the thread safety of my code.

Here's something I came up with, using the CLOS MOP to help me do this:

(defclass mutexed ()
  ((mutex
    :initform (sb-thread:make-mutex)
    :reader mutex-of)
   (initialized
    :initform nil
    :reader initialized-of)))

(defmethod initialize-instance :after ((object mutexed) &rest initargs)
  (declare (ignore initargs))
  (setf (slot-value object 'initialized) t))

(defun check-access (mode object slotd)
  (unless (or (equal (sb-mop:slot-definition-name slotd) 'mutex)
	      (equal (sb-mop:slot-definition-name slotd) 'initialized)
	      (not (initialized-of object))
	      (equal (sb-thread:mutex-value (mutex-of object))
		     sb-thread:*current-thread*))
    (format t ";; WARNING: unlocked ~A: slot ~S of ~S by thread ~S~%"
	    mode
	    (sb-mop:slot-definition-name slotd)
	    object
	    sb-thread:*current-thread*)))

(defmethod sb-mop:slot-value-using-class :after (class (object mutexed) slotd)
  (check-access "read" object slotd))

(defmethod (setf sb-mop:slot-value-using-class) :after (new-value class (object mutexed) slotd)
  (check-access "write" object slotd))

;; --- example follows


(defclass foo (mutexed)
  ((bar
    :initform nil
    :accessor bar-of)))

(defun test ()
  (let* ((object (make-instance 'foo))
	 (mutex (mutex-of object)))
    (setf (bar-of object) "bad")
    (format t "unlocked: ~S~%" (bar-of object))
    (sb-thread:with-mutex (mutex)
      (setf (bar-of object) "better")
      (format t "locked: ~S~%" (bar-of object)))))

In case it's not clear, the idea is that you subclass mutexed and ensure that every thread acquires an object's mutex before reading or writing any slots (I wrap the mutex acquisition in a little with- macro). The MOP methods above should warn you if any code does slot access without holding the mutex.

It seems to work out pretty well so far. This was my first stab at using the MOP for anything though - feel free to jump in and comment if I did something stupid!


(Post a new comment)

Not portable...
(Anonymous)
2007-07-17 05:24 pm UTC (link)
Hi,

Unfortunately, your code is not portable. The CLOS MOP specification says (albeit in somewhat disguised wording) that you are not allowed to define methods on slot-value-using-class and friends where you specialize only the second (object) argument. You have to specialize at least either the first (class) or the third (slot-definition) argument. For (setf slot-value-using-class), you have to specialize at least either the second or the fourth argument.

In practice, this means that you have to define your own metaclass whenever you want to modify slot access. There are some conceptual reasons for this, but also efficiency may matter: With your code, you theoretically slow down all slot accesses, not only those of your own classes.

For the sake of completeness, your code will probably not work in MCL and OpenMCL, at least.

Pascal Costanza

(Reply to this)(Thread)

Re: Not portable...
[info]abstractstuff
2007-07-17 05:33 pm UTC (link)
Good to know, thanks Pascal!

(Reply to this)(Parent)


Create an Account
Forgot your login?
Login w/ OpenID
English • Español • Deutsch • Русский…