Method: Thread.handle_interrupt
- Defined in:
- thread.c
.handle_interrupt(hash) { ... } ⇒ Object
Changes asynchronous interrupt timing.
interrupt means asynchronous event and corresponding procedure by Thread#raise, Thread#kill, signal trap (not supported yet) and main thread termination (if main thread terminates, then all other thread will be killed).
The given hash
has pairs like ExceptionClass => :TimingSymbol
. Where the ExceptionClass is the interrupt handled by the given block. The TimingSymbol can be one of the following symbols:
:immediate
-
Invoke interrupts immediately.
:on_blocking
-
Invoke interrupts while BlockingOperation.
:never
-
Never invoke all interrupts.
BlockingOperation means that the operation will block the calling thread, such as read and write. On CRuby implementation, BlockingOperation is any operation executed without GVL.
Masked asynchronous interrupts are delayed until they are enabled. This method is similar to sigprocmask(3).
NOTE
Asynchronous interrupts are difficult to use.
If you need to communicate between threads, please consider to use another way such as Queue.
Or use them with deep understanding about this method.
Usage
In this example, we can guard from Thread#raise exceptions.
Using the :never
TimingSymbol the RuntimeError exception will always be ignored in the first block of the main thread. In the second ::handle_interrupt block we can purposefully handle RuntimeError exceptions.
th = Thread.new do
Thread.handle_interrupt(RuntimeError => :never) {
begin
# You can write resource allocation code safely.
Thread.handle_interrupt(RuntimeError => :immediate) {
# ...
}
ensure
# You can write resource deallocation code safely.
end
}
end
Thread.pass
# ...
th.raise "stop"
While we are ignoring the RuntimeError exception, it’s safe to write our resource allocation code. Then, the ensure block is where we can safely deallocate your resources.
Guarding from Timeout::Error
In the next example, we will guard from the Timeout::Error exception. This will help prevent from leaking resources when Timeout::Error exceptions occur during normal ensure clause. For this example we use the help of the standard library Timeout, from lib/timeout.rb
require 'timeout'
Thread.handle_interrupt(Timeout::Error => :never) {
timeout(10){
# Timeout::Error doesn't occur here
Thread.handle_interrupt(Timeout::Error => :on_blocking) {
# possible to be killed by Timeout::Error
# while blocking operation
}
# Timeout::Error doesn't occur here
}
}
In the first part of the timeout
block, we can rely on Timeout::Error being ignored. Then in the Timeout::Error => :on_blocking
block, any operation that will block the calling thread is susceptible to a Timeout::Error exception being raised.
Stack control settings
It’s possible to stack multiple levels of ::handle_interrupt blocks in order to control more than one ExceptionClass and TimingSymbol at a time.
Thread.handle_interrupt(FooError => :never) {
Thread.handle_interrupt(BarError => :never) {
# FooError and BarError are prohibited.
}
}
Inheritance with ExceptionClass
All exceptions inherited from the ExceptionClass parameter will be considered.
Thread.handle_interrupt(Exception => :never) {
# all exceptions inherited from Exception are prohibited.
}
2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 |
# File 'thread.c', line 2227
static VALUE
rb_thread_s_handle_interrupt(VALUE self, VALUE mask_arg)
{
VALUE mask;
rb_execution_context_t * volatile ec = GET_EC();
rb_thread_t * volatile th = rb_ec_thread_ptr(ec);
volatile VALUE r = Qnil;
enum ruby_tag_type state;
if (!rb_block_given_p()) {
rb_raise(rb_eArgError, "block is needed.");
}
mask = 0;
mask_arg = rb_to_hash_type(mask_arg);
rb_hash_foreach(mask_arg, handle_interrupt_arg_check_i, (VALUE)&mask);
if (!mask) {
return rb_yield(Qnil);
}
OBJ_FREEZE_RAW(mask);
rb_ary_push(th->pending_interrupt_mask_stack, mask);
if (!rb_threadptr_pending_interrupt_empty_p(th)) {
th->pending_interrupt_queue_checked = 0;
RUBY_VM_SET_INTERRUPT(th->ec);
}
EC_PUSH_TAG(th->ec);
if ((state = EC_EXEC_TAG()) == TAG_NONE) {
r = rb_yield(Qnil);
}
EC_POP_TAG();
rb_ary_pop(th->pending_interrupt_mask_stack);
if (!rb_threadptr_pending_interrupt_empty_p(th)) {
th->pending_interrupt_queue_checked = 0;
RUBY_VM_SET_INTERRUPT(th->ec);
}
RUBY_VM_CHECK_INTS(th->ec);
if (state) {
EC_JUMP_TAG(th->ec, state);
}
return r;
}
|