timeout 0.4.4 → 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 -61
- metadata +2 -2
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,54 +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
|
-
# If the Mutex is already owned we are in a signal handler.
|
|
127
|
-
# In that case, just return and let the main thread create the @timeout_thread.
|
|
128
|
-
return if TIMEOUT_THREAD_MUTEX.owned?
|
|
129
|
-
TIMEOUT_THREAD_MUTEX.synchronize do
|
|
130
|
-
unless @timeout_thread and @timeout_thread.alive?
|
|
131
|
-
@timeout_thread = create_timeout_thread
|
|
132
|
-
end
|
|
133
|
-
end
|
|
134
|
-
end
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
# We keep a private reference so that time mocking libraries won't break
|
|
138
|
-
# Timeout.
|
|
139
|
-
GET_TIME = Process.method(:clock_gettime)
|
|
140
|
-
private_constant :GET_TIME
|
|
141
|
-
|
|
142
174
|
# :startdoc:
|
|
143
175
|
|
|
144
176
|
# Perform an operation in a block, raising an error if it takes longer than
|
|
@@ -167,7 +199,7 @@ module Timeout
|
|
|
167
199
|
# Note that this is both a method of module Timeout, so you can <tt>include
|
|
168
200
|
# Timeout</tt> into your classes so they have a #timeout method, as well as
|
|
169
201
|
# a module method, so you can call it directly as Timeout.timeout().
|
|
170
|
-
def timeout(sec, klass = nil, message = nil, &block) #:yield: +sec+
|
|
202
|
+
def self.timeout(sec, klass = nil, message = nil, &block) #:yield: +sec+
|
|
171
203
|
return yield(sec) if sec == nil or sec.zero?
|
|
172
204
|
raise ArgumentError, "Timeout sec must be a non-negative number" if 0 > sec
|
|
173
205
|
|
|
@@ -177,12 +209,14 @@ module Timeout
|
|
|
177
209
|
return scheduler.timeout_after(sec, klass || Error, message, &block)
|
|
178
210
|
end
|
|
179
211
|
|
|
180
|
-
|
|
212
|
+
state = State.instance
|
|
213
|
+
state.ensure_timeout_thread_created
|
|
214
|
+
|
|
181
215
|
perform = Proc.new do |exc|
|
|
182
216
|
request = Request.new(Thread.current, sec, exc, message)
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
217
|
+
state.queue_mutex.synchronize do
|
|
218
|
+
state.queue << request
|
|
219
|
+
state.condvar.signal
|
|
186
220
|
end
|
|
187
221
|
begin
|
|
188
222
|
return yield(sec)
|
|
@@ -197,5 +231,8 @@ module Timeout
|
|
|
197
231
|
Error.handle_timeout(message, &perform)
|
|
198
232
|
end
|
|
199
233
|
end
|
|
200
|
-
|
|
234
|
+
|
|
235
|
+
private def timeout(*args, &block)
|
|
236
|
+
Timeout.timeout(*args, &block)
|
|
237
|
+
end
|
|
201
238
|
end
|
metadata
CHANGED
|
@@ -1,13 +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
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2025-
|
|
10
|
+
date: 2025-12-08 00:00:00.000000000 Z
|
|
11
11
|
dependencies: []
|
|
12
12
|
description: Auto-terminate potentially long-running operations in Ruby.
|
|
13
13
|
email:
|