| William Bland ( @ 2007-07-17 09:05:00 |
| 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!