🔍

No recent searches

# module Timeout

`Timeout` long-running blocks

## Synopsis

    require 'timeout'
    status = Timeout.timeout(5) {
      # Something that should be interrupted if it takes more than 5 seconds...
    }

## Description

`Timeout` provides a way to auto-terminate a potentially long-running operation if it hasn’t finished in a fixed amount of time.

## Copyright

Copyright

  © 2000 Network Applied Communication Laboratory, Inc.

Copyright

  © 2000 Information-technology Promotion Agency, Japan

### Constants

VERSION

  The version

### Public Class Methods

timeout (sec, klass = nil, message = nil) { |sec| ... }
Source

    # File lib/timeout.rb, line 278
    def self.timeout(sec, klass = nil, message = nil, &block)   #:yield: +sec+
      return yield(sec) if sec == nil or sec.zero?
      raise ArgumentError, "Timeout sec must be a non-negative number" if 0 > sec

      message ||= "execution expired"

      if Fiber.respond_to?(:current_scheduler) && (scheduler = Fiber.current_scheduler)&.respond_to?(:timeout_after)
        perform = Proc.new do |exc|
          scheduler.timeout_after(sec, exc, message, &block)
        end
      else
        state = State.instance
        state.ensure_timeout_thread_created

        perform = Proc.new do |exc|
          request = Request.new(Thread.current, sec, exc, message)
          state.add_request(request)
          begin
            return yield(sec)
          ensure
            request.finished
          end
        end
      end

      if klass
        perform.call(klass)
      else
        Error.handle_timeout(message, &perform)
      end
    end

Perform an operation in a block, raising an exception if it takes longer than `sec` seconds to complete.

`sec`

  Number of seconds to wait for the block to terminate. Any non-negative number or nil may be used, including Floats to specify fractional seconds. A value of 0 or `nil` will execute the block without any timeout. Any negative number will raise an `ArgumentError`.

`klass`

  `Exception` Class to raise if the block fails to terminate in `sec` seconds. Omitting will use the default, `Timeout::Error`

`message`

  `Error` message to raise with `Exception` Class. Omitting will use the default, “execution expired”

Returns the result of the block if the block completed before `sec` seconds, otherwise raises an exception, based on the value of `klass`.

The exception raised to terminate the given block is the given `klass`, or `Timeout::ExitException` if `klass` is not given. The reason for that behavior is that `Timeout::Error` inherits from `RuntimeError` and might be caught unexpectedly by ‘rescue`. `Timeout::ExitException` inherits from `Exception` so it will only be rescued by `rescue Exception`. Note that the `Timeout::ExitException` is translated to a `Timeout::Error` once it reaches the `Timeout.timeout` call, so outside that call it will be a `Timeout::Error`.

In general, be aware that the code block may rescue the exception, and in such a case not respect the timeout. Also, the block can use `ensure` to prevent the handling of the exception. For those reasons, this method cannot be relied on to enforce timeouts for untrusted blocks.

If a scheduler is defined, it will be used to handle the timeout by invoking Scheduler#timeout_after.

Note that this is both a method of module `Timeout`, so you can `include Timeout` into your classes so they have a `timeout` method, as well as a module method, so you can call it directly as `Timeout.timeout()`.

#### Ensuring the exception does not fire inside ensure blocks

When using `Timeout.timeout` it can be desirable to ensure the timeout exception does not fire inside an `ensure` block. The simplest and best way to do so it to put the `Timeout.timeout` call inside the body of the begin/ensure/end:

    begin
      Timeout.timeout(sec) { some_long_operation }
    ensure
      cleanup # safe, cannot be interrupt by timeout
    end

If that is not feasible, e.g. if there are `ensure` blocks inside `some_long_operation`, they need to not be interrupted by timeout, and it’s not possible to move these ensure blocks outside, one can use `Thread.handle_interrupt` to delay the timeout exception like so:

    Thread.handle_interrupt(Timeout::Error => :never) {
      Timeout.timeout(sec, Timeout::Error) do
        setup # timeout cannot happen here, no matter how long it takes
        Thread.handle_interrupt(Timeout::Error => :immediate) {
          some_long_operation # timeout can happen here
        }
      ensure
        cleanup # timeout cannot happen here, no matter how long it takes
      end
    }

An important thing to note is the need to pass an exception klass to `Timeout.timeout`, otherwise it does not work. Specifically, using +Thread.handle_interrupt(`Timeout::ExitException` => …)+ is unsupported and causes subtle errors like raising the wrong exception outside the block, do not use that.

Note that `Thread.handle_interrupt` is somewhat dangerous because if setup or cleanup hangs then the current thread will hang too and the timeout will never fire. Also note the block might run for longer than `sec` seconds: e.g. some_long_operation executes for `sec` seconds + whatever time cleanup takes.

If you want the timeout to only happen on blocking operations one can use :on_blocking instead of :immediate. However, that means if the block uses no blocking operations after `sec` seconds, the block will not be interrupted.

### Private Instance Methods

timeout (*args, &block)
Source

    # File lib/timeout.rb, line 310
            def timeout(*args, &block)
      Timeout.timeout(*args, &block)
    end