[plt-scheme] threads, breaks, and races

From: Matthew Flatt (mflatt at cs.utah.edu)
Date: Sat Jun 30 03:13:54 EDT 2007

At Sat, 30 Jun 2007 02:54:51 -0400 (EDT), Dimitris Vyzovitis wrote:
> I would expect this code to die silently:
> (let ((thr (thread (lambda ()
>                      (with-handlers ((void void))
>                        (sync never-evt))))))
>   (break-thread thr))
> 
> Instead, I get this:
> (let ((thr (thread (lambda ()
>                      (with-handlers ((void void))
>                        (sync never-evt))))))
>   (break-thread thr))
> > user break

Right. The break is likely to be received before evaluation reaches
`with-handlers'. As your subject line says, there's a race condition.

> In fact, it seems that the break is not delivered to the
> target thread at all. Instead, it is captured by the main thread (but
> the continuation in the main thread is not broken!):
> > (let ((thr (thread (lambda ()
>                      (with-handlers ((void void))
>                        (sync never-evt))))))
>   (break-thread thr)
>   (thread-dead? thr))
> #f
> > user break

Same deal. The `thread-dead?' expression is very likely to be evaluated
before the new thread has run at all, so that it hasn't had a chance to
handle a break signal. When it does run, it handles the break before
getting into the `with-handlers' form. The break is definitely
delivered to the right thread.

> Now, if I add a sleep before breaking the thread, the break is delivered
> normally:
> 
> > (let ((thr (thread (lambda ()
>                      (with-handlers ((void void))
>                        (sync never-evt) 'boo)))))
>   (sleep 1)
>   (break-thread thr)
>   (sleep 1)
>   (thread-dead? thr))
> #t

Yes (unless your machine is extremely busy and you're unlucky with
thread scheduling).


Consider

 (let* ((ready (make-semaphore))
        (thr (thread 
              (lambda ()
                (with-handlers ((exn:break? void))
                  (semaphore-post ready)
                  (sync never-evt))))))
   (semaphore-wait ready)
   (break-thread thr))

This kind of pattern is very common in programs that use breaks
properly. It reliably waits until the new thread is ready before
sending it a break.

Then again, there are very few programs that use breaks correctly for
something like cooperative termination. Unless you've done it a lot, I
suggest that it's even more difficult than you think. :) Certainly,
it's too difficult for me in all but the simplest cases. In contrast, I
find that non-cooperative termination (using custodians) is something I
can reliably understand.

Matthew



Posted on the users mailing list.