1 ;;;; miscellaneous tests of thread stuff
3 ;;;; This software is part of the SBCL system. See the README file for
6 ;;;; While most of SBCL is derived from the CMU CL system, the test
7 ;;;; files (like this one) were written from scratch after the fork
10 ;;;; This software is in the public domain and is provided with
11 ;;;; absoluely no warranty. See the COPYING and CREDITS files for
12 ;;;; more information.
16 (defpackage :thread-test
17 (:use
:cl
:sb-thread
:sb-ext
))
19 (in-package :thread-test
)
21 (use-package :test-util
)
23 (with-test (:name atomic-update
24 :skipped-on
'(not :sb-thread
))
25 (let ((x (cons :count
0))
26 (nthreads (ecase sb-vm
:n-word-bits
(32 100) (64 1000))))
27 (mapc #'sb-thread
:join-thread
29 collect
(sb-thread:make-thread
32 do
(atomic-update (cdr x
) #'1+)
34 (assert (equal x
`(:count
,@(* 1000 nthreads
))))))
36 (with-test (:name mutex-owner
)
37 ;; Make sure basics are sane on unithreaded ports as well
38 (let ((mutex (make-mutex)))
40 (assert (eq *current-thread
* (mutex-value mutex
)))
41 (handler-bind ((warning #'error
))
42 (release-mutex mutex
))
43 (assert (not (mutex-value mutex
)))))
45 ;;; Terminating a thread that's waiting for the terminal.
48 (let ((thread (make-thread (lambda ()
49 (sb-thread::get-foreground
)))))
51 (assert (thread-alive-p thread
))
52 (terminate-thread thread
)
54 (assert (not (thread-alive-p thread
))))
56 ;;; Condition-wait should not be interruptible under WITHOUT-INTERRUPTS
58 (with-test (:name
:without-interrupts
+condition-wait
59 :skipped-on
'(not :sb-thread
)
60 :fails-on
'(and :win32
:sb-futex
))
61 (let* ((lock (make-mutex))
62 (queue (make-waitqueue))
63 (thread (make-thread (lambda ()
64 (sb-sys:without-interrupts
66 (condition-wait queue lock
)))))))
68 (assert (thread-alive-p thread
))
69 (terminate-thread thread
)
71 (assert (thread-alive-p thread
))
72 (condition-notify queue
)
74 (assert (not (thread-alive-p thread
)))))
76 ;;; GRAB-MUTEX should not be interruptible under WITHOUT-INTERRUPTS
78 (with-test (:name
:without-interrupts
+grab-mutex
:skipped-on
'(not :sb-thread
))
79 (let* ((lock (make-mutex))
80 (bar (progn (grab-mutex lock
) nil
))
81 (thread (make-thread (lambda ()
82 (sb-sys:without-interrupts
86 (assert (thread-alive-p thread
))
87 (terminate-thread thread
)
89 (assert (thread-alive-p thread
))
92 (assert (not (thread-alive-p thread
)))
93 (assert (eq :aborted
(join-thread thread
:default
:aborted
)))
96 (with-test (:name
:parallel-find-class
:skipped-on
'(not :sb-thread
))
98 (threads (loop repeat
10
99 collect
(make-thread (lambda ()
102 do
(find-class (gensym) nil
))
103 (serious-condition ()
105 (mapcar #'sb-thread
:join-thread threads
)
106 (assert (not oops
))))
108 (with-test (:name
:semaphore-multiple-waiters
:skipped-on
'(not :sb-thread
))
109 (let ((semaphore (make-semaphore :name
"test sem")))
110 (labels ((make-readers (n i
)
112 (loop for r from
0 below n
114 (sb-thread:make-thread
116 (let ((sem semaphore
))
118 (sb-thread:wait-on-semaphore sem
))))
121 (make-writers (n readers i
)
122 (let ((j (* readers i
)))
123 (multiple-value-bind (k rem
) (truncate j n
)
126 (loop for w from
0 below n
128 (sb-thread:make-thread
130 (let ((sem semaphore
))
132 (sb-thread:signal-semaphore sem
))))
138 (multiple-value-bind (readers x
) (make-readers r n
)
139 (assert (= (length readers
) r
))
140 (multiple-value-bind (writers y
) (make-writers w r n
)
141 (assert (= (length writers
) w
))
143 (mapc #'sb-thread
:join-thread writers
)
144 (mapc #'sb-thread
:join-thread readers
)
145 (assert (zerop (sb-thread:semaphore-count semaphore
)))
150 (sb-ext:with-timeout
10
161 ;;;; Printing waitqueues
163 (with-test (:name
:waitqueue-circle-print
:skipped-on
'(not :sb-thread
))
164 (let* ((*print-circle
* nil
)
165 (lock (sb-thread:make-mutex
))
166 (wq (sb-thread:make-waitqueue
)))
167 (sb-thread:with-recursive-lock
(lock)
168 (sb-thread:condition-notify wq
))
169 ;; Used to blow stack due to recursive structure.
170 (assert (princ-to-string wq
))))
172 ;;;; SYMBOL-VALUE-IN-THREAD
174 (with-test (:name
:symbol-value-in-thread
.1)
175 (let ((* (cons t t
)))
176 (assert (eq * (symbol-value-in-thread '* *current-thread
*)))
177 (setf (symbol-value-in-thread '* *current-thread
*) 123)
178 (assert (= 123 (symbol-value-in-thread '* *current-thread
*)))
181 (with-test (:name
:symbol-value-in-thread
.2 :skipped-on
'(not :sb-thread
))
182 (let* ((parent *current-thread
*)
183 (semaphore (make-semaphore))
184 (child (make-thread (lambda ()
185 (wait-on-semaphore semaphore
)
186 (let ((old (symbol-value-in-thread 'this-is-new parent
)))
187 (setf (symbol-value-in-thread 'this-is-new parent
) :from-child
)
189 (progv '(this-is-new) '(42)
190 (signal-semaphore semaphore
)
191 (assert (= 42 (join-thread child
)))
192 (assert (eq :from-child
(symbol-value 'this-is-new
))))))
194 ;;; Disabled on Darwin due to deadlocks caused by apparent OS specific deadlocks,
195 ;;; wich _appear_ to be caused by malloc() and free() not being thread safe: an
196 ;;; interrupted malloc in one thread can apparently block a free in another.
197 (with-test (:name
:symbol-value-in-thread
.3
198 :skipped-on
'(not :sb-thread
))
199 (let* ((parent *current-thread
*)
200 (semaphore (make-semaphore))
202 (noise (make-thread (lambda ()
204 do
(setf * (make-array 1024))
205 ;; Busy-wait a bit so we don't TOTALLY flood the
206 ;; system with GCs: a GC occurring in the middle of
207 ;; S-V-I-T causes it to start over -- we want that
208 ;; to occur occasionally, but not _all_ the time.
209 (loop repeat
(random 128)
212 (dotimes (i #+(or win32 openbsd
) 2000
213 #-
(or win32 openbsd
) 15000)
214 (when (zerop (mod i
200))
217 (let* ((mom-mark (cons t t
))
218 (kid-mark (cons t t
))
219 (child (make-thread (lambda ()
220 (wait-on-semaphore semaphore
)
221 (let ((old (symbol-value-in-thread 'this-is-new parent
)))
222 (setf (symbol-value-in-thread 'this-is-new parent
)
223 (make-array 24 :initial-element kid-mark
))
225 (progv '(this-is-new) (list (make-array 24 :initial-element mom-mark
))
226 (signal-semaphore semaphore
)
227 (assert (eq mom-mark
(aref (join-thread child
) 0)))
228 (assert (eq kid-mark
(aref (symbol-value 'this-is-new
) 0))))))
230 (join-thread noise
)))
232 (with-test (:name
:symbol-value-in-thread
.4 :skipped-on
'(not :sb-thread
))
233 (let* ((parent *current-thread
*)
234 (semaphore (make-semaphore))
235 (child (make-thread (lambda ()
236 (wait-on-semaphore semaphore
)
237 (symbol-value-in-thread 'this-is-new parent nil
)))))
238 (signal-semaphore semaphore
)
239 (assert (equal '(nil nil
) (multiple-value-list (join-thread child
))))))
241 (with-test (:name
:symbol-value-in-thread
.5 :skipped-on
'(not :sb-thread
))
242 (let* ((parent *current-thread
*)
243 (semaphore (make-semaphore))
244 (child (make-thread (lambda ()
245 (wait-on-semaphore semaphore
)
247 (symbol-value-in-thread 'this-is-new parent
)
248 (symbol-value-in-thread-error (e)
249 (list (thread-error-thread e
)
251 (sb-thread::symbol-value-in-thread-error-info e
))))))))
252 (signal-semaphore semaphore
)
253 (assert (equal (list *current-thread
* 'this-is-new
(list :read
:unbound-in-thread
))
254 (join-thread child
)))))
256 (with-test (:name
:symbol-value-in-thread
.6 :skipped-on
'(not :sb-thread
))
257 (let* ((parent *current-thread
*)
258 (semaphore (make-semaphore))
260 (child (make-thread (lambda ()
261 (wait-on-semaphore semaphore
)
263 (setf (symbol-value-in-thread name parent
) t
)
264 (symbol-value-in-thread-error (e)
265 (list (thread-error-thread e
)
267 (sb-thread::symbol-value-in-thread-error-info e
))))))))
268 (signal-semaphore semaphore
)
269 (let ((res (join-thread child
))
270 (want (list *current-thread
* name
(list :write
:no-tls-value
))))
271 (unless (equal res want
)
272 (error "wanted ~S, got ~S" want res
)))))
274 (with-test (:name
:symbol-value-in-thread
.7 :skipped-on
'(not :sb-thread
))
275 (let ((child (make-thread (lambda ())))
276 (error-occurred nil
))
279 (symbol-value-in-thread 'this-is-new child
)
280 (symbol-value-in-thread-error (e)
281 (setf error-occurred t
)
282 (assert (eq child
(thread-error-thread e
)))
283 (assert (eq 'this-is-new
(cell-error-name e
)))
284 (assert (equal (list :read
:thread-dead
)
285 (sb-thread::symbol-value-in-thread-error-info e
)))))
286 (assert error-occurred
)))
288 (with-test (:name
:symbol-value-in-thread
.8 :skipped-on
'(not :sb-thread
))
289 (let ((child (make-thread (lambda ())))
290 (error-occurred nil
))
293 (setf (symbol-value-in-thread 'this-is-new child
) t
)
294 (symbol-value-in-thread-error (e)
295 (setf error-occurred t
)
296 (assert (eq child
(thread-error-thread e
)))
297 (assert (eq 'this-is-new
(cell-error-name e
)))
298 (assert (equal (list :write
:thread-dead
)
299 (sb-thread::symbol-value-in-thread-error-info e
)))))
300 (assert error-occurred
)))
302 (with-test (:name
:deadlock-detection
.1 :skipped-on
'(not :sb-thread
))
305 do
(flet ((test (ma mb sa sb
)
308 (sb-thread:with-mutex
(ma)
309 (sb-thread:signal-semaphore sa
)
310 (sb-thread:wait-on-semaphore sb
)
311 (sb-thread:with-mutex
(mb)
313 (sb-thread:thread-deadlock
(e)
316 (let* ((m1 (sb-thread:make-mutex
:name
"M1"))
317 (m2 (sb-thread:make-mutex
:name
"M2"))
318 (s1 (sb-thread:make-semaphore
:name
"S1"))
319 (s2 (sb-thread:make-semaphore
:name
"S2"))
320 (t1 (sb-thread:make-thread
(test m1 m2 s1 s2
) :name
"T1"))
321 (t2 (sb-thread:make-thread
(test m2 m1 s2 s1
) :name
"T2")))
322 ;; One will deadlock, and the other will then complete normally.
323 (let ((res (list (sb-thread:join-thread t1
)
324 (sb-thread:join-thread t2
))))
325 (assert (or (equal '(:deadlock
:ok
) res
)
326 (equal '(:ok
:deadlock
) res
))))))))
328 (with-test (:name
:deadlock-detection
.2 :skipped-on
'(not :sb-thread
))
329 (let* ((m1 (sb-thread:make-mutex
:name
"M1"))
330 (m2 (sb-thread:make-mutex
:name
"M2"))
331 (s1 (sb-thread:make-semaphore
:name
"S1"))
332 (s2 (sb-thread:make-semaphore
:name
"S2"))
333 (t1 (sb-thread:make-thread
335 (sb-thread:with-mutex
(m1)
336 (sb-thread:signal-semaphore s1
)
337 (sb-thread:wait-on-semaphore s2
)
338 (sb-thread:with-mutex
(m2)
343 (handler-bind ((sb-thread:thread-deadlock
346 ;; Make sure we can print the condition
348 (let ((*print-circle
* nil
))
349 (setf err
(princ-to-string e
)))
353 (assert (eq :ok
(sb-thread:with-mutex
(m2)
355 (sb-thread:signal-semaphore s2
)
356 (sb-thread:wait-on-semaphore s1
)
358 (sb-thread:with-mutex
(m1)
360 (assert (stringp err
)))
361 (assert (eq :ok
(sb-thread:join-thread t1
)))))
363 (with-test (:name
:deadlock-detection
.3 :skipped-on
'(not :sb-thread
))
364 (let* ((m1 (sb-thread:make-mutex
:name
"M1"))
365 (m2 (sb-thread:make-mutex
:name
"M2"))
366 (s1 (sb-thread:make-semaphore
:name
"S1"))
367 (s2 (sb-thread:make-semaphore
:name
"S2"))
368 (t1 (sb-thread:make-thread
370 (sb-thread:with-mutex
(m1)
371 (sb-thread:signal-semaphore s1
)
372 (sb-thread:wait-on-semaphore s2
)
373 (sb-thread:with-mutex
(m2)
376 ;; Currently we don't consider it a deadlock
377 ;; if there is a timeout in the chain.
378 (assert (eq :deadline
380 (sb-thread:with-mutex
(m2)
381 (sb-thread:signal-semaphore s2
)
382 (sb-thread:wait-on-semaphore s1
)
384 (sb-sys:with-deadline
(:seconds
0.1)
385 (sb-thread:with-mutex
(m1)
387 (sb-sys:deadline-timeout
()
389 (sb-thread:thread-deadlock
()
391 (assert (eq :ok
(join-thread t1
)))))
394 (with-test (:name
:pass-arguments-to-thread
)
395 (assert (= 3 (join-thread (make-thread #'+ :arguments
'(1 2))))))
398 (with-test (:name
:pass-atom-to-thread
)
399 (assert (= 1/2 (join-thread (make-thread #'/ :arguments
2)))))
402 (with-test (:name
:pass-nil-to-thread
)
403 (assert (= 1 (join-thread (make-thread #'* :arguments
'())))))
406 (with-test (:name
:pass-nothing-to-thread
)
407 (assert (= 1 (join-thread (make-thread #'*)))))
410 (with-test (:name
:pass-improper-list-to-thread
)
411 (multiple-value-bind (value error
)
412 (ignore-errors (make-thread #'+ :arguments
'(1 .
1)))
415 (assert (and (null value
)
418 (with-test (:name
(:wait-for
:basics
))
419 (assert (not (sb-ext:wait-for nil
:timeout
0.1)))
420 (assert (eql 42 (sb-ext:wait-for
42)))
422 (assert (eql 100 (sb-ext:wait-for
(when (= 100 (incf n
))
425 (with-test (:name
(:wait-for
:deadline
))
427 (sb-sys:with-deadline
(:seconds
10)
428 (assert (not (sb-ext:wait-for nil
:timeout
0.1)))
430 (assert (eq :deadline
432 (sb-sys:with-deadline
(:seconds
0.1)
433 (sb-ext:wait-for nil
:timeout
10)
435 (sb-sys:deadline-timeout
() :deadline
)))))
437 (with-test (:name
(:condition-wait
:timeout
:one-thread
))
438 (let ((mutex (make-mutex))
439 (waitqueue (make-waitqueue)))
440 (assert (not (with-mutex (mutex)
441 (condition-wait waitqueue mutex
:timeout
0.01))))))
443 (with-test (:name
(:condition-wait
:timeout
:many-threads
)
444 :skipped-on
'(not :sb-thread
))
445 (let* ((mutex (make-mutex))
446 (waitqueue (make-waitqueue))
447 (sem (make-semaphore))
453 (wait-on-semaphore sem
)
457 do
(or (condition-wait waitqueue mutex
:timeout
0.01)
458 (return-from thread nil
)))
459 (assert (eq t
(pop data
)))
462 do
(with-mutex (mutex)
464 (condition-notify waitqueue
)))
465 (signal-semaphore sem
100)
466 (let ((ok (count-if #'join-thread workers
)))
468 (error "Wanted 50, got ~S" ok
)))))
470 (with-test (:name
(:wait-on-semaphore
:timeout
:one-thread
))
471 (let ((sem (make-semaphore))
473 (signal-semaphore sem
10)
475 do
(when (wait-on-semaphore sem
:timeout
0.001)
479 (with-test (:name
(:wait-on-semaphore
:timeout
:many-threads
)
480 :skipped-on
'(not :sb-thread
))
481 (let* ((sem (make-semaphore))
484 (signal-semaphore sem
10)
488 (sleep (random 0.02))
489 (wait-on-semaphore sem
:timeout
0.5)))))))
491 do
(signal-semaphore sem
2))
492 (let ((ok (count-if #'join-thread threads
)))
494 (error "Wanted 20, got ~S" ok
)))))
496 (with-test (:name
(:join-thread
:timeout
)
497 :skipped-on
'(not :sb-thread
))
500 (join-thread (make-join-thread (lambda () (sleep 10))) :timeout
0.01)
501 (join-thread-error ()
503 (let ((cookie (cons t t
)))
505 (join-thread (make-join-thread (lambda () (sleep 10)))
509 (with-test (:name
(:semaphore-notification
:wait-on-semaphore
:lp-1038034
)
510 :skipped-on
'(not :sb-thread
)
511 :fails-on
:sb-thread
)
512 ;; Test robustness of semaphore acquisition and notification with
513 ;; asynchronous thread termination... Which we know is currently
516 (let ((sem (make-semaphore)))
517 ;; In CRITICAL, WAIT-ON-SEMAPHORE and SLEEP can be interrupted
518 ;; by TERMINATE-THREAD below. But the SIGNAL-SEMAPHORE cleanup
519 ;; cannot be interrupted.
520 (flet ((critical (sleep)
521 (let ((note (make-semaphore-notification)))
522 (sb-sys:without-interrupts
524 (sb-sys:with-local-interrupts
525 (wait-on-semaphore sem
:notification note
)
527 ;; Re-increment on exit if we decremented it.
528 (when (semaphore-notification-status note
)
529 (signal-semaphore sem
)))))))
530 ;; Create /parallel/ threads trying to acquire and then signal
531 ;; the semaphore. Try to asynchronously abort T2 just as T1 is
533 (destructuring-bind (t1 t2 t3
)
535 for sleep in
'(0.01
0.02 0.02)
536 collect
(make-thread #'critical
:arguments sleep
537 :name
(format nil
"T~A" i
)))
538 (signal-semaphore sem
)
541 (terminate-thread t2
))
542 (flet ((safe-join-thread (thread &key timeout
)
548 (error "Hang in (join-thread ~A) ?" thread
))))
549 (safe-join-thread t1
:timeout
10)
550 (safe-join-thread t3
:timeout
10)))))
551 (when (zerop (mod run
60))
557 (with-test (:name
(:semaphore-notification
:wait-on-semaphore
)
558 :skipped-on
'(not :sb-thread
))
559 (let ((sem (make-semaphore))
563 (let ((note (make-semaphore-notification)))
564 (sb-sys:without-interrupts
567 (sb-sys:with-local-interrupts
568 (wait-on-semaphore sem
:notification note
)
569 (sleep (random 0.1)))
571 ;; Re-increment on exit if we decremented it.
572 (when (semaphore-notification-status note
)
573 (signal-semaphore sem
))
574 ;; KLUDGE: Prevent interrupts after this point from
575 ;; unwinding us, so that we can reason about the counts.
577 (sb-thread::block-deferrable-signals
))))))
578 (let* ((threads (loop for i from
1 upto
100
579 collect
(make-join-thread #'critical
:name
(format nil
"T~A" i
))))
582 (interruptor (make-thread (lambda ()
585 (dolist (thread threads
)
590 (terminate-thread thread
)))
593 (setf x
(not x
))))))))
594 (signal-semaphore sem
)
596 (join-thread interruptor
)
597 (mapc #'join-thread safe
)
598 (let ((k (count-if (lambda (th)
599 (join-thread th
:default nil
))
601 (assert (= n
(+ k
(length safe
))))
604 (with-test (:name
(:semaphore-notification
:try-sempahore
)
605 :skipped-on
'(not :sb-thread
))
606 (let* ((sem (make-semaphore))
607 (note (make-semaphore-notification)))
608 (try-semaphore sem
1 note
)
609 (assert (not (semaphore-notification-status note
)))
610 (signal-semaphore sem
)
611 (try-semaphore sem
1 note
)
612 (assert (semaphore-notification-status note
))))
614 (with-test (:name
(:return-from-thread
:normal-thread
)
615 :skipped-on
'(not :sb-thread
))
616 (let* ((thread (make-thread (lambda ()
617 (return-from-thread (values 1 2 3))
619 (values (multiple-value-list (join-thread thread
))))
620 (unless (equal (list 1 2 3) values
)
621 (error "got ~S, wanted (1 2 3)" values
))))
623 (with-test (:name
(:return-from-thread
:main-thread
))
624 (assert (main-thread-p))
627 (return-from-thread t
)
631 (with-test (:name
(:abort-thread
:normal-thread
)
632 :skipped-on
'(not :sb-thread
))
633 (let ((thread (make-thread (lambda ()
636 (assert (eq :aborted
! (join-thread thread
:default
:aborted
!)))))
638 (with-test (:name
(:abort-thread
:main-thread
))
639 (assert (main-thread-p))
646 ;; SB-THREAD:MAKE-THREAD used to lock SB-THREAD:*MAKE-THREAD-LOCK*
647 ;; before entering WITHOUT-INTERRUPTS. When a thread which was
648 ;; executing SB-THREAD:MAKE-THREAD was interrupted with code which
649 ;; also called SB-THREAD:MAKE-THREAD, it could happen that the first
650 ;; thread already owned SB-THREAD:*MAKE-THREAD-LOCK* and the
651 ;; interrupting code thus made a recursive lock attempt.
653 ;; See (:TIMER :DISPATCH-THREAD :MAKE-THREAD :BUG-1180102) in
654 ;; timer.impure.lisp.
655 (with-test (:name
(make-thread :interrupt-with make-thread
:bug-1180102
)
656 :skipped-on
'(not :sb-thread
))
662 (parent *current-thread
*))
668 (lambda () (push (make-thread (lambda ())) threads
)))))
670 (push (make-thread (lambda ())) threads
))
671 (mapc #'join-thread threads
))