import Base.Threads: @spawn
macro async_showerr(ex)
quote
@async try
eval(esc(ex))
catch err
rethrow()
end
end
end
function bad_func2(i)
try
error("error from bad_func2")
catch err
rethrow()
end
end
function bad_func(i)
error("error from bad_func")
end
function main()
is = collect(1:3)
while true
println("looping")
@sync for i in is
# @async bad_func(i) # shows error & halts
# @async bad_func2(i) # shows error & halts
@async_showerr bad_func(i) # no error, causes infinite loop. Why?
end
sleep(0.5)
end
end
main()
macro async_showerr(ex)
quote
@async try
eval(esc(ex))
catch err
rethrow()
end
end
end
I think the macro has hygiene problem. @sync will create a local channel named sync and @async will push task to this local channel. This relies on the escaped symbol sync (otherwise, they may refer to different variable with the same name). However, your macro async_showerr pollutes the scope and cause the mismatch of these two symbols. We can check this by calling @code_lowered main():
So you can see two symbols sync#41 and Main.:(var"##sync#41"). One is local and the other refers to a symbol in Main. So Julia will not ever synchronize at all.
To solve this problem, we need to escape the whole quote expression and everything is fine:
macro async_showerr(ex)
esc(quote
@async try
eval($ex)
catch err
rethrow()
end
end)
end
And we get:
julia> main()
looping
ERROR: TaskFailedException
nested task error: error from bad_func
Stacktrace:
[1] error(s::String)
@ Base ./error.jl:33
[2] bad_func(i::Int64)
@ Main ./REPL[18]:2
[3] (::var"#5#6"{Int64})()
@ Main ./task.jl:411
...and 2 more exceptions.
Stacktrace:
[1] sync_end(c::Channel{Any})
@ Base ./task.jl:369
[2] macro expansion
@ ./task.jl:388 [inlined]
[3] main()
@ Main ./REPL[19]:5
[4] top-level scope
@ REPL[20]:1