bordeaux-threads-test.lisp - clic - Clic is an command line interactive client for gopher written in Common LISP (HTM) git clone git://bitreich.org/clic/ git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/clic/ (DIR) Log (DIR) Files (DIR) Refs (DIR) Tags (DIR) README (DIR) LICENSE --- bordeaux-threads-test.lisp (9117B) --- 1 #| 2 Copyright 2006,2007 Greg Pfeil 3 4 Distributed under the MIT license (see LICENSE file) 5 |# 6 7 (defpackage bordeaux-threads/test 8 (:use #:cl #:bordeaux-threads #:fiveam) 9 (:shadow #:with-timeout)) 10 11 (in-package #:bordeaux-threads/test) 12 13 (def-suite :bordeaux-threads) 14 (def-fixture using-lock () 15 (let ((lock (make-lock))) 16 (&body))) 17 (in-suite :bordeaux-threads) 18 19 (test should-have-current-thread 20 (is (current-thread))) 21 22 (test current-thread-identity 23 (let* ((box (list nil)) 24 (thread (make-thread (lambda () 25 (setf (car box) (current-thread)))))) 26 (join-thread thread) 27 (is (eql (car box) thread)))) 28 29 (test join-thread-return-value 30 (is (eql 0 (join-thread (make-thread (lambda () 0)))))) 31 32 (test should-identify-threads-correctly 33 (is (threadp (current-thread))) 34 (is (threadp (make-thread (lambda () t) :name "foo"))) 35 (is (not (threadp (make-lock))))) 36 37 (test should-retrieve-thread-name 38 (is (equal "foo" (thread-name (make-thread (lambda () t) :name "foo"))))) 39 40 (test interrupt-thread 41 (let* ((box (list nil)) 42 (thread (make-thread (lambda () 43 (setf (car box) 44 (catch 'new-thread 45 (sleep 60) 46 'not-interrupted)))))) 47 (sleep 1) 48 (interrupt-thread thread (lambda () 49 (throw 'new-thread 'interrupted))) 50 (join-thread thread) 51 (is (eql 'interrupted (car box))))) 52 53 (test should-lock-without-contention 54 (with-fixture using-lock () 55 (is (acquire-lock lock t)) 56 (release-lock lock) 57 (is (acquire-lock lock nil)) 58 (release-lock lock))) 59 60 (defun set-equal (set-a set-b) 61 (and (null (set-difference set-a set-b)) 62 (null (set-difference set-b set-a)))) 63 64 (test default-special-bindings 65 (locally (declare (special *a* *c*)) 66 (let* ((the-as 50) (the-bs 150) (*b* 42) 67 some-a some-b some-other-a some-other-b 68 (*default-special-bindings* 69 `((*a* . (funcall ,(lambda () (incf the-as)))) 70 (*b* . (funcall ,(lambda () (incf the-bs)))) 71 ,@*default-special-bindings*)) 72 (threads (list (make-thread 73 (lambda () 74 (setf some-a *a* some-b *b*))) 75 (make-thread 76 (lambda () 77 (setf some-other-a *a* 78 some-other-b *b*)))))) 79 (declare (special *b*)) 80 (thread-yield) 81 (is (not (boundp '*a*))) 82 (loop while (some #'thread-alive-p threads) 83 do (thread-yield)) 84 (is (set-equal (list some-a some-other-a) '(51 52))) 85 (is (set-equal (list some-b some-other-b) '(151 152))) 86 (is (not (boundp '*a*)))))) 87 88 89 (defparameter *shared* 0) 90 (defparameter *lock* (make-lock)) 91 92 (test should-have-thread-interaction 93 ;; this simple test generates N process. Each process grabs and 94 ;; releases the lock until SHARED has some value, it then 95 ;; increments SHARED. the outer code first sets shared 1 which 96 ;; gets the thing running and then waits for SHARED to reach some 97 ;; value. this should, i think, stress test locks. 98 (setf *shared* 0) 99 (flet ((worker (i) 100 (loop 101 do (with-lock-held (*lock*) 102 (when (= i *shared*) 103 (incf *shared*) 104 (return))) 105 (thread-yield) 106 (sleep 0.001)))) 107 (let* ((procs (loop 108 for i from 1 upto 2 109 ;; create a new binding to protect against implementations that 110 ;; mutate instead of binding the loop variable 111 collect (let ((i i)) 112 (make-thread (lambda () 113 (funcall #'worker i)) 114 :name (format nil "Proc #~D" i)))))) 115 (with-lock-held (*lock*) 116 (incf *shared*)) 117 (block test 118 (loop 119 until (with-lock-held (*lock*) 120 (= (1+ (length procs)) *shared*)) 121 do (with-lock-held (*lock*) 122 (is (>= (1+ (length procs)) *shared*))) 123 (thread-yield) 124 (sleep 0.001)))))) 125 126 127 (defparameter *condition-variable* (make-condition-variable)) 128 129 (test condition-variable 130 (setf *shared* 0) 131 (flet ((worker (i) 132 (with-lock-held (*lock*) 133 (loop 134 until (= i *shared*) 135 do (condition-wait *condition-variable* *lock*)) 136 (incf *shared*)) 137 (condition-notify *condition-variable*))) 138 (let ((num-procs 100)) 139 (dotimes (i num-procs) 140 ;; create a new binding to protect against implementations that 141 ;; mutate instead of binding the loop variable 142 (let ((i i)) 143 (make-thread (lambda () 144 (funcall #'worker i)) 145 :name (format nil "Proc #~D" i)))) 146 (with-lock-held (*lock*) 147 (loop 148 until (= num-procs *shared*) 149 do (condition-wait *condition-variable* *lock*))) 150 (is (equal num-procs *shared*))))) 151 152 ;; Generally safe sanity check for the locks and single-notify 153 #+(and lispworks (not lispworks6)) 154 (test condition-variable-lw 155 (let ((condition-variable (make-condition-variable :name "Test")) 156 (test-lock (make-lock)) 157 (completed nil)) 158 (dotimes (id 6) 159 (let ((id id)) 160 (make-thread (lambda () 161 (with-lock-held (test-lock) 162 (condition-wait condition-variable test-lock) 163 (push id completed) 164 (condition-notify condition-variable)))))) 165 (sleep 2) 166 (if completed 167 (print "Failed: Premature passage through condition-wait") 168 (print "Successfully waited on condition")) 169 (condition-notify condition-variable) 170 (sleep 2) 171 (if (and completed 172 (eql (length completed) 6) 173 (equal (sort completed #'<) 174 (loop for id from 0 to 5 collect id))) 175 (print "Success: All elements notified") 176 (print (format nil "Failed: Of 6 expected elements, only ~A proceeded" completed))) 177 (bt::with-cv-access condition-variable 178 (if (and 179 (not (or (car wait-tlist) (cdr wait-tlist))) 180 (zerop (hash-table-count wait-hash)) 181 (zerop (hash-table-count unconsumed-notifications))) 182 (print "Success: condition variable restored to initial state") 183 (print "Error: condition variable retains residue from completed waiters"))) 184 (setq completed nil) 185 (dotimes (id 6) 186 (let ((id id)) 187 (make-thread (lambda () 188 (with-lock-held (test-lock) 189 (condition-wait condition-variable test-lock) 190 (push id completed)))))) 191 (sleep 2) 192 (condition-notify condition-variable) 193 (sleep 2) 194 (if (= (length completed) 1) 195 (print "Success: Notify-single only notified a single waiter to restart") 196 (format t "Failure: Notify-single restarted ~A items" (length completed))) 197 (condition-notify condition-variable) 198 (sleep 2) 199 (if (= (length completed) 2) 200 (print "Success: second Notify-single only notified a single waiter to restart") 201 (format t "Failure: Two Notify-singles restarted ~A items" (length completed))) 202 (loop for i from 0 to 5 do (condition-notify condition-variable)) 203 (print "Note: In the case of any failures, assume there are outstanding waiting threads") 204 (values))) 205 206 #+(or abcl allegro clisp clozure ecl lispworks6 sbcl scl) 207 (test condition-wait-timeout 208 (let ((lock (make-lock)) 209 (cvar (make-condition-variable)) 210 (flag nil)) 211 (make-thread (lambda () (sleep 0.4) (setf flag t))) 212 (with-lock-held (lock) 213 (condition-wait cvar lock :timeout 0.2) 214 (is (null flag)) 215 (sleep 0.4) 216 (is (eq t flag))))) 217 218 (test semaphore-signal 219 (let ((sem (make-semaphore))) 220 (make-thread (lambda () (sleep 0.4) (signal-semaphore sem))) 221 (is (not (null (wait-on-semaphore sem)))))) 222 223 (test semaphore-signal-n-of-m 224 (let* ((sem (make-semaphore :count 1)) 225 (lock (make-lock)) 226 (count 0) 227 (waiter (lambda () 228 (wait-on-semaphore sem) 229 (with-lock-held (lock) (incf count))))) 230 (make-thread (lambda () (sleep 0.2) (signal-semaphore sem :count 3))) 231 (dotimes (v 5) (make-thread waiter)) 232 (sleep 0.3) 233 (is (= count 4)) 234 ;; release other waiters 235 (signal-semaphore sem :count 10) 236 (sleep 0.1) 237 (is (= count 5)))) 238 239 (test semaphore-wait-timeout 240 (let ((sem (make-semaphore)) 241 (flag nil)) 242 (make-thread (lambda () (sleep 0.4) (setf flag t))) 243 (is (null (wait-on-semaphore sem :timeout 0.2))) 244 (is (null flag)) 245 (sleep 0.4) 246 (is (eq t flag)))) 247 248 (test semaphore-typed 249 (is (typep (bt:make-semaphore) 'bt:semaphore)) 250 (is (bt:semaphore-p (bt:make-semaphore))) 251 (is (null (bt:semaphore-p (bt:make-lock)))))