module Timeout

Public Class Methods

alarm(sec, exception=Timeout::Error, &block) click to toggle source

A forking timeout implementation that relies on signals to interrupt blocking I/O instead of passing that code to run in a separate process.

A process is fork()ed, sleeps for sec, then sends a ALRM signal to the Process.ppid process. ALRM is used to avoid conflicts with sleep()

This overwrites any signal

# File lib/active_ldap/timeout.rb, line 14
def Timeout.alarm(sec, exception=Timeout::Error, &block)
  return block.call if sec == nil or sec.zero?
 

  # Trap an alarm in case it comes before we're ready
  orig_alrm = trap(:ALRM, 'IGNORE')

  # Setup a fallback in case of a race condition of an
  # alarm before we set the other trap
  trap(:ALRM) do 
    # Don't leave zombies
    Process.wait2()
    # Restore the original handler
    trap('ALRM', orig_alrm)
    # Now raise an exception!
    raise exception, 'execution expired'
  end

  # Spawn the sleeper
  pid = Process.fork {
    begin
      # Sleep x seconds then send SIGALRM 
      sleep(sec)
      # Send alarm!
      Process.kill(:ALRM, Process.ppid)
    end
    exit! 0
  }

  # Setup the real handler
  trap(:ALRM) do
    # Make sure we clean up any zombies
    Process.waitpid(pid)
    # Restore the original handler
    trap(:ALRM, orig_alrm)
    # Now raise an exception!
    raise exception, 'execution expired'
  end

  begin
    # Run the code!
    return block.call
  ensure
    # Restore old alarm handler since we're done
    trap(:ALRM, orig_alrm)
    # Make sure the process is dead
    # This may be run twice (trap occurs during execution) so ignore ESRCH
    Process.kill(:TERM, pid) rescue Errno::ESRCH
    # Don't leave zombies
    Process.waitpid(pid) rescue Errno::ECHILD
  end
end