Skip to content

Commit

Permalink
interrupt handlers: Add test and docstrings
Browse files Browse the repository at this point in the history
jpsamaroo committed May 5, 2023
1 parent 28edeb0 commit 8a9b460
Showing 2 changed files with 57 additions and 6 deletions.
34 changes: 28 additions & 6 deletions base/task.jl
Original file line number Diff line number Diff line change
@@ -993,9 +993,31 @@ else
pause() = ccall(:pause, Cvoid, ())
end

interrupt_handlers() =
ccall(:jl_get_interrupt_handlers, Any, ())::Vector{Task}
register_interrupt_handler(t::Task) =
ccall(:jl_register_interrupt_handler, Cvoid, (Any,), t)
unregister_interrupt_handler(t::Task) =
ccall(:jl_unregister_interrupt_handler, Cvoid, (Any,), t)
"""
register_interrupt_handler(handler::Task)
Registers the task `handler` to handle interrupts (such as from Ctrl-C). When
an interrupt is received, all registered handler tasks will be scheduled at the
next `yield` call, with an `InterruptException` thrown to them. Once any
handler is registered, the runtime will only throw `InterruptException`s to
handlers, and not to any other task, allowing the handlers to soak up and
safely handle interrupts.
To unregister a previously-registered handler, use
[`unregister_interrupt_handler`](@ref).
!!! warn
Note that non-yielding tasks may block interrupt handlers from running;
this means that once an interrupt handler is registered, code like `while
true end` may become un-interruptible.
"""
register_interrupt_handler(handler::Task) =
ccall(:jl_register_interrupt_handler, Cvoid, (Any,), handler)
"""
unregister_interrupt_handler(handler::Task)
Unregisters the interrupt handler task `handler`; see
[`register_interrupt_handler`](@ref) for further details.
"""
unregister_interrupt_handler(handler::Task) =
ccall(:jl_unregister_interrupt_handler, Cvoid, (Any,), handler)
29 changes: 29 additions & 0 deletions test/stress.jl
Original file line number Diff line number Diff line change
@@ -84,5 +84,34 @@ if !Sys.iswindows()
ccall(:jl_gc_safepoint, Cvoid, ()) # wait for SIGINT to arrive
end
end

# interrupt handlers
let exc_ref = Ref{Any}()
handler = Threads.@spawn begin
try
wait()
catch exc
exc_ref[] = exc
end
end
yield() # let the handler start
Base.register_interrupt_handler(handler)
ccall(:kill, Cvoid, (Cint, Cint,), getpid(), 2)
for i in 1:10
Libc.systemsleep(0.1)
yield() # wait for the handler to be run
end
Base.unregister_interrupt_handler(handler)
@test isassigned(exc_ref) && exc_ref[] isa InterruptException
end

# ensure we revert to original interrupt behavior
@test_throws InterruptException begin
ccall(:kill, Cvoid, (Cint, Cint,), getpid(), 2)
for i in 1:10
Libc.systemsleep(0.1)
ccall(:jl_gc_safepoint, Cvoid, ()) # wait for SIGINT to arrive
end
end
Base.exit_on_sigint(true)
end

0 comments on commit 8a9b460

Please sign in to comment.