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
- Save that file as “silent_test_all_in_one.jl” and include it at the REPL
- call
start_blocking
and see the appropriate error
- 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 Tasks
s don’t seem to print out thrown errors or backtraces when the code actually errors out.
1 Like
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.
1 Like
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?
3 Likes
@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?
2 Likes
This is an old topic, but I have the very same issue with async tasks: they don’t show a useful stacktrace be default. Not only @async
itself, but also e.g. asyncmap
:
asyncmap(0:10) do i
1 ÷ i
end
ERROR: DivideError: integer division error
Stacktrace:
[1] (::Base.var"#770#772")(::Task) at ./asyncmap.jl:178
[2] foreach(::Base.var"#770#772", ::Array{Any,1}) at ./abstractarray.jl:2009
[3] maptwice(::Function, ::Channel{Any}, ::Array{Any,1}, ::UnitRange{Int64}) at ./asyncmap.jl:178
[4] wrap_n_exec_twice at ./asyncmap.jl:154 [inlined]
[5] #async_usemap#755 at ./asyncmap.jl:103 [inlined]
[6] #asyncmap#754 at ./asyncmap.jl:81 [inlined]
[7] asyncmap(::Function, ::UnitRange{Int64}) at ./asyncmap.jl:81
[8] top-level scope at REPL[3]:1
compare to map
:
map(0:10) do i
1 ÷ i
end
ERROR: DivideError: integer division error
Stacktrace:
[1] div at ./int.jl:260 [inlined]
[2] #13 at ./REPL[4]:2 [inlined]
[3] iterate at ./generator.jl:47 [inlined]
[4] _collect(::UnitRange{Int64}, ::Base.Generator{UnitRange{Int64},var"#13#14"}, ::Base.EltypeUnknown, ::Base.HasShape{1}) at ./array.jl:699
[5] collect_similar(::UnitRange{Int64}, ::Base.Generator{UnitRange{Int64},var"#13#14"}) at ./array.jl:628
[6] map(::Function, ::UnitRange{Int64}) at ./abstractarray.jl:2162
[7] top-level scope at REPL[4]:1
Is there any way to force the stacktrace go further than the asyncmap
internals, i.e. to the actual user function? I can only imagine putting try ... catch ... end
there manually.
3 Likes
airpmb
6
Wondering in the same thing; I very much got snared in this newbie trap.
1 Like
There’s an errormonitor
function to print errors thrown in an @async
task.
julia> errormonitor(@async (sleep(1.0); error("bla")))
Task (runnable) @0x00007fbe2ae5bb70
julia> Unhandled Task ERROR: bla
Stacktrace:
[1] error(s::String)
2 Likes