1 ;;;; threads.test --- Tests for Guile threading. -*- scheme -*-
3 ;;;; Copyright 2003, 2006, 2007 Free Software Foundation, Inc.
5 ;;;; This program is free software; you can redistribute it and/or modify
6 ;;;; it under the terms of the GNU General Public License as published by
7 ;;;; the Free Software Foundation; either version 2, or (at your option)
8 ;;;; any later version.
10 ;;;; This program 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
13 ;;;; GNU General Public License for more details.
15 ;;;; You should have received a copy of the GNU General Public License
16 ;;;; along with this software; see the file COPYING. If not, write to
17 ;;;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 ;;;; Boston, MA 02110-1301 USA
20 (define-module (test-threads)
21 :use-module (ice-9 threads)
22 :use-module (test-suite lib))
24 (define (asyncs-still-working?)
26 (system-async-mark (lambda ()
28 ;; The point of the following (equal? ...) is to go through
29 ;; primitive code (scm_equal_p) that includes a SCM_TICK call and
30 ;; hence gives system asyncs a chance to run. Of course the
31 ;; evaluator (eval.i.c) also calls SCM_TICK regularly, but in the
32 ;; near future we may be using the VM instead of the traditional
33 ;; compiler, and then we will still want asyncs-still-working? to
34 ;; work. (The VM should probably have SCM_TICK calls too, but
35 ;; let's not rely on that here.)
36 (equal? '(a b c) '(a b c))
39 (if (provided? 'threads)
42 (with-test-prefix "parallel"
78 (with-test-prefix "n-par-for-each"
80 (pass-if "0 in limit 10"
81 (n-par-for-each 10 noop '())
84 (pass-if "6 in limit 10"
85 (let ((v (make-vector 6 #f)))
86 (n-par-for-each 10 (lambda (n)
89 (equal? v '#(#t #t #t #t #t #t))))
91 (pass-if "6 in limit 1"
92 (let ((v (make-vector 6 #f)))
93 (n-par-for-each 1 (lambda (n)
96 (equal? v '#(#t #t #t #t #t #t))))
98 (pass-if "6 in limit 2"
99 (let ((v (make-vector 6 #f)))
100 (n-par-for-each 2 (lambda (n)
101 (vector-set! v n #t))
103 (equal? v '#(#t #t #t #t #t #t))))
105 (pass-if "6 in limit 3"
106 (let ((v (make-vector 6 #f)))
107 (n-par-for-each 3 (lambda (n)
108 (vector-set! v n #t))
110 (equal? v '#(#t #t #t #t #t #t)))))
113 ;; n-for-each-par-map
116 (with-test-prefix "n-for-each-par-map"
118 (pass-if "asyncs are still working 2"
119 (asyncs-still-working?))
121 (pass-if "0 in limit 10"
122 (n-for-each-par-map 10 noop noop '())
125 (pass-if "6 in limit 10"
127 (n-for-each-par-map 10
128 (lambda (n) (set! result (cons n result)))
131 (equal? result '(10 8 6 4 2 0))))
133 (pass-if "6 in limit 1"
135 (n-for-each-par-map 1
136 (lambda (n) (set! result (cons n result)))
139 (equal? result '(10 8 6 4 2 0))))
141 (pass-if "6 in limit 2"
143 (n-for-each-par-map 2
144 (lambda (n) (set! result (cons n result)))
147 (equal? result '(10 8 6 4 2 0))))
149 (pass-if "6 in limit 3"
151 (n-for-each-par-map 3
152 (lambda (n) (set! result (cons n result)))
155 (equal? result '(10 8 6 4 2 0)))))
158 ;; timed mutex locking
161 (with-test-prefix "lock-mutex"
163 (pass-if "asyncs are still working 3"
164 (asyncs-still-working?))
166 (pass-if "timed locking fails if timeout exceeded"
167 (let ((m (make-mutex)))
169 (let ((t (begin-thread (lock-mutex m (+ (current-time) 1)))))
170 (not (join-thread t)))))
172 (pass-if "asyncs are still working 6"
173 (asyncs-still-working?))
175 (pass-if "timed locking succeeds if mutex unlocked within timeout"
176 (let* ((m (make-mutex))
177 (c (make-condition-variable))
180 (let ((t (begin-thread (begin (lock-mutex cm)
181 (signal-condition-variable c)
184 (+ (current-time) 2))))))
186 (wait-condition-variable c cm)
192 (pass-if "asyncs are still working 7"
193 (asyncs-still-working?))
198 ;; timed mutex unlocking
201 (with-test-prefix "unlock-mutex"
203 (pass-if "asyncs are still working 5"
204 (asyncs-still-working?))
206 (pass-if "timed unlocking returns #f if timeout exceeded"
207 (let ((m (make-mutex))
208 (c (make-condition-variable)))
210 (not (unlock-mutex m c (current-time)))))
212 (pass-if "asyncs are still working 4"
213 (asyncs-still-working?))
215 (pass-if "timed unlocking returns #t if condition signaled"
216 (let ((m1 (make-mutex))
218 (c1 (make-condition-variable))
219 (c2 (make-condition-variable)))
221 (let ((t (begin-thread (begin (lock-mutex m1)
222 (signal-condition-variable c1)
229 (wait-condition-variable c1 m1)
232 (signal-condition-variable c2)
240 (with-test-prefix "join-thread"
242 (pass-if "timed joining fails if timeout exceeded"
243 (let* ((m (make-mutex))
244 (c (make-condition-variable))
245 (t (begin-thread (begin (lock-mutex m)
246 (wait-condition-variable c m))))
247 (r (join-thread t (current-time))))
251 (pass-if "join-thread returns timeoutval on timeout"
252 (let* ((m (make-mutex))
253 (c (make-condition-variable))
254 (t (begin-thread (begin (lock-mutex m)
255 (wait-condition-variable c m))))
256 (r (join-thread t (current-time) 'foo)))
261 (pass-if "timed joining succeeds if thread exits within timeout"
262 (let ((t (begin-thread (begin (sleep 1) #t))))
263 (join-thread t (+ (current-time) 2))))
265 (pass-if "asyncs are still working 1"
266 (asyncs-still-working?))
268 ;; scm_join_thread_timed has a SCM_TICK in the middle of it,
269 ;; to allow asyncs to run (including signal delivery). We
270 ;; used to have a bug whereby if the joined thread terminated
271 ;; at the same time as the joining thread is in this SCM_TICK,
272 ;; scm_join_thread_timed would not notice and would hang
273 ;; forever. So in this test we are setting up the following
274 ;; sequence of events.
275 ;; T=0 other thread is created and starts running
276 ;; T=2 main thread sets up an async that will sleep for 10 seconds
277 ;; T=2 main thread calls join-thread, which will...
278 ;; T=2 ...call the async, which starts sleeping
279 ;; T=5 other thread finishes its work and terminates
280 ;; T=7 async completes, main thread continues inside join-thread.
281 (pass-if "don't hang when joined thread terminates in SCM_TICK"
282 (let ((other-thread (make-thread sleep 5)))
283 (letrec ((delay-count 10)
285 (set! delay-count (- delay-count 1))
286 (if (zero? delay-count)
288 (system-async-mark aproc)))))
290 (system-async-mark aproc)
291 (join-thread other-thread)))
295 ;; thread cancellation
298 (with-test-prefix "cancel-thread"
300 (pass-if "cancel succeeds"
301 (let ((m (make-mutex)))
303 (let ((t (begin-thread (begin (lock-mutex m) 'foo))))
308 (pass-if "handler result passed to join"
309 (let ((m (make-mutex)))
311 (let ((t (begin-thread (lock-mutex m))))
312 (set-thread-cleanup! t (lambda () 'foo))
314 (eq? (join-thread t) 'foo))))
316 (pass-if "can cancel self"
317 (let ((m (make-mutex)))
319 (let ((t (begin-thread (begin
320 (set-thread-cleanup! (current-thread)
322 (cancel-thread (current-thread))
324 (eq? (join-thread t) 'foo))))
326 (pass-if "handler supplants final expr"
327 (let ((t (begin-thread (begin (set-thread-cleanup! (current-thread)
330 (eq? (join-thread t) 'bar)))
332 (pass-if "remove handler by setting false"
333 (let ((m (make-mutex)))
335 (let ((t (begin-thread (lock-mutex m) 'bar)))
336 (set-thread-cleanup! t (lambda () 'foo))
337 (set-thread-cleanup! t #f)
339 (eq? (join-thread t) 'bar))))
341 (pass-if "initial handler is false"
342 (not (thread-cleanup (current-thread)))))
348 (with-test-prefix "mutex-ownership"
349 (pass-if "mutex ownership for locked mutex"
350 (let ((m (make-mutex)))
352 (eq? (mutex-owner m) (current-thread))))
354 (pass-if "mutex ownership for unlocked mutex"
355 (let ((m (make-mutex)))
356 (not (mutex-owner m))))
358 (pass-if "locking mutex on behalf of other thread"
359 (let* ((m (make-mutex))
360 (t (begin-thread 'foo)))
362 (eq? (mutex-owner m) t)))
364 (pass-if "locking mutex with no owner"
365 (let ((m (make-mutex)))
367 (not (mutex-owner m)))))
373 (with-test-prefix "mutex-lock-levels"
375 (pass-if "unlocked level is 0"
376 (let ((m (make-mutex)))
377 (and (not (mutex-locked? m)) (eqv? (mutex-level m) 0))))
379 (pass-if "non-recursive lock level is 1"
380 (let ((m (make-mutex)))
382 (and (mutex-locked? m) (eqv? (mutex-level m) 1))))
384 (pass-if "recursive lock level is >1"
385 (let ((m (make-mutex 'recursive)))
388 (and (mutex-locked? m) (eqv? (mutex-level m) 2)))))
394 (with-test-prefix "mutex-behavior"
396 (pass-if "unchecked unlock"
397 (let* ((m (make-mutex 'unchecked-unlock)))
400 (pass-if "allow external unlock"
401 (let* ((m (make-mutex 'allow-external-unlock))
402 (t (begin-thread (lock-mutex m))))
406 (pass-if "recursive mutexes"
407 (let* ((m (make-mutex 'recursive)))
411 (pass-if "locking abandoned mutex throws exception"
412 (let* ((m (make-mutex))
413 (t (begin-thread (lock-mutex m)))
416 (catch 'abandoned-mutex-error
417 (lambda () (lock-mutex m))
418 (lambda key (set! success #t)))