модуль 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
-
Версия
Открытые методы класса
# 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 секунд блок не выполняет блокирующих операций, его выполнение не будет прервано.
Закрытые методы экземпляра
# 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.