0.9.5.20:
[sbcl.git] / tests / threads.impure.lisp
1 ;;;; miscellaneous tests of thread stuff
2
3 ;;;; This software is part of the SBCL system. See the README file for
4 ;;;; more information.
5 ;;;;
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
8 ;;;; from CMU CL.
9 ;;;
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.
13
14 (in-package "SB-THREAD") ; this is white-box testing, really
15
16 (use-package :test-util)
17
18 (defun wait-for-threads (threads)
19   (loop while (some #'sb-thread:thread-alive-p threads) do (sleep 0.01)))
20
21 (assert (eql 1 (length (list-all-threads))))
22
23 (assert (eq *current-thread*
24             (find (thread-name *current-thread*) (list-all-threads)
25                   :key #'thread-name :test #'equal)))
26
27 (assert (thread-alive-p *current-thread*))
28
29 (let ((a 0))
30   (interrupt-thread *current-thread* (lambda () (setq a 1)))
31   (assert (eql a 1)))
32
33 (let ((spinlock (make-spinlock)))
34   (with-spinlock (spinlock)))
35
36 (let ((mutex (make-mutex)))
37   (with-mutex (mutex)
38     mutex))
39
40 #-sb-thread (sb-ext:quit :unix-status 104)
41
42 (let ((old-threads (list-all-threads))
43       (thread (make-thread (lambda ()
44                              (assert (find *current-thread* *all-threads*))
45                              (sleep 2))))
46       (new-threads (list-all-threads)))
47   (assert (thread-alive-p thread))
48   (assert (eq thread (first new-threads)))
49   (assert (= (1+ (length old-threads)) (length new-threads)))
50   (sleep 3)
51   (assert (not (thread-alive-p thread))))
52
53 ;;; We had appalling scaling properties for a while.  Make sure they
54 ;;; don't reappear.
55 (defun scaling-test (function &optional (nthreads 5))
56   "Execute FUNCTION with NTHREADS lurking to slow it down."
57   (let ((queue (sb-thread:make-waitqueue))
58         (mutex (sb-thread:make-mutex)))
59     ;; Start NTHREADS idle threads.
60     (dotimes (i nthreads)
61       (sb-thread:make-thread (lambda ()
62                                (with-mutex (mutex)
63                                  (sb-thread:condition-wait queue mutex))
64                                (sb-ext:quit))))
65     (let ((start-time (get-internal-run-time)))
66       (funcall function)
67       (prog1 (- (get-internal-run-time) start-time)
68         (sb-thread:condition-broadcast queue)))))
69 (defun fact (n)
70   "A function that does work with the CPU."
71   (if (zerop n) 1 (* n (fact (1- n)))))
72 (let ((work (lambda () (fact 15000))))
73   (let ((zero (scaling-test work 0))
74         (four (scaling-test work 4)))
75     ;; a slightly weak assertion, but good enough for starters.
76     (assert (< four (* 1.5 zero)))))
77
78 ;;; For one of the interupt-thread tests, we want a foreign function
79 ;;; that does not make syscalls
80
81 (with-open-file (o "threads-foreign.c" :direction :output :if-exists :supersede)
82   (format o "void loop_forever() { while(1) ; }~%"))
83 (sb-ext:run-program
84  "cc"
85  (or #+linux '("-shared" "-o" "threads-foreign.so" "threads-foreign.c")
86      (error "Missing shared library compilation options for this platform"))
87  :search t)
88 (sb-alien:load-shared-object "threads-foreign.so")
89 (sb-alien:define-alien-routine loop-forever sb-alien:void)
90
91
92 ;;; elementary "can we get a lock and release it again"
93 (let ((l (make-mutex :name "foo"))
94       (p *current-thread*))
95   (assert (eql (mutex-value l) nil) nil "1")
96   (sb-thread:get-mutex l)
97   (assert (eql (mutex-value l) p) nil "3")
98   (sb-thread:release-mutex l)
99   (assert (eql (mutex-value l) nil) nil "5"))
100
101 (labels ((ours-p (value)
102            (sb-vm:control-stack-pointer-valid-p
103             (sb-sys:int-sap (sb-kernel:get-lisp-obj-address value)))))
104   (let ((l (make-mutex :name "rec")))
105     (assert (eql (mutex-value l) nil) nil "1")
106     (sb-thread:with-recursive-lock (l)
107       (assert (ours-p (mutex-value l)) nil "3")
108       (sb-thread:with-recursive-lock (l)
109         (assert (ours-p (mutex-value l)) nil "4"))
110       (assert (ours-p (mutex-value l)) nil "5"))
111     (assert (eql (mutex-value l) nil) nil "6")))
112
113 (let ((l (make-spinlock :name "spinlock"))
114       (p *current-thread*))
115   (assert (eql (spinlock-value l) 0) nil "1")
116   (with-spinlock (l)
117     (assert (eql (spinlock-value l) p) nil "2"))
118   (assert (eql (spinlock-value l) 0) nil "3"))
119
120 ;; test that SLEEP actually sleeps for at least the given time, even
121 ;; if interrupted by another thread exiting/a gc/anything
122 (let ((start-time (get-universal-time)))
123   (make-thread (lambda () (sleep 1) (sb-ext:gc :full t)))
124   (sleep 5)
125   (assert (>= (get-universal-time) (+ 5 start-time))))
126
127
128 (let ((queue (make-waitqueue :name "queue"))
129       (lock (make-mutex :name "lock"))
130       (n 0))
131   (labels ((in-new-thread ()
132              (with-mutex (lock)
133                (assert (eql (mutex-value lock) *current-thread*))
134                (format t "~A got mutex~%" *current-thread*)
135                ;; now drop it and sleep
136                (condition-wait queue lock)
137                ;; after waking we should have the lock again
138                (assert (eql (mutex-value lock) *current-thread*))
139                (assert (eql n 1))
140                (decf n))))
141     (make-thread #'in-new-thread)
142     (sleep 2)                           ; give it  a chance to start
143     ;; check the lock is free while it's asleep
144     (format t "parent thread ~A~%" *current-thread*)
145     (assert (eql (mutex-value lock) nil))
146     (with-mutex (lock)
147       (incf n)
148       (condition-notify queue))
149     (sleep 1)))
150
151 (let ((queue (make-waitqueue :name "queue"))
152       (lock (make-mutex :name "lock")))
153   (labels ((ours-p (value)
154              (sb-vm:control-stack-pointer-valid-p
155               (sb-sys:int-sap (sb-kernel:get-lisp-obj-address value))))
156            (in-new-thread ()
157              (with-recursive-lock (lock)
158                (assert (ours-p (mutex-value lock)))
159                (format t "~A got mutex~%" (mutex-value lock))
160                ;; now drop it and sleep
161                (condition-wait queue lock)
162                ;; after waking we should have the lock again
163                (format t "woken, ~A got mutex~%" (mutex-value lock))
164                (assert (ours-p (mutex-value lock))))))
165     (make-thread #'in-new-thread)
166     (sleep 2)                           ; give it  a chance to start
167     ;; check the lock is free while it's asleep
168     (format t "parent thread ~A~%" *current-thread*)
169     (assert (eql (mutex-value lock) nil))
170     (with-recursive-lock (lock)
171       (condition-notify queue))
172     (sleep 1)))
173
174 (let ((mutex (make-mutex :name "contended")))
175   (labels ((run ()
176              (let ((me *current-thread*))
177                (dotimes (i 100)
178                  (with-mutex (mutex)
179                    (sleep .1)
180                    (assert (eql (mutex-value mutex) me)))
181                  (assert (not (eql (mutex-value mutex) me))))
182                (format t "done ~A~%" *current-thread*))))
183     (let ((kid1 (make-thread #'run))
184           (kid2 (make-thread #'run)))
185       (format t "contention ~A ~A~%" kid1 kid2)
186       (wait-for-threads (list kid1 kid2)))))
187
188 ;;; semaphores
189
190 (defmacro raises-timeout-p (&body body)
191   `(handler-case (progn (progn ,@body) nil)
192     (sb-ext:timeout () t)))
193
194 (with-test (:name (:semaphore :wait-forever))
195   (let ((sem (make-semaphore :count 0)))
196     (assert (raises-timeout-p
197               (sb-ext:with-timeout 0.1
198                 (wait-on-semaphore sem))))))
199
200 (with-test (:name (:semaphore :initial-count))
201   (let ((sem (make-semaphore :count 1)))
202     (sb-ext:with-timeout 0.1
203       (wait-on-semaphore sem))))
204
205 (with-test (:name (:semaphore :wait-then-signal))
206   (let ((sem (make-semaphore))
207         (signalled-p nil))
208     (make-thread (lambda ()
209                    (sleep 0.1)
210                    (setq signalled-p t)
211                    (signal-semaphore sem)))
212     (wait-on-semaphore sem)
213     (assert signalled-p)))
214
215 (with-test (:name (:semaphore :signal-then-wait))
216   (let ((sem (make-semaphore))
217         (signalled-p nil))
218     (make-thread (lambda ()
219                    (signal-semaphore sem)
220                    (setq signalled-p t)))
221     (loop until signalled-p)
222     (wait-on-semaphore sem)
223     (assert signalled-p)))
224
225 (with-test (:name (:semaphore :multiple-signals))
226   (let* ((sem (make-semaphore :count 5))
227          (threads (loop repeat 20
228                         collect (make-thread (lambda ()
229                                                (wait-on-semaphore sem))))))
230     (flet ((count-live-threads ()
231              (count-if #'thread-alive-p threads)))
232       (sleep 0.5)
233       (assert (= 15 (count-live-threads)))
234       (signal-semaphore sem 10)
235       (sleep 0.5)
236       (assert (= 5 (count-live-threads)))
237       (signal-semaphore sem 3)
238       (sleep 0.5)
239       (assert (= 2 (count-live-threads)))
240       (signal-semaphore sem 4)
241       (sleep 0.5)
242       (assert (= 0 (count-live-threads))))))
243
244 (format t "~&semaphore tests done~%")
245
246 (defun test-interrupt (function-to-interrupt &optional quit-p)
247   (let ((child  (make-thread function-to-interrupt)))
248     ;;(format t "gdb ./src/runtime/sbcl ~A~%attach ~A~%" child child)
249     (sleep 2)
250     (format t "interrupting child ~A~%" child)
251     (interrupt-thread child
252                       (lambda ()
253                         (format t "child pid ~A~%" *current-thread*)
254                         (when quit-p (sb-ext:quit))))
255     (sleep 1)
256     child))
257
258 ;; separate tests for (a) interrupting Lisp code, (b) C code, (c) a syscall,
259 ;; (d) waiting on a lock, (e) some code which we hope is likely to be
260 ;; in pseudo-atomic
261
262 (let ((child (test-interrupt (lambda () (loop)))))  (terminate-thread child))
263
264 (test-interrupt #'loop-forever :quit)
265
266 (let ((child (test-interrupt (lambda () (loop (sleep 2000))))))
267   (terminate-thread child)
268   (wait-for-threads (list child)))
269
270 (let ((lock (make-mutex :name "loctite"))
271       child)
272   (with-mutex (lock)
273     (setf child (test-interrupt
274                  (lambda ()
275                    (with-mutex (lock)
276                      (assert (eql (mutex-value lock) *current-thread*)))
277                    (assert (not (eql (mutex-value lock) *current-thread*)))
278                    (sleep 10))))
279     ;;hold onto lock for long enough that child can't get it immediately
280     (sleep 5)
281     (interrupt-thread child (lambda () (format t "l ~A~%" (mutex-value lock))))
282     (format t "parent releasing lock~%"))
283   (terminate-thread child)
284   (wait-for-threads (list child)))
285
286 (format t "~&locking test done~%")
287
288 (defun alloc-stuff () (copy-list '(1 2 3 4 5)))
289
290 (progn
291   (let ((thread (sb-thread:make-thread (lambda () (loop (alloc-stuff))))))
292     (let ((killers
293            (loop repeat 4 collect
294                  (sb-thread:make-thread
295                   (lambda ()
296                     (loop repeat 25 do
297                           (sleep (random 0.1d0))
298                           (princ ".")
299                           (force-output)
300                           (sb-thread:interrupt-thread thread (lambda ()))))))))
301       (wait-for-threads killers)
302       (sb-thread:terminate-thread thread)
303       (wait-for-threads (list thread))))
304   (sb-ext:gc :full t))
305
306 (format t "~&multi interrupt test done~%")
307
308 (let ((c (make-thread (lambda () (loop (alloc-stuff))))))
309   ;; NB this only works on x86: other ports don't have a symbol for
310   ;; pseudo-atomic atomicity
311   (dotimes (i 100)
312     (sleep (random 0.1d0))
313     (interrupt-thread c
314                       (lambda ()
315                         (princ ".") (force-output)
316                         (assert (thread-alive-p *current-thread*))
317                         (assert (zerop SB-KERNEL:*PSEUDO-ATOMIC-ATOMIC*)))))
318   (terminate-thread c)
319   (wait-for-threads (list c)))
320
321 (format t "~&interrupt test done~%")
322
323 (defparameter *interrupt-count* 0)
324
325 (declaim (notinline check-interrupt-count))
326 (defun check-interrupt-count (i)
327   (declare (optimize (debug 1) (speed 1)))
328   ;; This used to lose if eflags were not restored after an interrupt.
329   (unless (typep i 'fixnum)
330     (error "!!!!!!!!!!!")))
331
332 (let ((c (make-thread
333           (lambda ()
334             (handler-bind ((error #'(lambda (cond)
335                                       (princ cond)
336                                       (sb-debug:backtrace
337                                        most-positive-fixnum))))
338               (loop (check-interrupt-count *interrupt-count*)))))))
339   (let ((func (lambda ()
340                 (princ ".")
341                 (force-output)
342                 (sb-impl::atomic-incf/symbol *interrupt-count*))))
343     (setq *interrupt-count* 0)
344     (dotimes (i 100)
345       (sleep (random 0.1d0))
346       (interrupt-thread c func))
347     (loop until (= *interrupt-count* 100) do (sleep 0.1))
348     (terminate-thread c)
349     (wait-for-threads (list c))))
350
351 (format t "~&interrupt count test done~%")
352
353 (let (a-done b-done)
354   (make-thread (lambda ()
355                  (dotimes (i 100)
356                    (sb-ext:gc) (princ "\\") (force-output))
357                  (setf a-done t)))
358   (make-thread (lambda ()
359                  (dotimes (i 25)
360                    (sb-ext:gc :full t)
361                    (princ "/") (force-output))
362                  (setf b-done t)))
363   (loop
364    (when (and a-done b-done) (return))
365    (sleep 1)))
366
367 (terpri)
368
369 (defun waste (&optional (n 100000))
370   (loop repeat n do (make-string 16384)))
371
372 (loop for i below 100 do
373       (princ "!")
374       (force-output)
375       (sb-thread:make-thread
376        #'(lambda ()
377            (waste)))
378       (waste)
379       (sb-ext:gc))
380
381 (terpri)
382
383 (defparameter *aaa* nil)
384 (loop for i below 100 do
385       (princ "!")
386       (force-output)
387       (sb-thread:make-thread
388        #'(lambda ()
389            (let ((*aaa* (waste)))
390              (waste))))
391       (let ((*aaa* (waste)))
392         (waste))
393       (sb-ext:gc))
394
395 (format t "~&gc test done~%")
396
397 ;; this used to deadlock on session-lock
398 (sb-thread:make-thread (lambda () (sb-ext:gc)))
399 ;; expose thread creation races by exiting quickly
400 (sb-thread:make-thread (lambda ()))
401
402 (defun exercise-syscall (fn reference-errno)
403   (sb-thread:make-thread
404    (lambda ()
405      (loop do
406           (funcall fn)
407           (let ((errno (sb-unix::get-errno)))
408             (sleep (random 0.1d0))
409             (unless (eql errno reference-errno)
410               (format t "Got errno: ~A (~A) instead of ~A~%"
411                       errno
412                       (sb-unix::strerror)
413                       reference-errno)
414               (force-output)
415               (sb-ext:quit :unix-status 1)))))))
416
417 (let* ((nanosleep-errno (progn
418                           (sb-unix:nanosleep -1 0)
419                           (sb-unix::get-errno)))
420        (open-errno (progn
421                      (open "no-such-file"
422                            :if-does-not-exist nil)
423                      (sb-unix::get-errno)))
424        (threads
425         (list
426          (exercise-syscall (lambda () (sb-unix:nanosleep -1 0)) nanosleep-errno)
427          (exercise-syscall (lambda () (open "no-such-file"
428                                             :if-does-not-exist nil))
429                            open-errno)
430          (sb-thread:make-thread (lambda () (loop (sb-ext:gc) (sleep 1)))))))
431   (sleep 10)
432   (princ "terminating threads")
433   (dolist (thread threads)
434     (sb-thread:terminate-thread thread)))
435
436 (format t "~&errno test done~%")
437
438 (loop repeat 100 do
439       (let ((thread (sb-thread:make-thread (lambda () (sleep 0.1)))))
440         (sb-thread:interrupt-thread
441          thread
442          (lambda ()
443            (assert (find-restart 'sb-thread:terminate-thread))))))
444
445 (sb-ext:gc :full t)
446
447 (format t "~&thread startup sigmask test done~%")
448
449 (sb-debug::enable-debugger)
450 (let* ((main-thread *current-thread*)
451        (interruptor-thread
452         (make-thread (lambda ()
453                        (sleep 2)
454                        (interrupt-thread main-thread #'break)
455                        (sleep 2)
456                        (interrupt-thread main-thread #'continue)))))
457   (with-session-lock (*session*)
458     (sleep 3))
459   (loop while (thread-alive-p interruptor-thread)))
460
461 (format t "~&session lock test done~%")
462
463 (wait-for-threads
464  (loop for i below 2000 collect
465        (sb-thread:make-thread (lambda ()))))
466
467 (format t "~&creation test done~%")
468
469 ;; watch out for *current-thread* being the parent thread after exit
470 (let* (sap
471        (thread (sb-thread:make-thread
472                 (lambda ()
473                   (setq sap (thread-sap-for-id
474                              (thread-os-thread *current-thread*)))))))
475   (wait-for-threads (list thread))
476   (assert (null (symbol-value-in-thread 'sb-thread:*current-thread*
477                                         sap))))
478
479 ;; interrupt handlers are per-thread with pthreads, make sure the
480 ;; handler installed in one thread is global
481 (sb-thread:make-thread
482  (lambda ()
483    (sb-ext:run-program "sleep" '("1") :search t :wait nil)))
484
485 #|  ;; a cll post from eric marsden
486 | (defun crash ()
487 |   (setq *debugger-hook*
488 |         (lambda (condition old-debugger-hook)
489 |           (debug:backtrace 10)
490 |           (unix:unix-exit 2)))
491 |   #+live-dangerously
492 |   (mp::start-sigalrm-yield)
493 |   (flet ((roomy () (loop (with-output-to-string (*standard-output*) (room)))))
494 |     (mp:make-process #'roomy)
495 |     (mp:make-process #'roomy)))
496 |#