

(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)
