How to get backtrace of `@async` Task


#1

I’m unsure how to get the thrown error or backtrace of an asynchronous Task.

Here’s an example script:

# silent_test_all_in_one.jl

function do_some_work(increase::Real)
    a = 0.0
    start_time = time()
    while time() - start_time < 5
        b += increase
        sleep(0.1)
    end
    a
end

function start_blocking()
    println("start() is waiting for do_some_work() to be done")
    do_some_work(10)
end

function start_async()
    start_time = time()
    t = @async do_some_work(10)
    while !istaskdone(t)
        println("start() is waiting for do_some_work() to be done")
        sleep(1)
    end
end

To test, I did the following

  1. Save that file as “silent_test_all_in_one.jl” and include it at the REPL
  2. call start_blocking and see the appropriate error
  3. call start_async and don’t see any indication of an error
julia> include("silent_test_all_in_one.jl")
start_async (generic function with 1 method)

julia> start_blocking()
start() is waiting for do_some_work() to be done
ERROR: UndefVarError: b not defined
Stacktrace:
 [1] do_some_work at C:\Users\dave\Documents\silent_test_all_in_one.jl:7 [inlined]
 [2] start_blocking() at C:\Users\dave\Documents\silent_test_all_in_one.jl:15
 [3] top-level scope at none:0

julia> start_async()
start() is waiting for do_some_work() to be done

julia> versioninfo()
Julia Version 1.0.2
Commit d789231e99 (2018-11-08 20:11 UTC)
Platform Info:
  OS: Windows (x86_64-w64-mingw32)
  CPU: Intel(R) Core(TM) i7-4770 CPU @ 3.40GHz
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-6.0.0 (ORCJIT, haswell)

Is this expected? Can this be avoided?

I’m working on a project that uses HTTP.jl to handle some requests asynchronously with Tasks. Often, the code “hangs” in the REPL and gives no feedback and that’s when I realized that asynchronous Taskss don’t seem to print out thrown errors or backtraces when the code actually errors out.


#2

You can either print the error yourself or look at what the task resolves to:

function start_async_print()
    start_time = time()
    t = @async try
        do_some_work(10)
    catch err
        bt = catch_backtrace()
        println()
        showerror(stderr, err, bt)
    end
    while !istaskdone(t)
        println("start() is waiting for do_some_work() to be done")
        sleep(1)
    end
end

function start_async_throw()
    start_time = time()
    t = do_some_work(10)
    while !istaskdone(t)
        println("start() is waiting for do_some_work() to be done")
        sleep(1)
    end
    wait(t) # or fetch(t)
end

gives

julia> start_async_print()
start() is waiting for do_some_work() to be done

UndefVarError: b not defined
Stacktrace:
 [1] do_some_work at ./untitled-8822451613b5304f5f50924dadde5d27:6 [inlined]
 [2] (::getfield(Main, Symbol("##27#28")))() at ./task.jl:259
julia> start_async_throw()
ERROR: UndefVarError: b not defined
Stacktrace:
 [1] do_some_work at ./untitled-8822451613b5304f5f50924dadde5d27:6 [inlined]
 [2] start_async_throw() at ./untitled-8822451613b5304f5f50924dadde5d27:33
 [3] top-level scope at none:0

The wait/fetch approach obviously doesn’t work very well when you don’t want to block.


#3

Thank you! Is there a good way to encapsulate that behavior in a new macro? This seems to work:

macro async_showerr(ex)
    quote
        t = @async try
            eval($(esc(ex)))
        catch err
            bt = catch_backtrace()
            println()
            showerror(stderr, err, bt)
        end
    end
end

But is there a downside to using a macro like this?


#4

@pfitzseb I’m curious why @async doesn’t do this automatically and I’m trying to imagine the downsides.

This seems like an easily avoidable trap for newbies. For more advanced users who want the current behavior explicitly, they could call a macro like @async_silent.

Does that make sense?