Project

General

Profile

Feature #6762

Updated by ko1 (Koichi Sasada) over 12 years ago

=begin 
 = Abstract 

 Add asynchronous interrupt timing control feature.    Control with the following three modes: 

 * immediate: process interrupt immediately 
 * never: never process interrupt 
 * on_blocking: delay interrupt until blocking operation 

   # example 
   th = Thread.new do 
     Thread.control_interrupt(RuntimeError => :never) do 
       # in this block, thrown RuntimeError doesn't occur  
     end 
     ... # raise thrown RuntimeError 
   end 
   ... 
   th.raise "foo"  

 = Background 

 == Terminology 

 * Interrupt: asynchronous interrupt and corresponding procedures 
   * Thread#raise and occurring exception 
   * signal and corresponding trap  
   * Thread#kill and thread termination 
   * Main thread termination and thread termination 
     (after main thread termination, all threads exit themselves) 
 * Interrupt checking: check interrupt 
 * Blocking operation: Possible to block the current thread such as IO read/write.    In CRuby implementation, it is nearly equals to tasks without GVL 

 == Current use-cases of Interrupt 

 There are several `Interrupt' in Ruby. 

   # Example 1 
   th = Thread.new{ 
     begin 
       ... 
     rescue FooError 
       ... 
     end 
   } 
   th.raise(FooError) #=> Raise FooError on thread `th' 

   # Example example 2 
   q = Queue.new 
   th1 = Thread.new{ 
     q << calc_in_algorithm1 
   } 
   th2 = Thread.new{ 
     q << calc_in_algorithm2 
   } 
   result = q.pop 
   th1.raise(TerminateCalcError) 
   th2.raise(TerminateCalcError) 
   # Run two algorithms simultaneously. 
   # If we get an answer from one algorithm, 
   # kill them with TerminateCalcError 
   # In this case, it is also okay with Thread#kill 

   # Example example 3 
   trap(SIGINT){ 
     # do something 
     # maybe termination process 
   } 
   trap(SIGHUP){ 
     # do something 
     # maybe reloading configuration process 
   } 
   server_exec # server main process 

 In such interrupts are checked at several points such as: 

 * method invocation timing 
 * method returning timing 
 * move program counter 
 * before and after block operation 

 == Problem 

 Interrupt causes the following problems because we can't control occurring timing.  

 * Un-safe ensure clause: Generally, ensure clause Free resources, such process should not interrupt because it contains important tasks such as freeing resources. interrupt. 
 * Un-safe resource allocation: If interrupt occurs between resource allocation and assign it to the variable, we can't free this object (however, this problem not too big because we have a gc and appropriate finalizer can free it). 
 * (other problems? please complement me) 

 I show an example below. 

   # Example example 4 
   # this method is similar implementation of timeout() 
   def timeout(sec) 
     timer_thread = Thread.new(Thread.current){|parent| 
       sleep(sec) 
       parent.raise(TimeoutError) 
     } 
     begin 
       yield 
     ensure 
       timer_thread.stop # close thread 
     end 
   end 
   
  
   timeout(3){ 
     begin 
       f = # point (a) 
       open(...)    # of course, there are no problem with open(...){|f| ...}  
                  # but it is an example to show the problem 
       ... 
     ensure 
       ... # point (b) 
       f.close 
     end 
   } 


 

 On example 4, there are two problems. 

 Point (b) is easy to understand.    If interrupt was thrown at point (b), then `f.close()' isn't called.    It is problem. 

 On the point (a), it is a position between resource allocation (open()) and assignment `f = '.    It is very rare, but it is possible.    If we get interrupt before assignment, then we can't free resources (can't call f.close()) in ensure clause.    It is also problem. 

 The problem is we can't control interrupt timing. 

 = Proposal 

 Adding interrupt timing control feature to Thread.    Introduce two methods to Thread class. 

 * Thread.control_interrupt 
 * Thread.check_interrupt 

 Rdoc documents are: 

 Thread.control_interrupt(): 

   call-seq: 
     Thread.control_interrupt(hash) { ... } -> result of the block 
 
   Thread.control_interrupt controls 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). 
 
   _hash_ has pairs of ExceptionClass and TimingSymbol.    TimingSymbol 
   is one of them: 
   - :immediate     Invoke interrupt immediately. 
   - :on_blocking Invoke interrupt while _BlockingOperation_. 
   - :never         Never invoke interrupt. 
 
   _BlockingOperation_ means that the operation will block the calling thread, 
   such as read and write.    On CRuby implementation, _BlockingOperation_ is 
   operation executed without GVL. 
 
   Masked interrupts are delayed until they are enabled. 
   This method is similar to sigprocmask(3). 
 
   TODO (DOC): control_interrupt is stacked. 
   TODO (DOC): check ancestors. 
   TODO (DOC): to prevent all interrupt, {Object => :never} works. 
 
   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. 
 
 
     # example: Guard from Thread#raise 
     th = Thread.new do 
       Thead.control_interrupt(RuntimeError => :never) { 
         begin 
           # Thread#raise doesn't interrupt here. 
           # You can write resource allocation code safely. 
           Thread.control_interrupt(RuntimeError => :immediate) { 
             # ... 
             # It is possible to be interrupted by Thread#raise. 
           } 
         ensure 
           # Thread#raise doesn't interrupt here. 
           # You can write resource dealocation code safely. 
         end 
       } 
     end 
     Thread.pass 
     # ... 
     th.raise "stop" 
 
     # example: Guard from TimeoutError 
     require 'timeout' 
     Thread.control_interrupt(TimeoutError => :never) { 
       timeout(10){ 
         # TimeoutError doesn't occur here 
         Thread.control_interrupt(TimeoutError => :on_blocking) { 
           # possible to be killed by TimeoutError 
           # while blocking operation 
         } 
         # TimeoutError doesn't occur here 
       } 
     } 
 
     # example: Stack control settings 
     Thread.control_interrupt(FooError => :never) { 
       Thread.control_interrupt(BarError => :never) { 
          # FooError and BarError are prohibited. 
       } 
     } 
 
     # example: check ancestors 
     Thread.control_interrupt(Exception => :never) { 
       # all exceptions inherited from Exception are prohibited. 
     } 

 Thread.check_interrupt(): 

   call-seq: 
     Thread.check_interrupt() -> nil 
 
   Check queued interrupts. 
 
   If there are queued interrupts, process respective procedures. 
 
   This method can be defined as the following Ruby code: 
 
     def Thread.check_interrupt 
       Thread.control_interrupt(Object => :immediate) { 
         Thread.pass 
       } 
     end 
 
   Examples: 
 
     th = Thread.new{ 
       Thread.control_interrupt(RuntimeError => :on_blocking){ 
         while true 
           ... 
           # reach safe point to invoke interrupt 
           Thread.check_interrupt 
           ... 
         end 
       } 
     } 
     ... 
     th.raise # stop thread 
 
   NOTE: This example can be described by the another code. 
         You need to keep to avoid asynchronous interrupts. 
 
     flag = true 
     th = Thread.new{ 
       Thread.control_interrupt(RuntimeError => :on_blocking){ 
         while true 
           ... 
           # reach safe point to invoke interrupt 
           break if flag == false 
           ... 
         end 
       } 
     } 
     ... 
     flag = false # stop thread 

 I have already commit-ed these methods into trunk. 
 Please try it and discuss. 

 This commit is easy to revert :) 

 Naming is also problem as usual.    Good naming is also welcome. 


 = Acknowledgment 

 The base of this proposal is a discussion[1]. 

 [1] Akira Tanaka "Re: Thread#raise, Thread#kill, and timeout.rb are 
 unsafe" ruty-talk (2008.3) <http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/294917> 

 Many dev-people help me to make up this proposal. 

 =end 

Back