class WEBrick::Utils::TimeoutHandler


will print ‘foo’
end
TimeoutHandler.cancel(id)
ensure
puts ‘foo’
sleep 5
begin
id = TimeoutHandler.register(10, Timeout::Error)
will raise Timeout::Error
end
TimeoutHandler.cancel(id)
ensure
puts ‘foo’
sleep 20
begin
id = TimeoutHandler.register(10, Timeout::Error)
synchronized.
Timeout handlers should be managed by using the class methods which are
Class used to manage timeout handlers across multiple threads.
#

def self.terminate

def self.terminate
  instance.terminate
end

def cancel(thread, id)

Cancels the timeout handler +id+
#
def cancel(thread, id)
  TimeoutMutex.synchronize{
    if ary = @timeout_info[thread]
      ary.delete_if{|info| info.object_id == id }
      if ary.empty?
        @timeout_info.delete(thread)
      end
      return true
    end
    return false
  }
end

def initialize

instead of creating the timeout handler directly.
Creates a new TimeoutHandler. You should use ::register and ::cancel
#
def initialize
  TimeoutMutex.synchronize{
    @timeout_info = Hash.new
  }
  @queue = Thread::Queue.new
  @watcher = nil
end

def interrupt(thread, id, exception)

Interrupts the timeout handler +id+ and raises +exception+
#
def interrupt(thread, id, exception)
  if cancel(thread, id) && thread.alive?
    thread.raise(exception, "execution timeout")
  end
end

def register(thread, time, exception)

+exception+:: Exception to raise when timeout elapsed
+time+:: Timeout in seconds

Registers a new timeout handler
#
def register(thread, time, exception)
  info = nil
  TimeoutMutex.synchronize{
    (@timeout_info[thread] ||= []) << (info = [time, exception])
  }
  @queue.push nil
  watcher
  return info.object_id
end

def terminate

#
def terminate
  TimeoutMutex.synchronize{
    @timeout_info.clear
    @watcher&.kill&.join
  }
end

def watch

def watch
  to_interrupt = []
  while true
    now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
    wakeup = nil
    to_interrupt.clear
    TimeoutMutex.synchronize{
      @timeout_info.each {|thread, ary|
        next unless ary
        ary.each{|info|
          time, exception = *info
          if time < now
            to_interrupt.push [thread, info.object_id, exception]
          elsif !wakeup || time < wakeup
            wakeup = time
          end
        }
      }
    }
    to_interrupt.each {|arg| interrupt(*arg)}
    if !wakeup
      @queue.pop
    elsif (wakeup -= now) > 0
      begin
        (th = Thread.start {@queue.pop}).join(wakeup)
      ensure
        th&.kill&.join
      end
    end
    @queue.clear
  end
end

def watcher

def watcher
  (w = @watcher)&.alive? and return w # usual case
  TimeoutMutex.synchronize{
    (w = @watcher)&.alive? and next w # pathological check
    @watcher = Thread.start(&method(:watch))
  }
end