[racket] Breaks always go to main thread?

From: Greg Hendershott (greghendershott at gmail.com)
Date: Wed Mar 18 10:53:34 EDT 2015

Preface to avoid any "XY problem": I recently added errortrace support
to racket-mode. But I noticed that C-c is bypassing this. Which is sad;
definitely want good context for that -- not "running body".

Digging in, it seems that exn:break is only ever given to the main
thread.

[This matters because the user program is running on a child thread (as
with racket/sandbox or the eventspace-handler-thread in DrR). The
interesting errortrace info is on/about the child thread. The main
thread is just sitting on a sync.]

I didn't realize that exn:break is unlike exn:fail (other exn's
generally?) in this respect.

As a distilled example:

  #lang racket/base

  (thread
   (λ ()
     (with-handlers ([exn:fail?
                      (λ _ (displayln "Fail caught in sub thread. Yay!"))])
       (/ 1 0))))

  (thread
   (λ ()
     (with-handlers ([exn:break? ;never gets here
                      (λ _ (displayln "Break caught in sub thread. Yay!"))])
       (displayln "Press C-c")
       (let loop ([n 0])
         (displayln n)
         (sleep 1)
         (loop (add1 n))))))

  (with-handlers ([exn:break?
                   (λ _ (displayln "Break caught in main thread. Boo!"))])
    (sync never-evt))

Running it:

    #<thread:/tmp/break.rkt:3:8>
    #<thread:/tmp/break.rkt:8:8>
    Press C-c
    0
    Fail caught in sub thread. Yay!
    1
    2
      C-c C-cBreak caught in main thread. Boo!

After some pondering and doc-re-reading, my only idea is to catch the
exn:break in the main thread, and break-thread the child thread, to
catch it there, too. For example:

  (define thd
    (thread
     (λ ()
       (with-handlers ([exn:break?
                        (λ _ (displayln "Break caught in sub thread. Yay!"))])
         (displayln "Press C-c")
         (let loop ([n 0])
           (displayln n)
           (sleep 1)
           (loop (add1 n)))))))

  (with-handlers ([exn:break?
                   (λ _
                     (break-thread thd)
                     (sleep))])
    (sync never-evt))

Running it:

    racket -t /tmp/break.rkt
    Press C-c
    0
    1
      C-c C-cBreak caught in sub thread. Yay!

So, "it works". And now racket-mode shows the full errortrace on a
break, apparently fixing the bug.

But do I understand this correctly? I'm cautious generally, but also
specifically because I didn't see this described in the main docs --
that exn:break is only ever given to the main thread.

Any caveats, cautions, advice?

Posted on the users mailing list.