1 ;;;; threads.test --- Tests for Guile threading. -*- scheme -*-
3 ;;;; Copyright 2003, 2006, 2007, 2009 Free Software Foundation, Inc.
5 ;;;; This library is free software; you can redistribute it and/or
6 ;;;; modify it under the terms of the GNU Lesser General Public
7 ;;;; License as published by the Free Software Foundation; either
8 ;;;; version 3 of the License, or (at your option) any later version.
10 ;;;; This library is distributed in the hope that it will be useful,
11 ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 ;;;; Lesser General Public License for more details.
15 ;;;; You should have received a copy of the GNU Lesser General Public
16 ;;;; License along with this library; if not, write to the Free Software
17 ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 (define-module (test-threads)
20 :use-module (ice-9 threads)
21 :use-module (test-suite lib))
23 (define (asyncs-still-working?)
25 (system-async-mark (lambda ()
27 ;; The point of the following (equal? ...) is to go through
28 ;; primitive code (scm_equal_p) that includes a SCM_TICK call and
29 ;; hence gives system asyncs a chance to run. Of course the
30 ;; evaluator (eval.i.c) also calls SCM_TICK regularly, but in the
31 ;; near future we may be using the VM instead of the traditional
32 ;; compiler, and then we will still want asyncs-still-working? to
33 ;; work. (The VM should probably have SCM_TICK calls too, but
34 ;; let's not rely on that here.)
35 (equal? '(a b c) '(a b c))
38 (if (provided? 'threads)
41 (with-test-prefix "parallel"
77 (with-test-prefix "n-par-for-each"
79 (pass-if "0 in limit 10"
80 (n-par-for-each 10 noop '())
83 (pass-if "6 in limit 10"
84 (let ((v (make-vector 6 #f)))
85 (n-par-for-each 10 (lambda (n)
88 (equal? v '#(#t #t #t #t #t #t))))
90 (pass-if "6 in limit 1"
91 (let ((v (make-vector 6 #f)))
92 (n-par-for-each 1 (lambda (n)
95 (equal? v '#(#t #t #t #t #t #t))))
97 (pass-if "6 in limit 2"
98 (let ((v (make-vector 6 #f)))
99 (n-par-for-each 2 (lambda (n)
100 (vector-set! v n #t))
102 (equal? v '#(#t #t #t #t #t #t))))
104 (pass-if "6 in limit 3"
105 (let ((v (make-vector 6 #f)))
106 (n-par-for-each 3 (lambda (n)
107 (vector-set! v n #t))
109 (equal? v '#(#t #t #t #t #t #t)))))
112 ;; n-for-each-par-map
115 (with-test-prefix "n-for-each-par-map"
117 (pass-if "asyncs are still working 2"
118 (asyncs-still-working?))
120 (pass-if "0 in limit 10"
121 (n-for-each-par-map 10 noop noop '())
124 (pass-if "6 in limit 10"
126 (n-for-each-par-map 10
127 (lambda (n) (set! result (cons n result)))
130 (equal? result '(10 8 6 4 2 0))))
132 (pass-if "6 in limit 1"
134 (n-for-each-par-map 1
135 (lambda (n) (set! result (cons n result)))
138 (equal? result '(10 8 6 4 2 0))))
140 (pass-if "6 in limit 2"
142 (n-for-each-par-map 2
143 (lambda (n) (set! result (cons n result)))
146 (equal? result '(10 8 6 4 2 0))))
148 (pass-if "6 in limit 3"
150 (n-for-each-par-map 3
151 (lambda (n) (set! result (cons n result)))
154 (equal? result '(10 8 6 4 2 0)))))
157 ;; timed mutex locking
160 (with-test-prefix "lock-mutex"
162 (pass-if "asyncs are still working 3"
163 (asyncs-still-working?))
165 (pass-if "timed locking fails if timeout exceeded"
166 (let ((m (make-mutex)))
168 (let ((t (begin-thread (lock-mutex m (+ (current-time) 1)))))
169 (not (join-thread t)))))
171 (pass-if "asyncs are still working 6"
172 (asyncs-still-working?))
174 (pass-if "timed locking succeeds if mutex unlocked within timeout"
175 (let* ((m (make-mutex))
176 (c (make-condition-variable))
179 (let ((t (begin-thread (begin (lock-mutex cm)
180 (signal-condition-variable c)
183 (+ (current-time) 2))))))
185 (wait-condition-variable c cm)
191 (pass-if "asyncs are still working 7"
192 (asyncs-still-working?))
197 ;; timed mutex unlocking
200 (with-test-prefix "unlock-mutex"
202 (pass-if "asyncs are still working 5"
203 (asyncs-still-working?))
205 (pass-if "timed unlocking returns #f if timeout exceeded"
206 (let ((m (make-mutex))
207 (c (make-condition-variable)))
209 (not (unlock-mutex m c (current-time)))))
211 (pass-if "asyncs are still working 4"
212 (asyncs-still-working?))
214 (pass-if "timed unlocking returns #t if condition signaled"
215 (let ((m1 (make-mutex))
217 (c1 (make-condition-variable))
218 (c2 (make-condition-variable)))
220 (let ((t (begin-thread (begin (lock-mutex m1)
221 (signal-condition-variable c1)
228 (wait-condition-variable c1 m1)
231 (signal-condition-variable c2)
239 (with-test-prefix "join-thread"
241 (pass-if "timed joining fails if timeout exceeded"
242 (let* ((m (make-mutex))
243 (c (make-condition-variable))
244 (t (begin-thread (begin (lock-mutex m)
245 (wait-condition-variable c m))))
246 (r (join-thread t (current-time))))
250 (pass-if "join-thread returns timeoutval on timeout"
251 (let* ((m (make-mutex))
252 (c (make-condition-variable))
253 (t (begin-thread (begin (lock-mutex m)
254 (wait-condition-variable c m))))
255 (r (join-thread t (current-time) 'foo)))
260 (pass-if "timed joining succeeds if thread exits within timeout"
261 (let ((t (begin-thread (begin (sleep 1) #t))))
262 (join-thread t (+ (current-time) 2))))
264 (pass-if "asyncs are still working 1"
265 (asyncs-still-working?))
267 ;; scm_join_thread_timed has a SCM_TICK in the middle of it,
268 ;; to allow asyncs to run (including signal delivery). We
269 ;; used to have a bug whereby if the joined thread terminated
270 ;; at the same time as the joining thread is in this SCM_TICK,
271 ;; scm_join_thread_timed would not notice and would hang
272 ;; forever. So in this test we are setting up the following
273 ;; sequence of events.
274 ;; T=0 other thread is created and starts running
275 ;; T=2 main thread sets up an async that will sleep for 10 seconds
276 ;; T=2 main thread calls join-thread, which will...
277 ;; T=2 ...call the async, which starts sleeping
278 ;; T=5 other thread finishes its work and terminates
279 ;; T=7 async completes, main thread continues inside join-thread.
280 (pass-if "don't hang when joined thread terminates in SCM_TICK"
281 (let ((other-thread (make-thread sleep 5)))
282 (letrec ((delay-count 10)
284 (set! delay-count (- delay-count 1))
285 (if (zero? delay-count)
287 (system-async-mark aproc)))))
289 (system-async-mark aproc)
290 (join-thread other-thread)))
294 ;; thread cancellation
297 (with-test-prefix "cancel-thread"
299 (pass-if "cancel succeeds"
300 (let ((m (make-mutex)))
302 (let ((t (begin-thread (begin (lock-mutex m) 'foo))))
307 (pass-if "handler result passed to join"
308 (let ((m (make-mutex)))
310 (let ((t (begin-thread (lock-mutex m))))
311 (set-thread-cleanup! t (lambda () 'foo))
313 (eq? (join-thread t) 'foo))))
315 (pass-if "can cancel self"
316 (let ((m (make-mutex)))
318 (let ((t (begin-thread (begin
319 (set-thread-cleanup! (current-thread)
321 (cancel-thread (current-thread))
323 (eq? (join-thread t) 'foo))))
325 (pass-if "handler supplants final expr"
326 (let ((t (begin-thread (begin (set-thread-cleanup! (current-thread)
329 (eq? (join-thread t) 'bar)))
331 (pass-if "remove handler by setting false"
332 (let ((m (make-mutex)))
334 (let ((t (begin-thread (lock-mutex m) 'bar)))
335 (set-thread-cleanup! t (lambda () 'foo))
336 (set-thread-cleanup! t #f)
338 (eq? (join-thread t) 'bar))))
340 (pass-if "initial handler is false"
341 (not (thread-cleanup (current-thread)))))
347 (with-test-prefix "mutex-ownership"
348 (pass-if "mutex ownership for locked mutex"
349 (let ((m (make-mutex)))
351 (eq? (mutex-owner m) (current-thread))))
353 (pass-if "mutex ownership for unlocked mutex"
354 (let ((m (make-mutex)))
355 (not (mutex-owner m))))
357 (pass-if "locking mutex on behalf of other thread"
358 (let* ((m (make-mutex))
359 (t (begin-thread 'foo)))
361 (eq? (mutex-owner m) t)))
363 (pass-if "locking mutex with no owner"
364 (let ((m (make-mutex)))
366 (not (mutex-owner m))))
368 (pass-if "mutex with owner not retained (bug #27450)"
369 (let ((g (make-guardian)))
370 (g (let ((m (make-mutex))) (lock-mutex m) m))
372 ;; Avoid false references to M on the stack.
373 (let cleanup ((i 20))
375 (begin (cleanup (1- i)) i)))
380 (eq? (mutex-owner m) (current-thread)))))))
386 (with-test-prefix "mutex-lock-levels"
388 (pass-if "unlocked level is 0"
389 (let ((m (make-mutex)))
390 (and (not (mutex-locked? m)) (eqv? (mutex-level m) 0))))
392 (pass-if "non-recursive lock level is 1"
393 (let ((m (make-mutex)))
395 (and (mutex-locked? m) (eqv? (mutex-level m) 1))))
397 (pass-if "recursive lock level is >1"
398 (let ((m (make-mutex 'recursive)))
401 (and (mutex-locked? m) (eqv? (mutex-level m) 2)))))
407 (with-test-prefix "mutex-behavior"
409 (pass-if "unchecked unlock"
410 (let* ((m (make-mutex 'unchecked-unlock)))
413 (pass-if "allow external unlock"
414 (let* ((m (make-mutex 'allow-external-unlock))
415 (t (begin-thread (lock-mutex m))))
419 (pass-if "recursive mutexes"
420 (let* ((m (make-mutex 'recursive)))
424 (pass-if "locking abandoned mutex throws exception"
425 (let* ((m (make-mutex))
426 (t (begin-thread (lock-mutex m)))
429 (catch 'abandoned-mutex-error
430 (lambda () (lock-mutex m))
431 (lambda key (set! success #t)))