racket/collects/tests/mzscheme/thread.ss
Matthew Flatt 43d2868700 thread-cell tests
svn: r870
2005-09-18 12:30:58 +00:00

1190 lines
33 KiB
Scheme

(load-relative "loadtest.ss")
(SECTION 'threads)
(define SLEEP-TIME 0.1)
;; ----------------------------------------
(define t (thread (lambda () 8)))
(test #t thread? t)
(arity-test thread 1 1)
(err/rt-test (thread 5) type?)
(err/rt-test (thread (lambda (x) 8)) type?)
(arity-test thread? 1 1)
;; ----------------------------------------
;; Thread sets
(define (test-set-balance as bs cs ds
sa sb sc sd
a% b% c% d%)
(let ([a (box 0)]
[b (box 0)]
[c (box 0)]
[d (box 0)]
[stop? #f])
(define (go box s s-amt)
(parameterize ([current-thread-group s])
(thread (lambda ()
(let loop ()
(set-box! box (add1 (unbox box)))
(sleep s-amt)
(unless stop?
(loop)))))))
(go a as sa)
(go b bs sb)
(go c cs sc)
(go d ds sd)
(sleep SLEEP-TIME)
(set! stop? #t)
(let ([va (/ (unbox a) a%)]
[vb (unbox b)]
[vc (unbox c)]
[vd (unbox d)])
(define (roughly= x y)
(<= (* (- x 1) 0.9) y (* (+ x 1) 1.1)))
(test #t roughly= vb (* b% va))
(test #t roughly= vc (* c% va))
(test #t roughly= vd (* d% va)))))
;; Simple test:
(let ([ts (make-thread-group)])
(test-set-balance (current-thread-group) ts ts ts
0 0 0 0
1 1/3 1/3 1/3))
;; Make two sets, should be balanced:
(let ([ts1 (make-thread-group)]
[ts2 (make-thread-group)])
(test-set-balance ts1 ts2 ts2 ts1
0 0 0 0
1 1 1 1))
;; Like first test, but with an explicit "root" set
(let* ([ts1 (make-thread-group)]
[ts2 (make-thread-group ts1)])
(test-set-balance ts1 ts2 ts2 ts2
0 0 0 0
1 1/3 1/3 1/3))
;; Like second test, but with an explicit "root" set
(let* ([ts0 (make-thread-group)]
[ts1 (make-thread-group ts0)]
[ts2 (make-thread-group ts0)])
(test-set-balance ts1 ts2 ts2 ts1
0 0 0 0
1 1 1 1))
;; Check that suspended threads don't break
;; scheduling. (The test really continues past this
;; one, since the threads don't die right away.)
(let* ([ts0 (make-thread-group)]
[ts1 (make-thread-group ts0)]
[ts2 (make-thread-group ts0)])
(test-set-balance ts1 ts2 ts2 ts1
0 0 (* SLEEP-TIME 10) (* SLEEP-TIME 10)
1 1 0 0))
(arity-test make-thread-group 0 1)
(err/rt-test (make-thread-group 5) type?)
(arity-test thread-group? 1 1)
(test #t thread-group? (make-thread-group))
(test #f thread-group? 5)
(arity-test current-thread-group 0 1)
(err/rt-test (current-thread-group 5))
;; ----------------------------------------
; Should be able to make an arbitrarily deep chain of custodians
; if only the first & last are accssible:
(test #t custodian?
(let loop ([n 1000][c (current-custodian)])
(if (zero? n)
c
(loop (sub1 n) (make-custodian c)))))
(define result 0)
(define th1 0)
(define set-ready
(let ([s (make-semaphore 1)]
[r #f])
(lambda (v)
(semaphore-wait s)
(begin0
r
(set! r v)
(semaphore-post s)))))
(define cm (make-custodian))
(define th2 (parameterize ([current-custodian cm])
(thread
(lambda ()
(let ([cm2 (make-custodian cm)])
(parameterize ([current-custodian cm2])
(set! th1 (thread
(lambda ()
(let loop ()
(let ([r (set-ready #f)])
(sleep SLEEP-TIME)
(set! result (add1 result))
(when r (semaphore-post r)))
(loop)))))))))))
(define start result)
(let ([r (make-semaphore)])
(set-ready r)
(semaphore-wait r))
(test #f eq? start result)
(kill-thread th2)
(set! start result)
(let ([r (make-semaphore)])
(set-ready r)
(semaphore-wait r))
(test #f eq? start result)
(test #t thread-running? th1)
(test #f thread-dead? th1)
(custodian-shutdown-all cm)
(thread-wait th1)
(set! start result)
(test #f thread-running? th1)
(test #t thread-dead? th1)
(sleep SLEEP-TIME)
(test #t eq? start result)
(let ([kept-going? #f])
(let ([c (make-custodian)])
(parameterize ([current-custodian c])
(thread-wait
(thread
(lambda ()
(custodian-shutdown-all c)
(set! kept-going? #t))))))
(test #f 'kept-going-after-shutdown? kept-going?))
(err/rt-test (parameterize ([current-custodian cm]) (kill-thread (current-thread)))
exn:application:mismatch?)
(test #t custodian? cm)
(test #f custodian? 1)
(arity-test custodian? 1 1)
(arity-test custodian-shutdown-all 1 1)
(err/rt-test (custodian-shutdown-all 0))
(arity-test make-custodian 0 1)
(err/rt-test (make-custodian 0))
(test (void) kill-thread t)
(arity-test kill-thread 1 1)
(err/rt-test (kill-thread 5) type?)
(arity-test break-thread 1 1)
(err/rt-test (break-thread 5) type?)
(arity-test thread-wait 1 1)
(err/rt-test (thread-wait 5) type?)
(test #t thread-running? (current-thread))
(arity-test thread-running? 1 1)
(err/rt-test (thread-running? 5) type?)
(test #f thread-dead? (current-thread))
(arity-test thread-dead? 1 1)
(err/rt-test (thread-dead? 5) type?)
(arity-test sleep 0 1)
(err/rt-test (sleep 'a) type?)
(err/rt-test (sleep 1+3i) type?)
(err/rt-test (sleep -1.0) type?)
(define s (make-semaphore 1))
(test #t semaphore? s)
(arity-test make-semaphore 0 1)
(err/rt-test (make-semaphore "a") type?)
(err/rt-test (make-semaphore -1) type?)
(err/rt-test (make-semaphore 1.0) type?)
(err/rt-test (make-semaphore (expt 2 64)) exn:fail?)
(arity-test semaphore? 1 1)
(define test-block
(lambda (block? thunk)
(let* ([hit? #f]
[t (parameterize ([current-custodian (make-custodian)])
(thread (lambda () (thunk) (set! hit? #t))))])
(sleep SLEEP-TIME)
(begin0 (test block? 'nondeterministic-block-test (not hit?))
(kill-thread t)))))
(test #t semaphore-try-wait? s)
(test #f semaphore-try-wait? s)
(semaphore-post s)
(test #t semaphore-try-wait? s)
(test #f semaphore-try-wait? s)
(semaphore-post s)
(test-block #f (lambda () (semaphore-wait s)))
(test-block #t (lambda () (semaphore-wait s)))
(semaphore-post s)
(test-block #f (lambda () (semaphore-wait/enable-break s)))
(test-block #t (lambda () (semaphore-wait/enable-break s)))
(arity-test semaphore-try-wait? 1 1)
(arity-test semaphore-wait 1 1)
(arity-test semaphore-post 1 1)
(define s (make-semaphore))
(define result 0)
(define t-loop
(lambda (n m)
(lambda ()
(if (zero? n)
(begin
(set! result m)
(semaphore-post s))
(thread (t-loop (sub1 n) (add1 m)))))))
(thread (t-loop 25 1))
(semaphore-wait s)
(test 26 'thread-loop result)
; Make sure you can break a semaphore-wait:
'(test 'ok
'break-semaphore-wait
(let* ([s1 (make-semaphore 0)]
[s2 (make-semaphore 0)]
[t (thread (lambda ()
(semaphore-post s1)
(with-handlers ([exn:break? (lambda (x) (semaphore-post s2))])
(semaphore-wait (make-semaphore 0)))))])
(semaphore-wait s1)
(sleep SLEEP-TIME)
(break-thread t)
(semaphore-wait s2)
'ok))
; Make sure two waiters can be released
(test 'ok
'double-semaphore-wait
(let* ([s1 (make-semaphore 0)]
[s2 (make-semaphore 0)]
[go (lambda ()
(semaphore-post s2)
(semaphore-wait s1)
(semaphore-post s2))])
(thread go) (thread go)
(semaphore-wait s2) (semaphore-wait s2)
(semaphore-post s1) (semaphore-post s1)
(semaphore-wait s2) (semaphore-wait s2)
'ok))
; Tests inspired by a question from David Tillman
(define (read-line/expire1 port expiration)
(with-handlers ([exn:break? (lambda (exn) #f)])
(let ([timer (thread (let ([id (current-thread)])
(lambda ()
(sleep expiration)
(break-thread id))))])
(dynamic-wind
void
(lambda () (read-line port))
(lambda () (kill-thread timer))))))
(define (read-line/expire2 port expiration)
(let ([done (make-semaphore 0)]
[result #f])
(let ([t1 (thread (lambda ()
(set! result (read-line port))
(semaphore-post done)))]
[t2 (thread (lambda ()
(sleep expiration)
(semaphore-post done)))])
(semaphore-wait done)
(kill-thread t1)
(kill-thread t2)
result)))
;; the main thread is special for semaphore blocking,
;; so we try read-line/expire1 in sub-threads for a couple
;; of configurations:
(define (read-line/expire3 port expiration)
(call-in-nested-thread (lambda ()
(read-line/expire1 port expiration))))
(define (read-line/expire4 port expiration)
(let ([v #f])
(let ([t (thread (lambda ()
(set! v (read-line/expire1 port expiration))))])
(thread-wait t)
v)))
(define (go read-line/expire)
(define p (let ([c 0]
[nl-sema (make-semaphore 1)]
[ready? #f]
[nl? #f])
(make-input-port
'read-line/expire
(lambda (s)
(let ([c (if nl?
(if ready?
#\newline
(wrap-evt nl-sema
(lambda (x) 0)))
(begin
(set! nl? #t)
(semaphore-try-wait? nl-sema)
(set! ready? #f)
(thread (lambda ()
(sleep 0.4)
(set! ready? #t)
(semaphore-post nl-sema)))
(set! c (add1 c))
(integer->char c)))])
(if c
(if (char? c)
(begin
(bytes-set! s 0 (char->integer c))
1)
c)
0)))
#f
void)))
(test #f read-line/expire p 0.2) ; should get char but not newline
(test "" read-line/expire p 0.6)) ; picks up newline
(go read-line/expire1)
(go read-line/expire2)
(go read-line/expire3)
(go read-line/expire4)
;; Make sure queueing works, and check kill/wait interaction:
(let* ([s (make-semaphore)]
[l null]
[wait (lambda (who)
(thread
(lambda ()
(semaphore-wait s)
(set! l (cons who l)))))]
[pause (lambda () (sleep 0.01))])
(wait 0) (pause)
(wait 1) (pause)
(wait 2)
(pause)
(test null 'queue l)
(semaphore-post s) (pause)
(test '(0) 'queue l)
(semaphore-post s) (pause)
(test '(1 0) 'queue l)
(semaphore-post s) (pause)
(test '(2 1 0) 'queue l)
(set! l null)
(wait 0) (pause)
(let ([t (wait 1)])
(pause)
(wait 2)
(pause)
(test null 'queue l)
(kill-thread t)
(semaphore-post s) (pause)
(test '(0) 'queue l)
(semaphore-post s) (pause)
(test '(2 0) 'queue l)
(semaphore-post s) (pause)
(test '(2 0) 'queue l)
(wait 3) (pause)
(test '(3 2 0) 'queue l)))
;; Nested threads
(test 5 call-in-nested-thread (lambda () 5))
(define (exn:thread? e)
(and (exn:fail? e) (not (exn:fail:contract? e))))
(err/rt-test (call-in-nested-thread (lambda () (kill-thread (current-thread)))) exn:thread?)
(err/rt-test (call-in-nested-thread (lambda () ((error-escape-handler)))) exn:thread?)
(err/rt-test (call-in-nested-thread (lambda () (raise (box 5)))) box?)
(define output-stream null)
(define (output v)
(set! output-stream
(append output-stream (list v))))
(define (test-stream v)
(test v 'output-stream output-stream))
(define (chain c)
(define c1 (make-custodian))
(define c2 (make-custodian))
(define c3 (make-custodian))
(set! output-stream null)
(output 'os)
(with-handlers ([void (lambda (x) x)])
(call-in-nested-thread
(lambda ()
(output 'ms)
(begin0
(dynamic-wind
(lambda () (output 'mpre))
(lambda ()
(let ([t1 (current-thread)])
(call-in-nested-thread
(lambda ()
(output 'is)
(with-handlers ([void (lambda (x)
(if (exn:break? x)
(output 'ibreak)
(output 'iother))
(raise x))])
(let ([get-c (lambda (c)
(case c
[(1) c1]
[(2) c2]
[(3) c3]
[else c]))])
(if (procedure? c)
(c t1 get-c)
(custodian-shutdown-all (get-c c)))))
(output 'ie)
'inner-result)
c2)))
(lambda () (output 'mpost)))
(output 'me)))
c1)))
(test 'inner-result chain 3)
(test-stream '(os ms mpre is ie mpost me))
(test #t exn:thread? (chain 1))
(test-stream '(os ms mpre is ibreak))
(parameterize-break #f
(test #t exn:thread? (chain 1))
(test-stream '(os ms mpre is ie))
(test (void) 'discard-break
(with-handlers ([void void])
(break-enabled #t)
(sleep)
'not-void)))
(test #t exn:thread? (chain 2))
(test-stream '(os ms mpre is mpost))
(test #t exn:thread? (chain (lambda (t1 get-c) (kill-thread (current-thread)))))
(test-stream '(os ms mpre is mpost))
(test #t exn:fail:contract? (chain 'wrong))
(test-stream '(os ms mpre is iother mpost))
(test #t exn:break? (chain (let ([t (current-thread)]) (lambda (t1 get-c) (break-thread t)))))
(test-stream '(os ms mpre is ibreak mpost))
(test #t exn:thread? (chain (lambda (t1 get-c) (kill-thread t1))))
(test-stream '(os ms mpre is ibreak))
(parameterize-break #f
(test #t exn:thread? (let ([t (current-thread)])
(chain (lambda (t1 get-c)
(custodian-shutdown-all (get-c 1))
(test #t thread-running? (current-thread))
(test #t thread-running? t)
(test #f thread-running? t1)))))
(test-stream '(os ms mpre is ie))
(test (void) 'discard-break
(with-handlers ([void void])
(break-enabled #t)
(sleep)
'not-void)))
(err/rt-test (let/cc k (call-in-nested-thread (lambda () (k)))) exn:fail:contract:continuation?)
(err/rt-test (let/ec k (call-in-nested-thread (lambda () (k)))) exn:fail:contract:continuation?)
(err/rt-test ((call-in-nested-thread (lambda () (let/cc k k)))) exn:fail:contract:continuation?)
(err/rt-test ((call-in-nested-thread (lambda () (let/ec k k)))) exn:fail:contract:continuation?)
(err/rt-test (call-in-nested-thread 5))
(err/rt-test (call-in-nested-thread (lambda (x) 10)))
(err/rt-test (call-in-nested-thread (lambda () 10) 5))
(arity-test call-in-nested-thread 1 2)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Test wait-multiple:
(let ([s (make-semaphore 1)]
[s2 (make-semaphore 1)])
(let ([w (list
(sync s s2)
(sync s s2))])
(test #t 'both (or (equal? w (list s s2))
(equal? w (list s2 s))))
(test #f semaphore-try-wait? s)
(test #f semaphore-try-wait? s2)))
;; same test, but throw in an empty pipe to avoid the
;; special case for just semaphores:
(let ([s (make-semaphore 1)]
[s2 (make-semaphore 1)])
(let-values ([(r w) (make-pipe)])
(let ([w (list
(sync s r s2)
(sync s r s2))])
(test #t 'both (or (equal? w (list s s2))
(equal? w (list s2 s))))
(test #f semaphore-try-wait? s)
(test #f semaphore-try-wait? s2))))
(let ([s (make-semaphore)]
[s-t (make-semaphore)]
[portnum (+ 40000 (random 100))]) ; so parallel tests work ok
(let ([t (thread
(lambda ()
(sync s-t)))]
[l (tcp-listen portnum 5 #t)]
[orig-thread (current-thread)])
(let-values ([(r w) (make-pipe)])
(define (try-all-blocked* wait)
(let ([v #f])
(let ([bt (thread
(lambda ()
(with-handlers ([exn:break? (lambda (x) (set! v 'break))])
(set! v (wait #f s t l r)))))])
(sleep 0.05) ;;; <---------- race condition (that's unlikely to fail)
(break-thread bt)
(sleep 0.05) ;;; <----------
)
(test 'break 'broken-wait v)))
(define (try-all-blocked)
(test #f sync/timeout 0.05 s t l r))
(try-all-blocked* sync/timeout)
(try-all-blocked* sync/timeout/enable-break)
(parameterize-break #f
(try-all-blocked* sync/timeout/enable-break))
(display #\x w)
(test r sync s t l r)
(test r sync s t l r)
(peek-char r)
(test r sync s t l r)
(read-char r)
(try-all-blocked)
;; pipe write always available, since no limit:
(test w sync s t l r w)
(semaphore-post s)
(test s sync s t l r)
(try-all-blocked)
(semaphore-post s-t)
(test t sync s t l r)
(test t sync s t l r)
(set! t (thread (lambda () (semaphore-wait (make-semaphore)))))
(let-values ([(cr cw) (tcp-connect "localhost" portnum)])
(test l sync s t l r)
(test l sync s t l r)
(let-values ([(sr sw) (tcp-accept l)])
(try-all-blocked)
(close-output-port w)
(test r sync s t l r)
(test r sync s t l r)
(set! r cr)
(try-all-blocked)
(display #\y sw)
(flush-output sw)
(test cr sync s t l sr cr)
(read-char cr)
(try-all-blocked)
(test sw sync s t l sr cr sw)
(display #\z cw)
(flush-output cw)
(test sr sync s t l sr cr)
(read-char sr)
(try-all-blocked)
(test cw sync s t l sr cr cw)
;; Fill up output buffer:
(test sw sync/timeout 0 sw)
(test #t
positive?
(let loop ([n 0])
(if (and (sync/timeout 0 sw)
(= 4096 (write-bytes-avail (make-bytes 4096 (char->integer #\x)) sw)))
(loop (add1 n))
n)))
(test #f sync/timeout 0 sw sr)
(test cr sync/timeout 0 sw sr cr)
;; Flush cr:
(let ([s (make-bytes 4096)])
(let loop ()
(when (and (char-ready? cr)
(= 4096 (read-bytes-avail! s cr)))
(loop))))
(close-output-port sw)
(test cr sync s t l sr cr)
(test cr sync s t l sr cr)
(close-output-port cw)
(test sr sync s t l sr))))
(tcp-close l)))
;; Test limited pipe output waiting:
(let-values ([(r w) (make-pipe 5000)])
(test #f sync/timeout 0 r)
(test w sync/timeout 0 r w)
(display (make-bytes 4999 (char->integer #\x)) w)
(test w sync/timeout 0 w)
(display #\y w)
(test #f sync/timeout 0 w)
(test 0 write-bytes-avail* #"hello" w)
(test r sync/timeout 0 r w)
(read-char r)
(test w sync/timeout 0 w)
(display #\z w)
(test #f sync/timeout 0 w)
(read-bytes 5000 r)
(test #f sync/timeout 0 r)
(test w sync/timeout 0 r w)
(display (make-bytes 5000 (char->integer #\x)) w)
(test r sync/timeout 0 r w)
(test #f sync/timeout 0 w))
;; ----------------------------------------
;; Suspend and resume
;; Suspend main thread:
(let ([v 17]
[s (make-semaphore)]
[t (current-thread)])
(let ([t2 (thread (lambda ()
(thread-suspend t)
(test #f thread-running? t)
(test #f thread-dead? t)
(semaphore-post s)
(sleep SLEEP-TIME)
(test 17 values v)
(thread-resume t)))])
(semaphore-wait s)
(set! v 99)
(thread-wait t2)))
;; Self-suspend main thread:
(let ([v 19]
[t (current-thread)])
(let ([t2 (thread (lambda ()
(sleep SLEEP-TIME)
(test 19 values v)
(thread-resume t)))])
(thread-suspend t)
(set! v 99)
(thread-wait t2)))
;; Self-suspend child thread:
(let ([v 20])
(let ([t2 (thread (lambda ()
(thread-suspend (current-thread))
(set! v 99)))])
(sleep SLEEP-TIME)
(test #f thread-running? t2)
(test #f thread-dead? t2)
(thread-resume t2)
(test 20 values v)
(thread-wait t2)
(test #f thread-running? t2)
(test #t thread-dead? t2)
(test 99 values v)))
;; Suspend child thread:
(let ([v 17]
[s (make-semaphore)]
[t (current-thread)])
(let ([t2 (thread (lambda ()
(semaphore-wait s)
(set! v 99)))])
(thread-suspend t2)
(test #f thread-dead? t2)
(test #f thread-running? t2)
(semaphore-post s)
(sleep SLEEP-TIME)
(test 17 values v)
(thread-resume t2)
(thread-wait t2)
(test #f thread-running? t2)
(test #t thread-dead? t2)
(test 99 values v)))
;; Breaking/killing:
(define /dev/null-for-err
(make-output-port 'dev/null always-evt (lambda (s start end ? ??) (- end start)) void void))
(for-each
(lambda (sleep0)
(test (list 'start-sleep0 sleep0) values (list 'start-sleep0 sleep0))
(let ([goes
(lambda (sleep1 sleep2 break-thread)
(test (list 'start-goes sleep1 sleep2 break-thread) values (list 'start-goes sleep1 sleep2 break-thread))
(test 'external-suspend values 'external-suspend)
(let ([v 10])
(let ([t2 (parameterize ([current-error-port /dev/null-for-err])
(thread
(lambda ()
(let loop () (when (= v 10) (sleep) (loop)))
(sleep0)
(set! v 99))))])
(sleep1)
(thread-suspend t2)
(set! v 20)
(test (void) break-thread t2)
(sleep2)
(test (void) thread-resume t2)
(test (void) thread-wait t2)
(test 20 values v)))
(test 'self-suspend values 'self-suspend)
(let ([v 20])
(let ([t2 (parameterize ([current-error-port /dev/null-for-err])
(thread (lambda ()
(thread-suspend (current-thread))
(sleep0)
(set! v 99))))])
(sleep1)
(break-thread t2)
(sleep2)
;; keep trying to resume until the thread stops:
(let loop ()
(unless (sync/timeout 0 t2)
(thread-resume t2)
(loop)))
(thread-wait t2)
(test 20 values v)))
(let ([w-block
(lambda (post wait)
(test (list 'start-w-block post wait) values (list 'start-w-block post wait))
;; Child thread sleeps
(let ([v 20])
(let ([t2 (parameterize ([current-error-port /dev/null-for-err])
(thread (lambda ()
(wait)
(sleep0)
(set! v 99)
(fprintf (current-error-port) "Thread shouldn't get here! ~a~n" (break-enabled)))))])
(sleep1)
(thread-suspend t2)
(post)
(sleep2)
(break-thread t2)
(sleep2)
(thread-resume t2)
(thread-wait t2)
(test 20 values v)))
(unless (eq? break-thread kill-thread)
(wait)
;; Main thread sleeps
(let ([v 25]
[t (current-thread)]
[done (make-semaphore)])
(with-handlers ([exn:break?
(lambda (x) (semaphore-post done))])
(let ([t2 (thread (lambda ()
(sleep1)
(thread-suspend t)
(post)
(sleep2)
(break-thread t)
(sleep2)
(thread-resume t)
(semaphore-wait done)
(test 25 values v)))])
(wait)
(sleep0)
(set! v 99)
(fprintf (current-error-port) "Shouldn't get here! ~a~n" (break-enabled)))))))])
(test 'sema-block values 'sema-block)
(let ([s (make-semaphore)])
(w-block (lambda () (semaphore-post s))
(lambda () (semaphore-wait s))))
(for-each
(lambda (init)
(test (list 'sema-block/enable-break init) values (list 'sema-block/enable-break init))
(let ([s (make-semaphore)])
(parameterize-break init
(w-block (lambda () (semaphore-post s))
(lambda () (semaphore-wait/enable-break s))))))
'(#t #f))
(test 'ch-block values 'ch-block)
(let ([ch (make-channel)])
(w-block (lambda () (thread (lambda () (channel-put ch 10))))
(lambda () (sync (make-semaphore) ch))))
(for-each
(lambda (init)
(test (list 'ch-block/enable-break init) values (list 'ch-block/enable-break init))
(let ([ch (make-channel)])
(parameterize-break #f
(w-block (lambda () (thread (lambda () (channel-put ch 10))))
(lambda () (sync/timeout/enable-break #f (make-semaphore) ch))))))
'(#t #f))))])
(define BKT-SLEEP-TIME (/ SLEEP-TIME 4))
(goes void void break-thread)
(goes void void kill-thread)
(goes sleep void break-thread)
(goes sleep void kill-thread)
(goes void sleep break-thread)
(goes void sleep kill-thread)
(goes sleep sleep break-thread)
(goes sleep sleep kill-thread)
(goes (lambda () (sleep BKT-SLEEP-TIME)) void break-thread)
(goes (lambda () (sleep BKT-SLEEP-TIME)) void kill-thread)
(goes void (lambda () (sleep BKT-SLEEP-TIME)) break-thread)
(goes void (lambda () (sleep BKT-SLEEP-TIME)) kill-thread)
(goes (lambda () (sleep BKT-SLEEP-TIME)) (lambda () (sleep BKT-SLEEP-TIME)) break-thread)
(goes (lambda () (sleep BKT-SLEEP-TIME)) (lambda () (sleep BKT-SLEEP-TIME)) kill-thread)))
(list sleep void))
;; ----------------------------------------
;; Simple multi-custodian threads
(let ([go
(lambda (1st1st? derived?)
(let* ([c1 (make-custodian)]
[c2 (make-custodian (if derived? c1 (current-custodian)))])
(let ([t (parameterize ([current-custodian c1])
(thread (lambda () (sleep 1000))))])
(test #t thread-running? t)
(thread-resume t c2)
(test #t thread-running? t)
(custodian-shutdown-all (if 1st1st? c1 c2))
(test (not (and derived? 1st1st?)) thread-running? t)
(custodian-shutdown-all (if 1st1st? c1 c2))
(test (not (and derived? 1st1st?)) thread-running? t)
(custodian-shutdown-all (if 1st1st? c2 c1))
(test #f thread-running? t))))])
(go #t #f)
(go #f #f)
(go #t #t)
(go #f #t))
;; Test collapsing custodians for resume
(let* ([c0 (make-custodian)]
[c1 (make-custodian c0)]
[c2 (make-custodian c0)]
[c3 (make-custodian c0)])
(let ([t (parameterize ([current-custodian c1])
(thread (lambda () (sleep 1000))))])
(thread-resume t c2)
(thread-resume t c3)
(thread-resume t c0)
(custodian-shutdown-all c0)
(test #f thread-running? t)))
;; ----------------------------------------
;; Kill versus Suspend
(let* ([v 0]
[loop (lambda ()
(let loop ()
(set! v (add1 v))
(sleep (/ SLEEP-TIME 2))
(loop)))]
[c0 (make-custodian)])
(let ([try
(lambda (resumable?)
(let* ([c (parameterize ([current-custodian c0])
(make-custodian))]
[t (parameterize ([current-custodian c])
((if resumable? thread/suspend-to-kill thread) loop))]
[check-inc (lambda (inc?)
(let ([v0 v])
(sleep SLEEP-TIME)
(test inc? > v v0)))])
(test #t thread-running? t)
(check-inc #t)
(custodian-shutdown-all c)
(test #f thread-running? t)
(test (not resumable?) thread-dead? t)
(test (and resumable? t) sync/timeout 0 (thread-suspend-evt t))
(check-inc #f)
(let ([r (thread-resume-evt t)])
(test #f sync/timeout 0 r)
(set! c (make-custodian))
(thread-resume t c)
(test (and resumable? t) sync/timeout 0 r))
(test resumable? thread-running? t)
(when resumable?
(check-inc #t)
(custodian-shutdown-all c)
(test #f thread-running? t)
(test #f thread-dead? t)
(check-inc #f)
(set! c (make-custodian))
(thread-resume t c)
(test #t thread-running? t)
(check-inc #t)
(kill-thread t)
(test #f thread-running? t)
(test #f thread-dead? t)
(check-inc #f)
(thread-resume t)
(check-inc #t)
(custodian-shutdown-all c)
(thread-wait
(parameterize ([current-custodian c0])
(thread (lambda () (thread-resume t (current-thread))))))
(check-inc #t)
(custodian-shutdown-all c)
(test #t thread-running? t)
(check-inc #t)
(set! c (make-custodian))
(thread-resume t c)
(custodian-shutdown-all c)
(test #t thread-running? t)
(check-inc #t)
(custodian-shutdown-all c0)
(check-inc #f)
(thread-resume t (current-thread))
(check-inc #t)
(custodian-shutdown-all c)
(test #t thread-running? t)
(check-inc #t)
(kill-thread t)
(check-inc #f))))])
(try #f)
(try #t)))
;; Transitive resume:
(let ([go
(lambda (thread c-suspend?)
(parameterize ([current-custodian (make-custodian)])
(letrec ([setup-transitive
(lambda (t depth)
(if (= depth 0)
null
(let ([t1 (thread (lambda () (sleep 1000)))]
[t2 (thread (lambda () (semaphore-wait (make-semaphore))))])
(thread-resume t1 t)
(thread-resume t2 t)
(append
(setup-transitive t1 (sub1 depth))
(setup-transitive t2 (sub1 depth))
(list t1 t2)))))])
(let ([t (thread (lambda () (sleep 10000)))])
(let ([threads (cons t (setup-transitive t 5))])
(for-each (lambda (t)
(test #t thread-running? t))
threads)
(if c-suspend?
(custodian-shutdown-all (current-custodian))
(for-each thread-suspend threads))
(for-each (lambda (t)
(test #f thread-running? t))
threads)
(test (void) thread-resume t)
(for-each (lambda (t)
(test (not c-suspend?) thread-running? t))
threads)))
(custodian-shutdown-all (current-custodian)))))])
(go thread #f)
(go thread/suspend-to-kill #t)
(go thread/suspend-to-kill #f))
(let ([t1 (thread (lambda () (semaphore-wait (make-semaphore))))]
[t2 (thread (lambda () (semaphore-wait (make-semaphore))))]
[t3 (thread (lambda () (semaphore-wait (make-semaphore))))])
(test (void) thread-resume t2 t1)
(test (void) thread-resume t3 t2)
(thread-suspend t1)
(thread-suspend t3)
(test #f thread-running? t1)
(test #t thread-running? t2)
(test #f thread-running? t3)
(thread-resume t1)
;; Thread t3 should not have been resumed...
(test #f thread-running? t3)
(thread-resume t2)
;; Still, thread t3 should not have been resumed...
(test #f thread-running? t3)
(thread-suspend t2)
(thread-resume t2)
;; Now it should be resumed!
(test #t thread-running? t3)
(kill-thread t3)
(thread-suspend t2)
(thread-resume t2)
(test #f thread-running? t3))
;; Transitive custodian addition:
(let ([c1 (make-custodian)]
[c2 (make-custodian)]
[c3 (make-custodian)])
(let ([t1 (parameterize ([current-custodian c1])
(thread/suspend-to-kill (lambda () (sleep 10000))))]
[t2 (parameterize ([current-custodian c2])
(thread/suspend-to-kill (lambda () (sleep 10000))))])
(let ([t2-2 (let loop ([n 5][t t2])
(if (zero? n)
t
(loop (sub1 n)
(parameterize ([current-custodian c2])
(let ([t2 (thread/suspend-to-kill (lambda () (sleep 10000)))])
(thread-resume t2 t)
t2)))))])
(custodian-shutdown-all c2)
(test #f thread-running? t2)
(test #f thread-running? t2-2)
(thread-resume t2)
(test #f thread-running? t2)
(test #f thread-running? t2-2)
(thread-resume t2 t1)
(test #t thread-running? t2)
(test #t thread-running? t2-2)
(thread-resume t1 c3)
(custodian-shutdown-all c1)
(test #t thread-running? t1)
(test #t thread-running? t2)
(test #t thread-running? t2-2)
(custodian-shutdown-all c3)
(test #f thread-running? t1)
(test #f thread-running? t2)
(test #f thread-running? t2-2))))
;; Cyclic thread yokes should be ok:
(let* ([c1 (make-custodian)]
[c2 (make-custodian)]
[t1 (parameterize ([current-custodian c1])
(thread (lambda () (sleep 10000))))]
[t2 (parameterize ([current-custodian c2])
(thread (lambda () (sleep 10000))))])
(thread-resume t1 t2)
(thread-resume t2 t1)
(thread-suspend t1)
(thread-suspend t2)
(test #f thread-running? t1)
(test #f thread-running? t2)
(thread-resume t1)
(test #t thread-running? t1)
(test #t thread-running? t2)
(thread-suspend t1)
(thread-suspend t2)
(test #f thread-running? t1)
(test #f thread-running? t2)
(thread-resume t2)
(test #t thread-running? t1)
(test #t thread-running? t2)
(custodian-shutdown-all c1)
(test #t thread-running? t1)
(test #t thread-running? t2)
(custodian-shutdown-all c2)
(test #f thread-running? t1)
(test #f thread-running? t2))
;; ----------------------------------------
;; Check that a terminated thread cleans up ownership
;; of runstack and mark stack (crashes or doesn't).
;; Also checks that deep runstacks are ok.
(let ()
(define bye #f)
(define hi #f)
(define k #f)
(define s (make-semaphore))
(define s2 (make-semaphore))
(define s3 (make-semaphore))
(define s4 (make-semaphore))
(define s5 (make-semaphore))
(define t0
(thread (lambda ()
(semaphore-wait s)
(semaphore-wait s)
(kill-thread t))))
(define t
(thread (lambda ()
(let loop ([n 4000])
(with-continuation-mark 'x 10
(dynamic-wind
void
(lambda ()
(let ()
(if (zero? n)
(let ()
(let/cc x
(set! k x)
(printf "Bye\n")
(semaphore-post s)
(sync s2 s3 s4 s5))
(printf "Hi\n"))
(loop (sub1 n)))))
void))))))
(semaphore-post s)
(thread-wait t)
(thread-wait (thread k)))
; ----------------------------------------
(let ([c (make-thread-cell 10)]
[c2 (make-thread-cell -10 #t)]
[c3 (make-thread-cell 3 #t)])
(test 10 thread-cell-ref c)
(test -10 thread-cell-ref c2)
(let ([orig (current-preserved-thread-cell-values)])
(test (void) thread-cell-set! c 11)
(test (void) thread-cell-set! c2 -11)
(test 11 thread-cell-ref c)
(test -11 thread-cell-ref c2)
(test 3 thread-cell-ref c3)
(let ([check-sub
(lambda ()
(thread-wait (thread (lambda ()
(test 10 thread-cell-ref c)
(test -11 thread-cell-ref c2)
(test 3 thread-cell-ref c3)
(test (void) thread-cell-set! c 12)
(test 12 thread-cell-ref c)
(test (void) thread-cell-set! c2 -12)
(test -12 thread-cell-ref c2)
(test (void) thread-cell-set! c3 13)
(test 13 thread-cell-ref c3)))))]
[post (current-preserved-thread-cell-values)])
(check-sub)
(current-preserved-thread-cell-values orig)
(test 11 thread-cell-ref c)
(test -10 thread-cell-ref c2)
(test 3 thread-cell-ref c3)
(test (void) thread-cell-set! c3 23)
(test 23 thread-cell-ref c3)
(current-preserved-thread-cell-values post)
(test 11 thread-cell-ref c)
(test -11 thread-cell-ref c2)
(test 3 thread-cell-ref c3)
(check-sub)
(thread-wait (thread (lambda ()
(current-preserved-thread-cell-values post)
(test 10 thread-cell-ref c)
(test -11 thread-cell-ref c2)
(test 3 thread-cell-ref c3)
(test (void) thread-cell-set! c3 13)
(test 13 thread-cell-ref c3)
(current-preserved-thread-cell-values post)
(test 3 thread-cell-ref c3)))))))
; --------------------
(report-errs)