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.

Examples:

SafeRequestTimeout.timeout(5) do
  # calling check_timeout! will raise an error if the block has taken
  # longer than 5 seconds to execute.
  SafeRequestTimeout.check_timeout!
end

Defined Under Namespace

Modules: Hooks Classes: ActiveRecordHook, RackMiddleware, Railtie, SidekiqMiddleware, TimeoutError

Constant Summary collapse

VERSION =
File.read(File.expand_path("../../VERSION", __dir__)).chomp.freeze

Class Method Summary collapse

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.

Raises:



67
68
69
70
71
72
# File 'lib/safe_request_timeout.rb', line 67

def check_timeout!
  if timed_out?
    Thread.current[: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.

Yields:

  • the block to execute if one is given

Yield Returns:

  • (Object)

    the result of the block



112
113
114
115
116
117
118
# File 'lib/safe_request_timeout.rb', line 112

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.



97
98
99
100
101
102
103
104
105
# File 'lib/safe_request_timeout.rb', line 97

def set_timeout(duration)
  if Thread.current[: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
    Thread.current[:safe_request_timeout_started_at] = start_at
    Thread.current[:safe_request_timeout_timeout_at] = timeout_at
  end
end

.time_elapsedFloat?

Get the number of seconds elapsed in the current timeout block or nil if there is no timeout block.

Returns:

  • (Float, nil)

    the number of seconds elapsed in the current timeout block began



87
88
89
90
# File 'lib/safe_request_timeout.rb', line 87

def time_elapsed
  start_at = Thread.current[:safe_request_timeout_started_at]
  Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_at if start_at
end

.time_remainingFloat?

Get the number of seconds remaining in the current timeout block or nil if there is no timeout block.

Returns:

  • (Float, nil)

    the number of seconds remaining in the current timeout block



78
79
80
81
# File 'lib/safe_request_timeout.rb', line 78

def time_remaining
  timeout_at = Thread.current[: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.

Returns:

  • (Boolean)

    true if the current timeout block has timed out



56
57
58
59
# File 'lib/safe_request_timeout.rb', line 56

def timed_out?
  timeout_at = Thread.current[: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.

Parameters:

  • duration (Integer)

    the number of seconds to wait before timing out

Yields:

  • the block to execute

Yield Returns:

  • (Object)

    the result of the block



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/safe_request_timeout.rb', line 31

def timeout(duration, &block)
  duration = duration.call if duration.respond_to?(:call)

  previous_start_at = Thread.current[:safe_request_timeout_started_at]
  previous_timeout_at = Thread.current[: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
    Thread.current[:safe_request_timeout_started_at] = start_at
    Thread.current[:safe_request_timeout_timeout_at] = timeout_at
    yield
  ensure
    Thread.current[:safe_request_timeout_started_at] = previous_start_at
    Thread.current[:safe_request_timeout_timeout_at] = previous_timeout_at
  end
end