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.