Spec-Zone.ru › Ruby 4.0

модуль Timeout

Timeout длительно выполняющихся блоков

Краткое описание

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

Описание

Timeout позволяет автоматически прервать потенциально длительную операцию, если она не завершилась за заданное время.

Авторские права

Авторские права

© 2000 Network Applied Communication Laboratory, Inc.

Авторские права

© 2000 Information-technology Promotion Agency, Japan

Константы

VERSION

Версия

Открытые методы класса

timeout (sec, klass = nil, message = nil) { |sec| ... } Показать исходный код
# 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)
    return scheduler.timeout_after(sec, klass || Error, message, &block)
  end

  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

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

Выполняет операцию в блоке, вызывая исключение, если для её выполнения требуется более sec секунд.

sec

Количество секунд ожидания завершения блока. Можно указать любое неотрицательное число или nil, в том числе дробное число типа Float. Значение 0 или nil выполнит блок без ограничения по времени. Отрицательное число вызовет ArgumentError.

klass

Класс Exception, который нужно вызвать, если блок не завершится за sec секунд. Если не указан, используется значение по умолчанию — Timeout::Error

message

Сообщение Error, передаваемое классу Exception. Если не указано, используется значение по умолчанию: «execution expired»

Возвращает результат блока, если блок завершился раньше чем через sec секунд; в противном случае вызывает исключение в зависимости от значения klass.

Для прерывания заданного блока вызывается указанное исключение klass или Timeout::ExitException, если klass не задан. Это сделано потому, что Timeout::Error наследуется от RuntimeError и может быть неожиданно перехвачено с помощью ‘rescue`. Timeout::ExitException наследуется от Exception, поэтому будет перехвачено только с помощью `rescue Exception`. Обратите внимание: Timeout::ExitException преобразуется в Timeout::Error при возврате в вызов Timeout.timeout, поэтому за пределами этого вызова это будет Timeout::Error.

В целом учитывайте, что блок кода может перехватить исключение, и в таком случае ограничение по времени не будет соблюдено. Кроме того, блок может использовать ensure, чтобы предотвратить обработку исключения. Поэтому этот метод нельзя использовать для гарантированного ограничения времени выполнения ненадёжных блоков.

Если определён планировщик, для обработки тайм-аута он будет использовать Scheduler#timeout_after.

Обратите внимание: это метод модуля Timeout, поэтому его можно include Timeout в свои классы, чтобы они получили метод timeout. Кроме того, это метод модуля, поэтому его можно вызывать напрямую как Timeout.timeout().

Гарантия того, что исключение не будет вызвано внутри блоков ensure

При использовании Timeout.timeout может быть важно гарантировать, что исключение тайм-аута не будет вызвано внутри блока ensure. Самый простой и надёжный способ — поместить вызов Timeout.timeout в тело конструкции begin/ensure/end:

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

Если это невозможно — например, если внутри some_long_operation есть блоки ensure, которые не должны прерываться тайм-аутом, а вынести эти блоки ensure наружу нельзя, — можно использовать Thread.handle_interrupt, чтобы отложить исключение тайм-аута, например:

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
}

Важно передать класс исключения в Timeout.timeout, иначе это не сработает. В частности, использовать +Thread.handle_interrupt(Timeout::ExitException => …)+ нельзя: это может привести к трудноуловимым ошибкам, например к вызову неправильного исключения за пределами блока. Не используйте этот вариант.

Обратите внимание, что Thread.handle_interrupt в некоторой степени опасен: если настройка или очистка зависнет, зависнет и текущий поток, и тайм-аут никогда не сработает. Кроме того, блок может выполняться дольше sec секунд: например, some_long_operation выполняется sec секунд плюс время, необходимое для очистки.

Если нужно, чтобы тайм-аут срабатывал только при блокирующих операциях, можно использовать :on_blocking вместо :immediate. Однако в этом случае, если после sec секунд блок не выполняет блокирующих операций, его выполнение не будет прервано.

Закрытые методы экземпляра

timeout (*args, &block) Показать исходный код
# File lib/timeout.rb, line 308
        def timeout(*args, &block)
  Timeout.timeout(*args, &block)
end

Ruby Core © 1993–2025 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API