I was needing to find a reason why a given fiber was hanged. This situation can happen when waiting for IO or Channel operation that seems it will never arrive.
To solve this I, temporally, added the call-stack of the fiber before yielding, so given a Fiber instance I will be able to print where it has stopped.
It was great to have that information so explicitly. Here is the patch I used and a small example.
The example spawn a fiber that need two receive actions two finish. After sending one, the main fiber asks where the spawned fiber f
is hanged. The backtrace shows exactly it is on the second receive.
# file: foo.cr
require "./fiber-debug"
def foo(ch)
bar(ch)
end
def bar(ch)
ch.receive
ch.receive
end
ch = Channel(Nil).new
f = spawn(name: "worker") do
foo(ch)
end
ch.send nil
f.print_backtrace
# Fiber's backtrace (name: worker):
# fiber-debug.cr:17:27 in 'resume'
# crystal-0.36.1/src/crystal/scheduler.cr:48:5 in 'resume'
# crystal-0.36.1/src/fiber.cr:197:5 in 'resume'
# crystal-0.36.1/src/crystal/scheduler.cr:149:9 in 'reschedule'
# crystal-0.36.1/src/crystal/scheduler.cr:44:5 in 'reschedule'
# crystal-0.36.1/src/channel.cr:311:7 in 'receive'
# foo.cr:9:3 in 'bar' <-- The second ch.receive 🎉🎉🎉
# foo.cr:4:3 in 'foo'
# foo.cr:15:3 in '->'
# crystal-0.36.1/src/primitives.cr:255:3 in 'run'
# crystal-0.36.1/src/fiber.cr:92:34 in '->'
ch.send nil
This is the temporal extension I used.
# file: fiber-debug.cr
class Fiber
property call_stack : Exception::CallStack?
def print_backtrace
puts "Fiber's backtrace (name: #{name}):"
if call_stack = @call_stack
call_stack.printable_backtrace.each { |l| puts l }
else
puts "(running, unable to get backtrace)"
end
puts
end
end
class Crystal::Scheduler
protected def resume(fiber : Fiber) : Nil
@current.call_stack = Exception::CallStack.new
previous_def
@current.call_stack = nil
end
end