SIGINT: no exception handler available

A standard way to terminate a process is using an OS signal.

With Julia we may capture a SIGINT signal, for example generated by a user pressing Ctrl-C, and try to shutdown in a controlled way the process.

But the fact that an InterruptException is delivered to one task chosen at discretion of the runtime task scheduler make it very difficult to design a robust application because each concurrent/parallel task have to catch and manage correctly the InterruptException in cooperation with all the other tasks.

Besides this there are pathological cases where it became impossible: in fact the InterruptException may be delivered to a terminated task and in this scenario nothing can be done to control the shutdown logic.

The following simple example shows the case when the interrupt is delivered to a terminated task. When this occurs a fatal error is raised:

   fatal: error thrown and no exception handler available.

demo.jl:

function task(_)
    println("doing something")
end

Timer(task, 1)

function main()
    try
        while true
            sleep(5)
        end
    catch e
        println("got $e: clean shutdown ...")
    end
end

main()

Running

julia -e 'include(pop!(ARGS))' demo.jl

and pressing Ctrl-C after the timer timed out but in 5 seconds you get:

doing something
^Cfatal: error thrown and no exception handler available.
InterruptException()
_jl_mutex_unlock at /cache/build/default-amdci5-5/julialang/julia-release-1-dot-9/src/threading.c:798
jl_mutex_unlock at /cache/build/default-amdci5-5/julialang/julia-release-1-dot-9/src/julia_locks.h:81 [inlined]
ijl_task_get_next at /cache/build/default-amdci5-5/julialang/julia-release-1-dot-9/src/partr.c:394
poptask at ./task.jl:963
wait at ./task.jl:972
task_done_hook at ./task.jl:672
jfptr_task_done_hook_31470.clone_1 at /home/adona/.asdf/installs/julia/1.9.0-rc1/lib/julia/sys.so (unknown line)
_jl_invoke at /cache/build/default-amdci5-5/julialang/julia-release-1-dot-9/src/gf.c:2731 [inlined]
ijl_apply_generic at /cache/build/default-amdci5-5/julialang/julia-release-1-dot-9/src/gf.c:2913
jl_apply at /cache/build/default-amdci5-5/julialang/julia-release-1-dot-9/src/julia.h:1878 [inlined]
jl_finish_task at /cache/build/default-amdci5-5/julialang/julia-release-1-dot-9/src/task.c:320
start_task at /cache/build/default-amdci5-5/julialang/julia-release-1-dot-9/src/task.c:1103

The Visor way

Visor capture SIGINT and shutdown in a reliable way all supervised task following the shutdown logic configured by the application.

The above example instrumented with Visor becames:

using Visor

function task(_)
    println("doing something")
end

Timer(task, 1)

function main(self)
    try
        while true
            sleep(5)
        end
    catch e
        println("got $e: clean shutdown ...")
    end
end

supervise([process(main)]);
doing something
^Cgot Visor.ProcessInterrupt("main"): clean shutdown ...