safe_request_timeout 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/README.md +17 -9
- data/VERSION +1 -1
- data/lib/safe_request_timeout/active_record_hook.rb +46 -5
- data/lib/safe_request_timeout/hooks.rb +18 -7
- data/lib/safe_request_timeout/railtie.rb +15 -8
- data/lib/safe_request_timeout.rb +40 -13
- data/safe_request_timeout.gemspec +7 -5
- metadata +9 -39
- data/dependabot.yml +0 -12
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c87a2c51edeb0644cea9c06b230dcc657b38fa61cf294dc395cd93396dc8e13a
|
|
4
|
+
data.tar.gz: 2b025ed3a1e7e4d1f40b29e4699ab24184de1924eaa417042c9a6cc411a03c2e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a7826eb9467f5b842be5d54c5044d693288dfcf7439075e3e36b8adc7197e3100929e3505b7c2421f9e3c1b00a68388b21065f08503e64b5da20a40a69d50812
|
|
7
|
+
data.tar.gz: 6f126f39f97f17fbe778ebf3db11bf2b1c5cb5cab300bd5a98489b80f60acc9c5c27ad494035a34dc1bf9289550c77a55ba5b92709ea878e621951ecbd11f625
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,20 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## 1.0.3
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- Removed unused runtime dependency on the redis gem.
|
|
11
|
+
- The ActiveJob timeout block no longer clears a timeout already established for the request or worker running the job (e.g. jobs run with `perform_now` inside a request, or ActiveJob jobs running with the Sidekiq middleware).
|
|
12
|
+
- `SafeRequestTimeout.check_timeout!` no longer raises a second error from an enclosing timeout block when a deadline shared by nested timeout blocks has already raised.
|
|
13
|
+
- ActiveRecord hooks no longer require a live database connection when they are installed. They are now added to each adapter class the first time a connection is instantiated, so applications that boot while the database is unavailable and applications with multiple databases are fully covered.
|
|
14
|
+
- Registering the same hooks more than once is now a no-op instead of raising an error.
|
|
15
|
+
- The transaction commit hook now clears the timeout after the commit succeeds instead of before it runs.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- Timeout state is stored in `ActiveSupport::IsolatedExecutionState` when available (ActiveSupport 7+) so that it follows the application's configured isolation level. Otherwise it is stored in fiber-local variables as before.
|
|
19
|
+
- ActiveJob and ActiveRecord integrations in the Railtie are now set up with lazy load hooks instead of loading the frameworks during initialization.
|
|
20
|
+
|
|
7
21
|
## 1.0.2
|
|
8
22
|
|
|
9
23
|
### Added
|
data/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Safe Request Timeout
|
|
2
2
|
|
|
3
3
|
[](https://github.com/bdurand/safe_request_timeout/actions/workflows/continuous_integration.yml)
|
|
4
|
-
[](https://github.com/bdurand/safe_request_timeout/actions/workflows/regression_test.yml)
|
|
5
4
|
[](https://github.com/testdouble/standard)
|
|
5
|
+
[](https://badge.fury.io/rb/safe_request_timeout)
|
|
6
6
|
|
|
7
7
|
This gem provides a safe and convenient mechanism for adding a timeout mechanism to a block of code. The gem ensures that the timeout is safe to call and will not raise timeout errors from random places in your code which can leave your application in an indeterminate state.
|
|
8
8
|
|
|
@@ -16,7 +16,7 @@ There is built in support for Rails applications. For other frameworks you will
|
|
|
16
16
|
|
|
17
17
|
## Usage
|
|
18
18
|
|
|
19
|
-
You can wrap code in a
|
|
19
|
+
You can wrap code in a timeout block.
|
|
20
20
|
|
|
21
21
|
```ruby
|
|
22
22
|
SafeRequestTimeout.timeout(15) do
|
|
@@ -33,7 +33,7 @@ SafeRequestTimeout.timeout(5) do
|
|
|
33
33
|
1000.times do
|
|
34
34
|
# This will raise an error if the loop takes longer than 5 seconds.
|
|
35
35
|
SafeRequestTimeout.check_timeout!
|
|
36
|
-
|
|
36
|
+
do_something
|
|
37
37
|
end
|
|
38
38
|
end
|
|
39
39
|
```
|
|
@@ -41,7 +41,7 @@ end
|
|
|
41
41
|
You can also set a timeout value retroactively from within a `timeout` block. You can use this feature to change the timeout based on application state.
|
|
42
42
|
|
|
43
43
|
```ruby
|
|
44
|
-
# Setting a timeout of nil will set up a block that will never
|
|
44
|
+
# Setting a timeout of nil will set up a block that will never time out.
|
|
45
45
|
SafeRequestTimeout.timeout(nil) do
|
|
46
46
|
# Set the timeout duration to 5 seconds for non-admin users
|
|
47
47
|
SafeRequestTimeout.set_timeout(5) unless current_user.admin?
|
|
@@ -58,7 +58,7 @@ SafeRequestTimeout.timeout(lambda { CurrentUser.new.admin? ? nil : 5 })
|
|
|
58
58
|
end
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
You can clear the timeout if you want to ensure a block of code can run without
|
|
61
|
+
You can clear the timeout if you want to ensure a block of code can run without being timed out (i.e. if you need to run cleanup code).
|
|
62
62
|
|
|
63
63
|
```ruby
|
|
64
64
|
SafeRequestTimeout.timeout(5) do
|
|
@@ -71,9 +71,17 @@ SafeRequestTimeout.timeout(5) do
|
|
|
71
71
|
end
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
+
### Behavior notes
|
|
75
|
+
|
|
76
|
+
- Timeout blocks can be nested. A nested block will use the soonest deadline of the nested blocks, so a nested block cannot extend a timeout set by an enclosing block. Calling `set_timeout` inside a nested block, however, replaces the deadline for that block even if it extends past the enclosing block's deadline; the enclosing deadline is restored when the nested block exits.
|
|
77
|
+
|
|
78
|
+
- `SafeRequestTimeout::TimeoutError` is a subclass of `Timeout::Error` from the Ruby standard library. Generic error handling that rescues or retries on `Timeout::Error` will catch it as well.
|
|
79
|
+
|
|
80
|
+
- The timeout state is stored per thread or fiber. When ActiveSupport 7+ is available, the state is stored in `ActiveSupport::IsolatedExecutionState` and follows the application's configured `active_support.isolation_level`. Otherwise the state is stored in fiber-local variables. In either case, the timeout will not be visible in new threads spawned within a timeout block (including ActiveRecord `load_async` queries), so `check_timeout!` calls in those threads will do nothing.
|
|
81
|
+
|
|
74
82
|
### Hooks
|
|
75
83
|
|
|
76
|
-
You can add hooks into other classes to check the current timeout if you don't want to have to sprinkle `SafeRequestTimeout.check_timeout!` throughout your code. To do this, use the `SafeRequestTimeout.add_timeout!` method. You need to specify the class and methods where you want to add the timeout hooks:
|
|
84
|
+
You can add hooks into other classes to check the current timeout if you don't want to have to sprinkle `SafeRequestTimeout.check_timeout!` throughout your code. To do this, use the `SafeRequestTimeout::Hooks.add_timeout!` method. You need to specify the class and methods where you want to add the timeout hooks:
|
|
77
85
|
|
|
78
86
|
```ruby
|
|
79
87
|
# Add a timeout check to the MyDriver#make_request method.
|
|
@@ -129,13 +137,13 @@ end
|
|
|
129
137
|
|
|
130
138
|
This gem comes with built in support for Rails applications.
|
|
131
139
|
|
|
132
|
-
- The Rack middleware is added to the middleware chain. There is no timeout value set by default. You can specify a global one by setting `
|
|
140
|
+
- The Rack middleware is added to the middleware chain. There is no timeout value set by default. You can specify a global one by setting `safe_request_timeout.rack_timeout` in your Rails configuration.
|
|
133
141
|
|
|
134
142
|
- If Sidekiq is being used, then the Sidekiq middleware is added. Sidekiq workers can specify a timeout with the `safe_request_timeout` option.
|
|
135
143
|
|
|
136
|
-
- A timeout block is added around ActiveJob execution. Jobs can specify a timeout by calling `SafeRequestTimeout.set_timeout` in the `perform` method or in a `before_perform` callback.
|
|
144
|
+
- A timeout block is added around ActiveJob execution. Jobs can specify a timeout by calling `SafeRequestTimeout.set_timeout` in the `perform` method or in a `before_perform` callback. If a timeout has already been set up for the request or worker running the job (i.e. a job run with `perform_now` inside a request, or an ActiveJob running on Sidekiq with the Sidekiq middleware), then that timeout is kept rather than replaced.
|
|
137
145
|
|
|
138
|
-
- A timeout check is added on all ActiveRecord queries. The timeout is cleared when a database transaction is committed so that you won't unexpectedly timeout a request after making persistent changes. You can disable these hooks by setting `safe_request_timeout.active_record_hook` to false in your Rails configuration.
|
|
146
|
+
- A timeout check is added on all ActiveRecord queries. The timeout is cleared when a database transaction is committed so that you won't unexpectedly timeout a request after making persistent changes. You can disable these hooks by setting `safe_request_timeout.active_record_hook` to false in your Rails configuration. The hooks are installed on each database adapter class the first time a connection is instantiated, so no database connection is required at boot time and all databases in a multiple database configuration are covered.
|
|
139
147
|
|
|
140
148
|
## Installation
|
|
141
149
|
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.0.
|
|
1
|
+
1.0.3
|
|
@@ -2,19 +2,60 @@
|
|
|
2
2
|
|
|
3
3
|
module SafeRequestTimeout
|
|
4
4
|
class ActiveRecordHook
|
|
5
|
+
MUTEX = Mutex.new
|
|
6
|
+
private_constant :MUTEX
|
|
7
|
+
|
|
8
|
+
@hooked_classes = []
|
|
9
|
+
|
|
5
10
|
class << self
|
|
6
|
-
# Add the timeout hook to
|
|
11
|
+
# Add the timeout hook to a connection adapter class. If no class is given, then the
|
|
12
|
+
# hooks will be added to each connection adapter class the first time it is instantiated.
|
|
13
|
+
# This does not require a database connection to be established and covers every adapter
|
|
14
|
+
# in use, including applications with multiple databases.
|
|
7
15
|
#
|
|
8
16
|
# @param connection_class [Class] The class to add the timeout hook to.
|
|
9
17
|
# @return [void]
|
|
10
18
|
def add_timeout!(connection_class = nil)
|
|
11
|
-
connection_class
|
|
12
|
-
|
|
19
|
+
if connection_class
|
|
20
|
+
add_hooks(connection_class)
|
|
21
|
+
else
|
|
22
|
+
::ActiveRecord::ConnectionAdapters::AbstractAdapter.prepend(AdapterInitializer)
|
|
23
|
+
# Add the hooks to any adapter that already has a live connection.
|
|
24
|
+
if ::ActiveRecord::Base.connected?
|
|
25
|
+
::ActiveRecord::Base.connection_pool.with_connection do |connection|
|
|
26
|
+
add_hooks(connection.class)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
13
33
|
|
|
14
|
-
|
|
34
|
+
def add_hooks(connection_class)
|
|
35
|
+
MUTEX.synchronize do
|
|
36
|
+
return if @hooked_classes.include?(connection_class)
|
|
15
37
|
|
|
16
|
-
|
|
38
|
+
exec_method = (connection_class.method_defined?(:internal_exec_query) ? :internal_exec_query : :exec_query)
|
|
39
|
+
SafeRequestTimeout::Hooks.add_timeout!(connection_class, [exec_method])
|
|
40
|
+
SafeRequestTimeout::Hooks.clear_timeout!(connection_class, [:commit_db_transaction])
|
|
41
|
+
|
|
42
|
+
@hooked_classes << connection_class
|
|
43
|
+
end
|
|
17
44
|
end
|
|
18
45
|
end
|
|
46
|
+
|
|
47
|
+
# Prepended to AbstractAdapter so the timeout hooks are added to each concrete adapter
|
|
48
|
+
# class the first time it is instantiated. The hooks must go on the concrete class since
|
|
49
|
+
# adapters can override the hooked methods.
|
|
50
|
+
module AdapterInitializer
|
|
51
|
+
ruby_major, ruby_minor, _ = RUBY_VERSION.split(".").collect(&:to_i)
|
|
52
|
+
splat_args = ((ruby_major >= 3 || (ruby_major == 2 && ruby_minor >= 7)) ? "..." : "*args, &block")
|
|
53
|
+
class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
|
54
|
+
def initialize(#{splat_args})
|
|
55
|
+
super(#{splat_args})
|
|
56
|
+
SafeRequestTimeout::ActiveRecordHook.add_timeout!(self.class)
|
|
57
|
+
end
|
|
58
|
+
RUBY
|
|
59
|
+
end
|
|
19
60
|
end
|
|
20
61
|
end
|
|
@@ -4,12 +4,17 @@ module SafeRequestTimeout
|
|
|
4
4
|
# Hooks into other classes from other libraries with timeout blocks. This allows
|
|
5
5
|
# timeouts to be automatically checked before making requests to external services.
|
|
6
6
|
module Hooks
|
|
7
|
+
MUTEX = Mutex.new
|
|
8
|
+
private_constant :MUTEX
|
|
9
|
+
|
|
7
10
|
class << self
|
|
8
11
|
# Hooks into a class by surrounding specified instance methods with timeout checks.
|
|
9
12
|
def add_timeout!(klass, methods, module_name = nil)
|
|
10
13
|
hooks_module = create_module(klass, module_name, "AddTimeout")
|
|
11
14
|
|
|
12
15
|
Array(methods).each do |method_name|
|
|
16
|
+
next if hooks_module.method_defined?(method_name)
|
|
17
|
+
|
|
13
18
|
hooks_module.class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
|
14
19
|
def #{method_name}(#{splat_args})
|
|
15
20
|
SafeRequestTimeout.check_timeout!
|
|
@@ -25,10 +30,13 @@ module SafeRequestTimeout
|
|
|
25
30
|
hooks_module = create_module(klass, module_name, "ClearTimeout")
|
|
26
31
|
|
|
27
32
|
Array(methods).each do |method_name|
|
|
33
|
+
next if hooks_module.method_defined?(method_name)
|
|
34
|
+
|
|
28
35
|
hooks_module.class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
|
29
36
|
def #{method_name}(#{splat_args})
|
|
37
|
+
result = super(#{splat_args})
|
|
30
38
|
SafeRequestTimeout.clear_timeout
|
|
31
|
-
|
|
39
|
+
result
|
|
32
40
|
end
|
|
33
41
|
RUBY
|
|
34
42
|
end
|
|
@@ -41,17 +49,20 @@ module SafeRequestTimeout
|
|
|
41
49
|
def create_module(klass, module_name, module_type)
|
|
42
50
|
# Create a module that will be prepended to the specified class.
|
|
43
51
|
unless module_name
|
|
44
|
-
camelized_name = name.to_s.gsub(/[^a-z0-9]+([a-z0-9])/i) { |m| m[
|
|
52
|
+
camelized_name = name.to_s.gsub(/[^a-z0-9]+([a-z0-9])/i) { |m| m[-1].upcase }
|
|
45
53
|
camelized_name = "#{camelized_name[0].upcase}#{camelized_name[1, camelized_name.length]}"
|
|
46
54
|
module_name = "#{klass.name.split("::").join}#{camelized_name}#{module_type}"
|
|
47
55
|
end
|
|
48
56
|
|
|
49
|
-
|
|
50
|
-
|
|
57
|
+
# Reuse an existing module so that registering the same hooks twice is a no-op.
|
|
58
|
+
MUTEX.synchronize do
|
|
59
|
+
if const_defined?(module_name, false)
|
|
60
|
+
const_get(module_name, false)
|
|
61
|
+
else
|
|
62
|
+
# Dark arts & witchery to dynamically generate the module methods.
|
|
63
|
+
const_set(module_name, Module.new)
|
|
64
|
+
end
|
|
51
65
|
end
|
|
52
|
-
|
|
53
|
-
# Dark arts & witchery to dynamically generate the module methods.
|
|
54
|
-
const_set(module_name, Module.new)
|
|
55
66
|
end
|
|
56
67
|
|
|
57
68
|
def splat_args
|
|
@@ -9,17 +9,24 @@ module SafeRequestTimeout
|
|
|
9
9
|
initializer "safe_request_timeout" do |app|
|
|
10
10
|
if app.config.safe_request_timeout.active_record_hook
|
|
11
11
|
ActiveSupport.on_load(:active_record) do
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
SafeRequestTimeout::ActiveRecordHook.add_timeout!
|
|
13
|
+
rescue => e
|
|
14
|
+
# The hook is an optional safeguard, so any error installing it (including an
|
|
15
|
+
# unexpected incompatibility with the ActiveRecord version in use) is logged
|
|
16
|
+
# rather than allowed to prevent the application from booting.
|
|
17
|
+
Rails.logger&.warn("Could not add ActiveRecord hook for SafeRequestTimeout: #{e.inspect}")
|
|
17
18
|
end
|
|
18
19
|
end
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
ActiveSupport.on_load(:active_job) do
|
|
22
|
+
around_perform do |job, block|
|
|
23
|
+
# Open a timeout context so jobs can call set_timeout, but don't replace a timeout
|
|
24
|
+
# already established for the request or worker that is running the job.
|
|
25
|
+
if SafeRequestTimeout.time_elapsed
|
|
26
|
+
block.call
|
|
27
|
+
else
|
|
28
|
+
SafeRequestTimeout.timeout(nil, &block)
|
|
29
|
+
end
|
|
23
30
|
end
|
|
24
31
|
end
|
|
25
32
|
|
data/lib/safe_request_timeout.rb
CHANGED
|
@@ -31,8 +31,9 @@ module SafeRequestTimeout
|
|
|
31
31
|
def timeout(duration, &block)
|
|
32
32
|
duration = duration.call if duration.respond_to?(:call)
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
state = current_state
|
|
35
|
+
previous_start_at = state[:safe_request_timeout_started_at]
|
|
36
|
+
previous_timeout_at = state[:safe_request_timeout_timeout_at]
|
|
36
37
|
|
|
37
38
|
start_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
38
39
|
timeout_at = start_at + duration if duration
|
|
@@ -41,12 +42,18 @@ module SafeRequestTimeout
|
|
|
41
42
|
end
|
|
42
43
|
|
|
43
44
|
begin
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
state[:safe_request_timeout_started_at] = start_at
|
|
46
|
+
state[:safe_request_timeout_timeout_at] = timeout_at
|
|
46
47
|
yield
|
|
47
48
|
ensure
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
# If the deadline shared with the parent block was already raised by check_timeout!,
|
|
50
|
+
# don't re-arm it when restoring the parent block's state.
|
|
51
|
+
if previous_timeout_at && previous_timeout_at == state[:safe_request_timeout_fired_at]
|
|
52
|
+
previous_timeout_at = nil
|
|
53
|
+
end
|
|
54
|
+
state[:safe_request_timeout_started_at] = previous_start_at
|
|
55
|
+
state[:safe_request_timeout_timeout_at] = previous_timeout_at
|
|
56
|
+
state[:safe_request_timeout_fired_at] = nil if previous_start_at.nil?
|
|
50
57
|
end
|
|
51
58
|
end
|
|
52
59
|
|
|
@@ -54,7 +61,7 @@ module SafeRequestTimeout
|
|
|
54
61
|
#
|
|
55
62
|
# @return [Boolean] true if the current timeout block has timed out
|
|
56
63
|
def timed_out?
|
|
57
|
-
timeout_at =
|
|
64
|
+
timeout_at = current_state[:safe_request_timeout_timeout_at]
|
|
58
65
|
!!timeout_at && Process.clock_gettime(Process::CLOCK_MONOTONIC) > timeout_at
|
|
59
66
|
end
|
|
60
67
|
|
|
@@ -66,7 +73,9 @@ module SafeRequestTimeout
|
|
|
66
73
|
# @raise [SafeRequestTimeout::TimeoutError] if the current timeout block has timed out
|
|
67
74
|
def check_timeout!
|
|
68
75
|
if timed_out?
|
|
69
|
-
|
|
76
|
+
state = current_state
|
|
77
|
+
state[:safe_request_timeout_fired_at] = state[:safe_request_timeout_timeout_at]
|
|
78
|
+
state[:safe_request_timeout_timeout_at] = nil
|
|
70
79
|
raise TimeoutError.new("after #{time_elapsed.round(6)} seconds")
|
|
71
80
|
end
|
|
72
81
|
end
|
|
@@ -76,7 +85,7 @@ module SafeRequestTimeout
|
|
|
76
85
|
#
|
|
77
86
|
# @return [Float, nil] the number of seconds remaining in the current timeout block
|
|
78
87
|
def time_remaining
|
|
79
|
-
timeout_at =
|
|
88
|
+
timeout_at = current_state[:safe_request_timeout_timeout_at]
|
|
80
89
|
[timeout_at - Process.clock_gettime(Process::CLOCK_MONOTONIC), 0.0].max if timeout_at
|
|
81
90
|
end
|
|
82
91
|
|
|
@@ -85,7 +94,7 @@ module SafeRequestTimeout
|
|
|
85
94
|
#
|
|
86
95
|
# @return [Float, nil] the number of seconds elapsed in the current timeout block began
|
|
87
96
|
def time_elapsed
|
|
88
|
-
start_at =
|
|
97
|
+
start_at = current_state[:safe_request_timeout_started_at]
|
|
89
98
|
Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_at if start_at
|
|
90
99
|
end
|
|
91
100
|
|
|
@@ -95,12 +104,17 @@ module SafeRequestTimeout
|
|
|
95
104
|
#
|
|
96
105
|
# @return [void]
|
|
97
106
|
def set_timeout(duration)
|
|
98
|
-
|
|
107
|
+
state = current_state
|
|
108
|
+
if state[:safe_request_timeout_started_at]
|
|
99
109
|
start_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
100
110
|
duration = duration.call if duration.respond_to?(:call)
|
|
101
111
|
timeout_at = start_at + duration if duration
|
|
102
|
-
|
|
103
|
-
|
|
112
|
+
state[:safe_request_timeout_started_at] = start_at
|
|
113
|
+
state[:safe_request_timeout_timeout_at] = timeout_at
|
|
114
|
+
# Arming a new deadline invalidates any record of a previously fired deadline so
|
|
115
|
+
# that the ensure block in `timeout` cannot mistake the new deadline for one that
|
|
116
|
+
# has already raised.
|
|
117
|
+
state[:safe_request_timeout_fired_at] = nil if timeout_at
|
|
104
118
|
end
|
|
105
119
|
end
|
|
106
120
|
|
|
@@ -116,6 +130,19 @@ module SafeRequestTimeout
|
|
|
116
130
|
set_timeout(nil)
|
|
117
131
|
end
|
|
118
132
|
end
|
|
133
|
+
|
|
134
|
+
private
|
|
135
|
+
|
|
136
|
+
# Storage for the timeout state. ActiveSupport::IsolatedExecutionState is used when it
|
|
137
|
+
# is available so that the state follows the application's configured isolation level
|
|
138
|
+
# (:thread or :fiber). Otherwise the state is fiber local.
|
|
139
|
+
def current_state
|
|
140
|
+
if defined?(::ActiveSupport::IsolatedExecutionState)
|
|
141
|
+
::ActiveSupport::IsolatedExecutionState
|
|
142
|
+
else
|
|
143
|
+
::Thread.current
|
|
144
|
+
end
|
|
145
|
+
end
|
|
119
146
|
end
|
|
120
147
|
end
|
|
121
148
|
|
|
@@ -8,6 +8,12 @@ Gem::Specification.new do |spec|
|
|
|
8
8
|
spec.homepage = "https://github.com/bdurand/safe_request_timeout"
|
|
9
9
|
spec.license = "MIT"
|
|
10
10
|
|
|
11
|
+
spec.metadata = {
|
|
12
|
+
"homepage_uri" => spec.homepage,
|
|
13
|
+
"source_code_uri" => spec.homepage,
|
|
14
|
+
"changelog_uri" => "#{spec.homepage}/blob/main/CHANGELOG.md"
|
|
15
|
+
}
|
|
16
|
+
|
|
11
17
|
# Specify which files should be added to the gem when it is released.
|
|
12
18
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
13
19
|
ignore_files = %w[
|
|
@@ -26,9 +32,5 @@ Gem::Specification.new do |spec|
|
|
|
26
32
|
|
|
27
33
|
spec.require_paths = ["lib"]
|
|
28
34
|
|
|
29
|
-
spec.
|
|
30
|
-
|
|
31
|
-
spec.add_development_dependency "bundler"
|
|
32
|
-
|
|
33
|
-
spec.required_ruby_version = ">= 2.5"
|
|
35
|
+
spec.required_ruby_version = ">= 2.6"
|
|
34
36
|
end
|
metadata
CHANGED
|
@@ -1,44 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: safe_request_timeout
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Brian Durand
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
12
|
-
dependencies:
|
|
13
|
-
- !ruby/object:Gem::Dependency
|
|
14
|
-
name: redis
|
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
|
16
|
-
requirements:
|
|
17
|
-
- - ">="
|
|
18
|
-
- !ruby/object:Gem::Version
|
|
19
|
-
version: '0'
|
|
20
|
-
type: :runtime
|
|
21
|
-
prerelease: false
|
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
-
requirements:
|
|
24
|
-
- - ">="
|
|
25
|
-
- !ruby/object:Gem::Version
|
|
26
|
-
version: '0'
|
|
27
|
-
- !ruby/object:Gem::Dependency
|
|
28
|
-
name: bundler
|
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
|
30
|
-
requirements:
|
|
31
|
-
- - ">="
|
|
32
|
-
- !ruby/object:Gem::Version
|
|
33
|
-
version: '0'
|
|
34
|
-
type: :development
|
|
35
|
-
prerelease: false
|
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
-
requirements:
|
|
38
|
-
- - ">="
|
|
39
|
-
- !ruby/object:Gem::Version
|
|
40
|
-
version: '0'
|
|
41
|
-
description:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
42
12
|
email:
|
|
43
13
|
- bbdurand@gmail.com
|
|
44
14
|
executables: []
|
|
@@ -49,7 +19,6 @@ files:
|
|
|
49
19
|
- MIT_LICENSE.txt
|
|
50
20
|
- README.md
|
|
51
21
|
- VERSION
|
|
52
|
-
- dependabot.yml
|
|
53
22
|
- lib/safe_request_timeout.rb
|
|
54
23
|
- lib/safe_request_timeout/active_record_hook.rb
|
|
55
24
|
- lib/safe_request_timeout/hooks.rb
|
|
@@ -61,8 +30,10 @@ files:
|
|
|
61
30
|
homepage: https://github.com/bdurand/safe_request_timeout
|
|
62
31
|
licenses:
|
|
63
32
|
- MIT
|
|
64
|
-
metadata:
|
|
65
|
-
|
|
33
|
+
metadata:
|
|
34
|
+
homepage_uri: https://github.com/bdurand/safe_request_timeout
|
|
35
|
+
source_code_uri: https://github.com/bdurand/safe_request_timeout
|
|
36
|
+
changelog_uri: https://github.com/bdurand/safe_request_timeout/blob/main/CHANGELOG.md
|
|
66
37
|
rdoc_options: []
|
|
67
38
|
require_paths:
|
|
68
39
|
- lib
|
|
@@ -70,15 +41,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
70
41
|
requirements:
|
|
71
42
|
- - ">="
|
|
72
43
|
- !ruby/object:Gem::Version
|
|
73
|
-
version: '2.
|
|
44
|
+
version: '2.6'
|
|
74
45
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
75
46
|
requirements:
|
|
76
47
|
- - ">="
|
|
77
48
|
- !ruby/object:Gem::Version
|
|
78
49
|
version: '0'
|
|
79
50
|
requirements: []
|
|
80
|
-
rubygems_version:
|
|
81
|
-
signing_key:
|
|
51
|
+
rubygems_version: 4.0.3
|
|
82
52
|
specification_version: 4
|
|
83
53
|
summary: Mechanism for safely aborting long-running requests after a specified timeout.
|
|
84
54
|
test_files: []
|
data/dependabot.yml
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
# Dependabot update strategy
|
|
2
|
-
version: 2
|
|
3
|
-
updates:
|
|
4
|
-
- package-ecosystem: bundler
|
|
5
|
-
directory: "/"
|
|
6
|
-
schedule:
|
|
7
|
-
interval: daily
|
|
8
|
-
allow:
|
|
9
|
-
# Automatically keep all runtime dependencies updated
|
|
10
|
-
- dependency-name: "*"
|
|
11
|
-
dependency-type: "production"
|
|
12
|
-
versioning-strategy: lockfile-only
|