timeout 0.4.3 → 0.5.0
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/lib/timeout.rb +98 -58
- metadata +3 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 64020467581a9c1fe3f3b8dac1276315e0653c8029e12e031ba61dffe379629d
|
|
4
|
+
data.tar.gz: 13473fd4bcfa02a9406c1e4b8c949c696634def65c3fdbd03bfe35670adf3194
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 79b97c0bc72c9417b5185928947f629733b027fc907213293bb90441bca438afe3ccaf9e549811dfb9da4c5796c6a413eb31d4d687bb84868e718a0e6bb4bbaf
|
|
7
|
+
data.tar.gz: 49ea49ecd7e9b73834e5410188d955be6c256f0f5ea349aa816187bb57df32e403bb8f87f80fea11dd42c1fc789c7ac17de4b6f5942ce17513a18d7fa0b35834
|
data/lib/timeout.rb
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
module Timeout
|
|
22
22
|
# The version
|
|
23
|
-
VERSION = "0.
|
|
23
|
+
VERSION = "0.5.0"
|
|
24
24
|
|
|
25
25
|
# Internal error raised to when a timeout is triggered.
|
|
26
26
|
class ExitException < Exception
|
|
@@ -44,12 +44,92 @@ module Timeout
|
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
# :stopdoc:
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
|
|
48
|
+
# We keep a private reference so that time mocking libraries won't break Timeout.
|
|
49
|
+
GET_TIME = Process.method(:clock_gettime)
|
|
50
|
+
if defined?(Ractor.make_shareable)
|
|
51
|
+
# Ractor.make_shareable(Method) only works on Ruby 4+
|
|
52
|
+
Ractor.make_shareable(GET_TIME) rescue nil
|
|
53
|
+
end
|
|
54
|
+
private_constant :GET_TIME
|
|
55
|
+
|
|
56
|
+
class State
|
|
57
|
+
attr_reader :condvar, :queue, :queue_mutex # shared with Timeout.timeout()
|
|
58
|
+
|
|
59
|
+
def initialize
|
|
60
|
+
@condvar = ConditionVariable.new
|
|
61
|
+
@queue = Queue.new
|
|
62
|
+
@queue_mutex = Mutex.new
|
|
63
|
+
|
|
64
|
+
@timeout_thread = nil
|
|
65
|
+
@timeout_thread_mutex = Mutex.new
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
if defined?(Ractor.store_if_absent) && defined?(Ractor.shareable?) && Ractor.shareable?(GET_TIME)
|
|
69
|
+
# Ractor support if
|
|
70
|
+
# 1. Ractor.store_if_absent is available
|
|
71
|
+
# 2. Method object can be shareable (4.0~)
|
|
72
|
+
def self.instance
|
|
73
|
+
Ractor.store_if_absent :timeout_gem_state do
|
|
74
|
+
State.new
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
else
|
|
78
|
+
GLOBAL_STATE = State.new
|
|
79
|
+
|
|
80
|
+
def self.instance
|
|
81
|
+
GLOBAL_STATE
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def create_timeout_thread
|
|
86
|
+
watcher = Thread.new do
|
|
87
|
+
requests = []
|
|
88
|
+
while true
|
|
89
|
+
until @queue.empty? and !requests.empty? # wait to have at least one request
|
|
90
|
+
req = @queue.pop
|
|
91
|
+
requests << req unless req.done?
|
|
92
|
+
end
|
|
93
|
+
closest_deadline = requests.min_by(&:deadline).deadline
|
|
94
|
+
|
|
95
|
+
now = 0.0
|
|
96
|
+
@queue_mutex.synchronize do
|
|
97
|
+
while (now = GET_TIME.call(Process::CLOCK_MONOTONIC)) < closest_deadline and @queue.empty?
|
|
98
|
+
@condvar.wait(@queue_mutex, closest_deadline - now)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
requests.each do |req|
|
|
103
|
+
req.interrupt if req.expired?(now)
|
|
104
|
+
end
|
|
105
|
+
requests.reject!(&:done?)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
if !watcher.group.enclosed? && (!defined?(Ractor.main?) || Ractor.main?)
|
|
110
|
+
ThreadGroup::Default.add(watcher)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
watcher.name = "Timeout stdlib thread"
|
|
114
|
+
watcher.thread_variable_set(:"\0__detached_thread__", true)
|
|
115
|
+
watcher
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def ensure_timeout_thread_created
|
|
119
|
+
unless @timeout_thread&.alive?
|
|
120
|
+
# If the Mutex is already owned we are in a signal handler.
|
|
121
|
+
# In that case, just return and let the main thread create the Timeout thread.
|
|
122
|
+
return if @timeout_thread_mutex.owned?
|
|
123
|
+
|
|
124
|
+
@timeout_thread_mutex.synchronize do
|
|
125
|
+
unless @timeout_thread&.alive?
|
|
126
|
+
@timeout_thread = create_timeout_thread
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
private_constant :State
|
|
53
133
|
|
|
54
134
|
class Request
|
|
55
135
|
attr_reader :deadline
|
|
@@ -91,51 +171,6 @@ module Timeout
|
|
|
91
171
|
end
|
|
92
172
|
private_constant :Request
|
|
93
173
|
|
|
94
|
-
def self.create_timeout_thread
|
|
95
|
-
watcher = Thread.new do
|
|
96
|
-
requests = []
|
|
97
|
-
while true
|
|
98
|
-
until QUEUE.empty? and !requests.empty? # wait to have at least one request
|
|
99
|
-
req = QUEUE.pop
|
|
100
|
-
requests << req unless req.done?
|
|
101
|
-
end
|
|
102
|
-
closest_deadline = requests.min_by(&:deadline).deadline
|
|
103
|
-
|
|
104
|
-
now = 0.0
|
|
105
|
-
QUEUE_MUTEX.synchronize do
|
|
106
|
-
while (now = GET_TIME.call(Process::CLOCK_MONOTONIC)) < closest_deadline and QUEUE.empty?
|
|
107
|
-
CONDVAR.wait(QUEUE_MUTEX, closest_deadline - now)
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
requests.each do |req|
|
|
112
|
-
req.interrupt if req.expired?(now)
|
|
113
|
-
end
|
|
114
|
-
requests.reject!(&:done?)
|
|
115
|
-
end
|
|
116
|
-
end
|
|
117
|
-
ThreadGroup::Default.add(watcher) unless watcher.group.enclosed?
|
|
118
|
-
watcher.name = "Timeout stdlib thread"
|
|
119
|
-
watcher.thread_variable_set(:"\0__detached_thread__", true)
|
|
120
|
-
watcher
|
|
121
|
-
end
|
|
122
|
-
private_class_method :create_timeout_thread
|
|
123
|
-
|
|
124
|
-
def self.ensure_timeout_thread_created
|
|
125
|
-
unless @timeout_thread and @timeout_thread.alive?
|
|
126
|
-
TIMEOUT_THREAD_MUTEX.synchronize do
|
|
127
|
-
unless @timeout_thread and @timeout_thread.alive?
|
|
128
|
-
@timeout_thread = create_timeout_thread
|
|
129
|
-
end
|
|
130
|
-
end
|
|
131
|
-
end
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
# We keep a private reference so that time mocking libraries won't break
|
|
135
|
-
# Timeout.
|
|
136
|
-
GET_TIME = Process.method(:clock_gettime)
|
|
137
|
-
private_constant :GET_TIME
|
|
138
|
-
|
|
139
174
|
# :startdoc:
|
|
140
175
|
|
|
141
176
|
# Perform an operation in a block, raising an error if it takes longer than
|
|
@@ -164,7 +199,7 @@ module Timeout
|
|
|
164
199
|
# Note that this is both a method of module Timeout, so you can <tt>include
|
|
165
200
|
# Timeout</tt> into your classes so they have a #timeout method, as well as
|
|
166
201
|
# a module method, so you can call it directly as Timeout.timeout().
|
|
167
|
-
def timeout(sec, klass = nil, message = nil, &block) #:yield: +sec+
|
|
202
|
+
def self.timeout(sec, klass = nil, message = nil, &block) #:yield: +sec+
|
|
168
203
|
return yield(sec) if sec == nil or sec.zero?
|
|
169
204
|
raise ArgumentError, "Timeout sec must be a non-negative number" if 0 > sec
|
|
170
205
|
|
|
@@ -174,12 +209,14 @@ module Timeout
|
|
|
174
209
|
return scheduler.timeout_after(sec, klass || Error, message, &block)
|
|
175
210
|
end
|
|
176
211
|
|
|
177
|
-
|
|
212
|
+
state = State.instance
|
|
213
|
+
state.ensure_timeout_thread_created
|
|
214
|
+
|
|
178
215
|
perform = Proc.new do |exc|
|
|
179
216
|
request = Request.new(Thread.current, sec, exc, message)
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
217
|
+
state.queue_mutex.synchronize do
|
|
218
|
+
state.queue << request
|
|
219
|
+
state.condvar.signal
|
|
183
220
|
end
|
|
184
221
|
begin
|
|
185
222
|
return yield(sec)
|
|
@@ -194,5 +231,8 @@ module Timeout
|
|
|
194
231
|
Error.handle_timeout(message, &perform)
|
|
195
232
|
end
|
|
196
233
|
end
|
|
197
|
-
|
|
234
|
+
|
|
235
|
+
private def timeout(*args, &block)
|
|
236
|
+
Timeout.timeout(*args, &block)
|
|
237
|
+
end
|
|
198
238
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: timeout
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yukihiro Matsumoto
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 2025-12-08 00:00:00.000000000 Z
|
|
12
11
|
dependencies: []
|
|
13
12
|
description: Auto-terminate potentially long-running operations in Ruby.
|
|
14
13
|
email:
|
|
@@ -32,7 +31,6 @@ metadata:
|
|
|
32
31
|
homepage_uri: https://github.com/ruby/timeout
|
|
33
32
|
source_code_uri: https://github.com/ruby/timeout
|
|
34
33
|
changelog_uri: https://github.com/ruby/timeout/releases
|
|
35
|
-
post_install_message:
|
|
36
34
|
rdoc_options: []
|
|
37
35
|
require_paths:
|
|
38
36
|
- lib
|
|
@@ -47,8 +45,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
47
45
|
- !ruby/object:Gem::Version
|
|
48
46
|
version: '0'
|
|
49
47
|
requirements: []
|
|
50
|
-
rubygems_version: 3.
|
|
51
|
-
signing_key:
|
|
48
|
+
rubygems_version: 3.6.9
|
|
52
49
|
specification_version: 4
|
|
53
50
|
summary: Auto-terminate potentially long-running operations in Ruby.
|
|
54
51
|
test_files: []
|