What is Racket DOING???

A neat feature of the JVM is that, out of the box, you can send a running JVM process a SIGQUIT signal and it'll dump stack traces for all running threads to stdout. The output looks like this. It can be really handy when you're trying to debug a live system.

Racket doesn't have this feature, but you can build something like it yourself by combining some of the introspection tools the runtime system provides to you. Given a Racket thread, you can get its continuation marks, using the continuation-marks procedure:

> (continuation-marks (thread void))
#<continuation-mark-set>

With a set of marks in hand, you can get an approximate stack trace by calling continuation-mark-set->context:

> (continuation-mark-set->context
   (let ([thd (thread (λ () (let loop () (sleep 5) (loop))))])
     ;; Give the thread a chance to activate.
     (sync (system-idle-evt))
     (continuation-marks thd)))
(list (cons #f (srcloc 'string 2 20 53 42)))

The result is a list of pairs of procedure names (or #f if a procedure name is not available, as above) and source locations. Converting that list to a textual stack trace is straightforward.

The next step is to get a list of all running threads. All threads in Racket are managed by a custodian. If you have access to a custodian and its parent, you can ask for all of the objects managed by that custodian by calling custodian-managed-list.

> (define root (current-custodian))
> (current-custodian (make-custodian root))
> (define thd (thread (lambda () (let loop () (sleep 5) (loop)))))
> (custodian-managed-list (current-custodian) root)
'(#<thread>)

The result may include custodians subordinate to the custodian you're querying:

;; ... continued from above
> (define child (make-custodian))
> (define thd-of-child
    (parameterize ([current-custodian child])
      (thread (lambda () (let loop () (sleep 5) (loop))))))
> (custodian-managed-list (current-custodian) root)
'(#<thread> #<custodian>)

So, you have to collect the list of threads recursively by dispatching on the types of the values in the list:

;; ...continued from above
> (define thds
    (let loop ([v (current-custodian)])
      (cond
        [(thread? v) (list v)]
        [(custodian? v) (loop (custodian-managed-list v root))]
        [(list? v) (apply append (map loop v))]
        [else null])))
> thds
'(#<thread> #<thread>)

With that, you can print stack traces for all threads reachable from the topmost custodian your program or library has access to.

This is now a a built-in feature of dbg. The client has a new dump-threads procedure that returns a string representing the stack traces of all the threads accessible by the debugging server in a process1 and the GUI displays that same information under a new "Threads" tab.

  1. More specifically, in a place, since each place has its own custodian tree.