Shouldn’t I get notified it a inner task fails?
Yes you should:
julia> t = @async begin
sleep(1)
error("FAIL")
end
Task (runnable) @0x00007f0fd9203820
julia> fetch(t)
ERROR: TaskFailedException:
FAIL
Stacktrace:
[1] error(::String) at ./error.jl:33
[2] macro expansion at ./REPL[1]:3 [inlined]
[3] (::var"#5#6")() at ./task.jl:333
Stacktrace:
[1] wait at ./task.jl:251 [inlined]
[2] fetch(::Task) at ./task.jl:266
[3] top-level scope at REPL[2]:1
julia> t = Threads.@spawn begin
sleep(1)
error("FAIL")
end
Task (runnable) @0x00007f0fd92004f0
julia> fetch(t)
ERROR: TaskFailedException:
FAIL
Stacktrace:
[1] error(::String) at ./error.jl:33
[2] macro expansion at ./REPL[3]:3 [inlined]
[3] (::var"#7#8")() at ./threadingconstructs.jl:113
Stacktrace:
[1] wait at ./task.jl:251 [inlined]
[2] fetch(::Task) at ./task.jl:266
[3] top-level scope at REPL[4]:1
Or are we not speaking of the same thing?
Thank you for the quick anwser!
julia> Threads.@spawn @async error("Fail")
Task (done) @0x0000000010821fb0
I did this.
You need to fetch the result.
Shouldn’t this fire anyway?
I think it should. We need structured concurrency for this:
https://github.com/JuliaLang/julia/issues/33248
Until we get structured concurrency, making sure to wrap @async
and @spawn
always with @sync
is the closest thing you can do.
How would I use @sync
here?
function t()
@async error("")
return 1
end
t()
Either return the task and wait for it or put @sync
in t
. @sync
syncs the tasks that are lexically enclosed.
Treating something like this safely requires so-called nursery (also known as task group and a few other names). ATM, if you want a task to live longer than the function creating it, you need to manually pass it back to the caller. It’s not safe (you may forget doing so). That’s why I think we need structured concurrency.
I think I don’t mind the unstructured concurrency. But it’s very important to have a stacktrace if something fails! Where is the right place to say that?
In what task should the error be thrown?
I think the whole program should stop running unless you started the task in a try-catch-block.
I think the error should be thrown in the most inner task and propagate out until it reaches a try-catch-block or crashes the program.
The stack trace should go from the occurred error through the started task and so on.
These are my first thoughts
That is structured concurrency.
Would this
function f()
@async sleep(10)
return 1
end
try f() |> println catch end
dont_have_to_wait_and_do_the_next_thing()
still work with structured concurrency?
You’d write something like
function f(nursery)
@async nursery sleep(10)
return 1
end
try
withnursery() do nursery
f(nursery) |> println
dont_have_to_wait_and_do_the_next_thing()
end
catch
end
I don’t understand the syntax. What is withnursery()
and where is it defined?
The syntax gets more complicated. What’s the point against:
It’s bad because the caller can’t tell if your function or any function that might exist in the call stack starting from your function may leak un-synchronized tasks; i.e., it violates so-called “black box rule”.
For more information, please have a look at Julep Juleps/StructuredConcurrency.md at master · JuliaLang/Juleps · GitHub and the discussion in the PR. Your point is related to this discussion on effect system which is effectively a dynamically-scoped nursery: Taking Structured Concurrency Seriously · Issue #33248 · JuliaLang/julia · GitHub
I’m not sure, what I want. Both sites have their benefits…
This is my solution as far:
macro my_async(ex)
quote
@async try $(esc(ex))
catch e
println("IN @my_async")
# Here I would like to get the definition line number of `ex`
@show e
end
end
end
So now I get feedback if a task fails.
FWIW, you can improve the error printing with
macro my_async(ex)
quote
@async try
$(esc(ex))
catch e
println("In task:")
@error(exception = (e, catch_backtrace()))
end
end
end