How to debug in coroutine(task)?

Debugging tasks can be quite challenging. I used to have this prototype project where the tasks, spawned a task which spawned a task. It was a bad design, but for purposes to get it done, I found that redefining the @async macro like:

import Base.sync_varname
import Base.@async

macro async(expr)

    tryexpr = quote
        try
            $expr
        catch err
            @warn "error within async" exception=err # line $(__source__.line):
            @show stacktrace(catch_backtrace())
        end
    end

    thunk = esc(:(()->($tryexpr)))

    var = esc(sync_varname)
    quote
        local task = Task($thunk)
        if $(Expr(:isdefined, var))
            push!($var, task)
        end
        schedule(task)
        task
    end
end

works quite well while debugging.

From my limited experience, and this really is a matter of opinion, a good strategy is also to test the spawned function separately. To do that, it helps to use channels for IO. For instance, reading a string from a socket can be refactored into reading byte string from a Channel, which is easier to test. In addition to avoiding introducing more tasks, one can subtype AbstractChannel, and that way insert a code dealing with the IO object.

Another thing that helps in debugging tasks is reformulating the logic in communicating finite state machines. Something like:

newstate, newmsg = step(state, msg)

works quite well with Julia’s multiple dispatch mechanism. This design also goes hand in hand with the ability to notify condition condition = Threads.Condition() with a value like:

notify(condition, state)

enabling to debug the code within the task without using the @show method.

2 Likes