+ (eq *current-thread* value)))
+ (let ((l (make-spinlock :name "rec")))
+ (assert (eql (spinlock-value l) nil) nil "1")
+ (with-recursive-spinlock (l)
+ (assert (ours-p (spinlock-value l)) nil "3")
+ (with-recursive-spinlock (l)
+ (assert (ours-p (spinlock-value l)) nil "4"))
+ (assert (ours-p (spinlock-value l)) nil "5"))
+ (assert (eql (spinlock-value l) nil) nil "6"))))
+
+(with-test (:name (:mutex :nesting-mutex-and-recursive-lock))
+ (let ((l (make-mutex :name "a mutex")))
+ (with-mutex (l)
+ (with-recursive-lock (l)))))
+
+(with-test (:name (:spinlock :nesting-spinlock-and-recursive-spinlock))
+ (let ((l (make-spinlock :name "a spinlock")))
+ (with-spinlock (l)
+ (with-recursive-spinlock (l)))))
+
+(with-test (:name (:spinlock :more-basics))
+ (let ((l (make-spinlock :name "spinlock")))
+ (assert (eql (spinlock-value l) nil) ((spinlock-value l))
+ "spinlock not free (1)")
+ (with-spinlock (l)
+ (assert (eql (spinlock-value l) *current-thread*) ((spinlock-value l))
+ "spinlock not taken"))
+ (assert (eql (spinlock-value l) nil) ((spinlock-value l))
+ "spinlock not free (2)")))
+
+;; test that SLEEP actually sleeps for at least the given time, even
+;; if interrupted by another thread exiting/a gc/anything
+(with-test (:name (:sleep :continue-sleeping-after-interrupt))
+ (let ((start-time (get-universal-time)))
+ (make-thread (lambda () (sleep 1) (sb-ext:gc :full t)))
+ (sleep 5)
+ (assert (>= (get-universal-time) (+ 5 start-time)))))
+
+
+(with-test (:name (:condition-wait :basics-1))
+ (let ((queue (make-waitqueue :name "queue"))
+ (lock (make-mutex :name "lock"))
+ (n 0))
+ (labels ((in-new-thread ()
+ (with-mutex (lock)
+ (assert (eql (mutex-value lock) *current-thread*))
+ (format t "~A got mutex~%" *current-thread*)
+ ;; now drop it and sleep
+ (condition-wait queue lock)
+ ;; after waking we should have the lock again
+ (assert (eql (mutex-value lock) *current-thread*))
+ (assert (eql n 1))
+ (decf n))))
+ (make-thread #'in-new-thread)
+ (sleep 2) ; give it a chance to start
+ ;; check the lock is free while it's asleep
+ (format t "parent thread ~A~%" *current-thread*)
+ (assert (eql (mutex-value lock) nil))
+ (with-mutex (lock)
+ (incf n)
+ (condition-notify queue))
+ (sleep 1))))
+
+(with-test (:name (:condition-wait :basics-2))
+ (let ((queue (make-waitqueue :name "queue"))
+ (lock (make-mutex :name "lock")))
+ (labels ((ours-p (value)
+ (eq *current-thread* value))
+ (in-new-thread ()
+ (with-recursive-lock (lock)
+ (assert (ours-p (mutex-value lock)))
+ (format t "~A got mutex~%" (mutex-value lock))
+ ;; now drop it and sleep
+ (condition-wait queue lock)
+ ;; after waking we should have the lock again
+ (format t "woken, ~A got mutex~%" (mutex-value lock))
+ (assert (ours-p (mutex-value lock))))))
+ (make-thread #'in-new-thread)
+ (sleep 2) ; give it a chance to start
+ ;; check the lock is free while it's asleep
+ (format t "parent thread ~A~%" *current-thread*)
+ (assert (eql (mutex-value lock) nil))
+ (with-recursive-lock (lock)
+ (condition-notify queue))
+ (sleep 1))))
+
+(with-test (:name (:mutex :contention))
+ (let ((mutex (make-mutex :name "contended")))
+ (labels ((run ()
+ (let ((me *current-thread*))
+ (dotimes (i 100)
+ (with-mutex (mutex)
+ (sleep .03)
+ (assert (eql (mutex-value mutex) me)))
+ (assert (not (eql (mutex-value mutex) me))))
+ (format t "done ~A~%" *current-thread*))))
+ (let ((kid1 (make-thread #'run))
+ (kid2 (make-thread #'run)))
+ (format t "contention ~A ~A~%" kid1 kid2)
+ (wait-for-threads (list kid1 kid2))))))
+
+;;; GRAB-MUTEX
+
+(with-test (:name (:grab-mutex :waitp nil))
+ (let ((m (make-mutex)))
+ (with-mutex (m)
+ (assert (null (join-thread (make-thread
+ #'(lambda ()
+ (grab-mutex m :waitp nil)))))))))
+
+(with-test (:name (:grab-mutex :timeout :acquisition-fail))
+ #+sb-lutex
+ (error "Mutex timeout not supported here.")
+ (let ((m (make-mutex))
+ (w (make-semaphore)))
+ (with-mutex (m)
+ (let ((th (make-thread
+ #'(lambda ()
+ (prog1
+ (grab-mutex m :timeout 0.1)
+ (signal-semaphore w))))))
+ ;; Wait for it to -- otherwise the detect the deadlock chain
+ ;; from JOIN-THREAD.
+ (wait-on-semaphore w)
+ (assert (null (join-thread th)))))))
+
+(with-test (:name (:grab-mutex :timeout :acquisition-success))
+ #+sb-lutex
+ (error "Mutex timeout not supported here.")
+ (let ((m (make-mutex))
+ (child))
+ (with-mutex (m)
+ (setq child (make-thread #'(lambda () (grab-mutex m :timeout 1.0))))
+ (sleep 0.2))
+ (assert (eq (join-thread child) 't))))
+
+(with-test (:name (:grab-mutex :timeout+deadline))
+ #+sb-lutex
+ (error "Mutex timeout not supported here.")
+ (let ((m (make-mutex))
+ (w (make-semaphore)))
+ (with-mutex (m)
+ (let ((th (make-thread #'(lambda ()
+ (sb-sys:with-deadline (:seconds 0.0)
+ (handler-case
+ (grab-mutex m :timeout 0.0)
+ (sb-sys:deadline-timeout ()
+ (signal-semaphore w)
+ :deadline)))))))
+ (wait-on-semaphore w)
+ (assert (eq (join-thread th) :deadline))))))
+
+(with-test (:name (:grab-mutex :waitp+deadline))
+ #+sb-lutex
+ (error "Mutex timeout not supported here.")
+ (let ((m (make-mutex)))
+ (with-mutex (m)
+ (assert (eq (join-thread
+ (make-thread #'(lambda ()
+ (sb-sys:with-deadline (:seconds 0.0)
+ (handler-case
+ (grab-mutex m :waitp nil)
+ (sb-sys:deadline-timeout ()
+ :deadline))))))
+ 'nil)))))
+
+;;; semaphores
+
+(defmacro raises-timeout-p (&body body)
+ `(handler-case (progn (progn ,@body) nil)
+ (sb-ext:timeout () t)))
+
+(with-test (:name (:semaphore :wait-forever))
+ (let ((sem (make-semaphore :count 0)))
+ (assert (raises-timeout-p
+ (sb-ext:with-timeout 0.1
+ (wait-on-semaphore sem))))))
+
+(with-test (:name (:semaphore :initial-count))
+ (let ((sem (make-semaphore :count 1)))
+ (sb-ext:with-timeout 0.1
+ (wait-on-semaphore sem))))
+
+(with-test (:name (:semaphore :wait-then-signal))
+ (let ((sem (make-semaphore))
+ (signalled-p nil))
+ (make-thread (lambda ()
+ (sleep 0.1)
+ (setq signalled-p t)
+ (signal-semaphore sem)))
+ (wait-on-semaphore sem)
+ (assert signalled-p)))
+
+(with-test (:name (:semaphore :signal-then-wait))
+ (let ((sem (make-semaphore))
+ (signalled-p nil))
+ (make-thread (lambda ()
+ (signal-semaphore sem)
+ (setq signalled-p t)))
+ (loop until signalled-p)
+ (wait-on-semaphore sem)
+ (assert signalled-p)))
+
+(defun test-semaphore-multiple-signals (wait-on-semaphore)
+ (let* ((sem (make-semaphore :count 5))
+ (threads (loop repeat 20 collecting
+ (make-thread (lambda ()
+ (funcall wait-on-semaphore sem))))))
+ (flet ((count-live-threads ()
+ (count-if #'thread-alive-p threads)))
+ (sleep 0.5)
+ (assert (= 15 (count-live-threads)))
+ (signal-semaphore sem 10)
+ (sleep 0.5)
+ (assert (= 5 (count-live-threads)))
+ (signal-semaphore sem 3)
+ (sleep 0.5)
+ (assert (= 2 (count-live-threads)))
+ (signal-semaphore sem 4)
+ (sleep 0.5)
+ (assert (= 0 (count-live-threads))))))
+
+(with-test (:name (:semaphore :multiple-signals))
+ (test-semaphore-multiple-signals #'wait-on-semaphore))
+
+(with-test (:name (:try-semaphore :trivial-fail))
+ (assert (eq (try-semaphore (make-semaphore :count 0)) 'nil)))
+
+(with-test (:name (:try-semaphore :trivial-success))
+ (let ((sem (make-semaphore :count 1)))
+ (assert (try-semaphore sem))
+ (assert (zerop (semaphore-count sem)))))
+
+(with-test (:name (:try-semaphore :trivial-fail :n>1))
+ (assert (eq (try-semaphore (make-semaphore :count 1) 2) 'nil)))
+
+(with-test (:name (:try-semaphore :trivial-success :n>1))
+ (let ((sem (make-semaphore :count 10)))
+ (assert (try-semaphore sem 5))
+ (assert (try-semaphore sem 5))
+ (assert (zerop (semaphore-count sem)))))
+
+(with-test (:name (:try-semaphore :emulate-wait-on-semaphore))
+ (flet ((busy-wait-on-semaphore (sem)
+ (loop until (try-semaphore sem) do (sleep 0.001))))
+ (test-semaphore-multiple-signals #'busy-wait-on-semaphore)))
+
+;;; Here we test that interrupting TRY-SEMAPHORE does not leave a
+;;; semaphore in a bad state.
+(with-test (:name (:try-semaphore :interrupt-safe))
+ (flet ((make-threads (count fn)
+ (loop repeat count collect (make-thread fn)))
+ (kill-thread (thread)
+ (when (thread-alive-p thread)
+ (ignore-errors (terminate-thread thread))))
+ (count-live-threads (threads)
+ (count-if #'thread-alive-p threads)))
+ ;; WAITERS will already be waiting on the semaphore while
+ ;; threads-being-interrupted will perform TRY-SEMAPHORE on that
+ ;; semaphore, and MORE-WAITERS are new threads trying to wait on
+ ;; the semaphore during the interruption-fire.
+ (let* ((sem (make-semaphore :count 100))
+ (waiters (make-threads 20 #'(lambda ()
+ (wait-on-semaphore sem))))
+ (triers (make-threads 40 #'(lambda ()
+ (sleep (random 0.01))
+ (try-semaphore sem (1+ (random 5))))))
+ (more-waiters
+ (loop repeat 10
+ do (kill-thread (nth (random 40) triers))
+ collect (make-thread #'(lambda () (wait-on-semaphore sem)))
+ do (kill-thread (nth (random 40) triers)))))
+ (sleep 0.5)
+ ;; Now ensure that the waiting threads will all be waked up,
+ ;; i.e. that the semaphore is still working.
+ (loop repeat (+ (count-live-threads waiters)
+ (count-live-threads more-waiters))
+ do (signal-semaphore sem))
+ (sleep 0.5)
+ (assert (zerop (count-live-threads triers)))
+ (assert (zerop (count-live-threads waiters)))
+ (assert (zerop (count-live-threads more-waiters))))))
+
+
+
+(format t "~&semaphore tests done~%")