Suppose I have a few tasks synchronized by @sync and @async as show below:
running = true
function task()
i = 0
while running
println(i+=1)
sleep(1)
end
end
@sync begin
@async task()
@async task()
end
How can I catch an InterruptException and set running=false to gracefully shutdown the tasks? Currently Julia will crash immediately:
β> ~/code julia async-interrupt.jl 19:58:321
1
2
2
^C
signal (2): Interrupt
in expression starting at /home/ubuntu/code/async-interrupt.jl:14
epoll_wait at /lib/x86_64-linux-gnu/libc.so.6 (unknown line)
uv__io_poll at /workspace/srcdir/libuv/src/unix/epoll.c:240
uv_run at /workspace/srcdir/libuv/src/unix/core.c:383
jl_task_get_next at /buildworker/worker/package_linux64/build/src/partr.c:481
poptask at ./task.jl:827
wait at ./task.jl:836
wait at ./condition.jl:123
_trywait at ./asyncevent.jl:118
wait at ./asyncevent.jl:136 [inlined]
sleep at ./asyncevent.jl:221
task at /home/ubuntu/code/async-interrupt.jl:7
#2 at ./task.jl:423
unknown function (ip: 0x7fd6b2142fdf)
_jl_invoke at /buildworker/worker/package_linux64/build/src/gf.c:2247 [inlined]
jl_apply_generic at /buildworker/worker/package_linux64/build/src/gf.c:2429
jl_apply at /buildworker/worker/package_linux64/build/src/julia.h:1788 [inlined]
start_task at /buildworker/worker/package_linux64/build/src/task.c:877
unknown function (ip: (nil))
Allocations: 2723 (Pool: 2712; Big: 11); GC: 0
julia> function job()
try
sleep(50)
catch
println("woked")
end
end
julia> try
disable_sigint() do
@sync for i in 1:100
@async job()
end
end
catch
println("ctrl-c")
end
# code is now running
# first ctrl-c
woked
# second ctrl-c
ctrl-c
julia>
I leave doing everything in the right order as an exercise for the reader
@async returns a Task - you can interact with that, though note that interrupting running tasks from a different tasks is generally not a good idea. Itβs usually better to write the tasks in a way that they check some condition from time to time to make sure they can shut down gracefully or have some waitable object that you can close.
You can then try ... catch InterruptException ... end to handle that case - though be aware that this is pretty finicky, as itβs not really defined where exactly youβll get that.
running = true
function task()
try
i = 0
while running
println(i+=1)
sleep(1)
end
println("Task is stopped")
catch err
global running = false
@error "Task is interrupted" exception = (err, catch_backtrace())
end
end
Base.exit_on_sigint(false)
@sync begin
@async task()
@async task()
end
println("Program exited")
Now the code will exit gracefully after ctrl+c:
~/julia ξ° ./julia async-interupt.jl
1
1
^Cβ Error: Task is interrupted
β exception =
β InterruptException:
β Stacktrace:
β [1] poptask(W::Base.InvasiveLinkedListSynchronized{Task})
β @ Base ./task.jl:935
β [2] wait()
β @ Base ./task.jl:944
β [3] wait(c::Base.GenericCondition{Base.Threads.SpinLock})
β @ Base ./condition.jl:124
β [4] _trywait(t::Timer)
β @ Base ./asyncevent.jl:129
β [5] wait
β @ ./asyncevent.jl:147 [inlined]
β [6] sleep(sec::Int64)
β @ Base ./asyncevent.jl:232
β [7] task()
β @ Main ~/julia/async-interupt.jl:7
β [8] (::var"#2#4")()
β @ Main ./task.jl:490
β @ Main ~/julia/async-interupt.jl:11
Task is stopped
Program exited
This is only the behavior if nothing else catches that on the main task. The ambiguity lies in who ultimately receives the SIGINT, which in this case ends up being the main task since you throw a new error in the inner task, which gets rethrown to the @sync task. If you have
try
@sync begin
@async task()
@async task()
end
catch err
# ...
end
It wonβt just exit by default, even with exit_on_sigint(true).