Module: SafeRequestTimeout
- Defined in:
- lib/safe_request_timeout.rb,
lib/safe_request_timeout/hooks.rb,
lib/safe_request_timeout/railtie.rb,
lib/safe_request_timeout/version.rb,
lib/safe_request_timeout/rack_middleware.rb,
lib/safe_request_timeout/active_record_hook.rb,
lib/safe_request_timeout/sidekiq_middleware.rb
Overview
This module adds the capability to add a general timeout to any block of code.
Unlike the Timeout module, an error is not raised to indicate a timeout.
Instead, the timed_out? method can be used to check if the block of code
has taken longer than the specified duration so the application can take
the appropriate action.
This is a safer alternative to the Timeout module because it does not fork new threads or risk raising errors from unexpected places.
Defined Under Namespace
Modules: Hooks Classes: ActiveRecordHook, RackMiddleware, Railtie, SidekiqMiddleware, TimeoutError
Constant Summary collapse
- VERSION =
File.read(File.("../../VERSION", __dir__)).chomp.freeze
Class Method Summary collapse
-
.check_timeout! ⇒ void
Raise an error if the current timeout block has timed out.
-
.clear_timeout { ... } ⇒ Object
Clear the current timeout.
-
.set_timeout(duration) ⇒ void
Set the duration for the current timeout block.
-
.time_elapsed ⇒ Float?
Get the number of seconds elapsed in the current timeout block or nil if there is no timeout block.
-
.time_remaining ⇒ Float?
Get the number of seconds remaining in the current timeout block or nil if there is no timeout block.
-
.timed_out? ⇒ Boolean
Check if the current timeout block has timed out.
-
.timeout(duration) { ... } ⇒ Object
Execute the given block with a timeout.
Class Method Details
.check_timeout! ⇒ void
This method returns an undefined value.
Raise an error if the current timeout block has timed out. If there is no timeout block, then this method does nothing. If an error is raised, then the current timeout is cleared to prevent the error from being raised multiple times.
74 75 76 77 78 79 80 81 |
# File 'lib/safe_request_timeout.rb', line 74 def check_timeout! if timed_out? state = current_state state[:safe_request_timeout_fired_at] = state[:safe_request_timeout_timeout_at] state[:safe_request_timeout_timeout_at] = nil raise TimeoutError.new("after #{time_elapsed.round(6)} seconds") end end |
.clear_timeout { ... } ⇒ Object
Clear the current timeout. If a block is passed, then the timeout will be cleared only for the duration of the block.
126 127 128 129 130 131 132 |
# File 'lib/safe_request_timeout.rb', line 126 def clear_timeout(&block) if block timeout(nil, &block) else set_timeout(nil) end end |
.set_timeout(duration) ⇒ void
This method returns an undefined value.
Set the duration for the current timeout block. This is useful if you want to set the duration after the timeout block has started. The timer for the timeout block will restart whenever a new duration is set.
106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/safe_request_timeout.rb', line 106 def set_timeout(duration) state = current_state if state[:safe_request_timeout_started_at] start_at = Process.clock_gettime(Process::CLOCK_MONOTONIC) duration = duration.call if duration.respond_to?(:call) timeout_at = start_at + duration if duration state[:safe_request_timeout_started_at] = start_at state[:safe_request_timeout_timeout_at] = timeout_at # Arming a new deadline invalidates any record of a previously fired deadline so # that the ensure block in `timeout` cannot mistake the new deadline for one that # has already raised. state[:safe_request_timeout_fired_at] = nil if timeout_at end end |
.time_elapsed ⇒ Float?
Get the number of seconds elapsed in the current timeout block or nil if there is no timeout block.
96 97 98 99 |
# File 'lib/safe_request_timeout.rb', line 96 def time_elapsed start_at = current_state[:safe_request_timeout_started_at] Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_at if start_at end |
.time_remaining ⇒ Float?
Get the number of seconds remaining in the current timeout block or nil if there is no timeout block.
87 88 89 90 |
# File 'lib/safe_request_timeout.rb', line 87 def time_remaining timeout_at = current_state[:safe_request_timeout_timeout_at] [timeout_at - Process.clock_gettime(Process::CLOCK_MONOTONIC), 0.0].max if timeout_at end |
.timed_out? ⇒ Boolean
Check if the current timeout block has timed out.
63 64 65 66 |
# File 'lib/safe_request_timeout.rb', line 63 def timed_out? timeout_at = current_state[:safe_request_timeout_timeout_at] !!timeout_at && Process.clock_gettime(Process::CLOCK_MONOTONIC) > timeout_at end |
.timeout(duration) { ... } ⇒ Object
Execute the given block with a timeout. If the block takes longer than the specified duration to execute, then the `timed_out?`` method will return true within the block.
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/safe_request_timeout.rb', line 31 def timeout(duration, &block) duration = duration.call if duration.respond_to?(:call) state = current_state previous_start_at = state[:safe_request_timeout_started_at] previous_timeout_at = state[:safe_request_timeout_timeout_at] start_at = Process.clock_gettime(Process::CLOCK_MONOTONIC) timeout_at = start_at + duration if duration if timeout_at && previous_timeout_at && previous_timeout_at < timeout_at timeout_at = previous_timeout_at end begin state[:safe_request_timeout_started_at] = start_at state[:safe_request_timeout_timeout_at] = timeout_at yield ensure # If the deadline shared with the parent block was already raised by check_timeout!, # don't re-arm it when restoring the parent block's state. if previous_timeout_at && previous_timeout_at == state[:safe_request_timeout_fired_at] previous_timeout_at = nil end state[:safe_request_timeout_started_at] = previous_start_at state[:safe_request_timeout_timeout_at] = previous_timeout_at state[:safe_request_timeout_fired_at] = nil if previous_start_at.nil? end end |