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.
Files changed (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/timeout.rb +98 -61
  3. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2e546a82029e45c714f45f0a47cbcb3e747ba7faee26569c1c23126aa15af62f
4
- data.tar.gz: 3a3b43a30e17b257eb10efbaf542cfa1cba3f2382900c5d84d6fc8ffd48fc053
3
+ metadata.gz: 64020467581a9c1fe3f3b8dac1276315e0653c8029e12e031ba61dffe379629d
4
+ data.tar.gz: 13473fd4bcfa02a9406c1e4b8c949c696634def65c3fdbd03bfe35670adf3194
5
5
  SHA512:
6
- metadata.gz: aed390b63eb9cbbf06ad8077e107fc8524400102b63bd02615336ecf051e86c35d5258df1e148246c0675c22c84dfc4a652f9b1dffeef79515c011ffec31eb9b
7
- data.tar.gz: e53db10269c082ff01290139ab63783b1e33e4f2cf29a33ef7741af766256187f54270a16c9f1da84060a4824b5bb723d1822043513a4f7292947588d11f5f99
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.4.4"
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
- CONDVAR = ConditionVariable.new
48
- QUEUE = Queue.new
49
- QUEUE_MUTEX = Mutex.new
50
- TIMEOUT_THREAD_MUTEX = Mutex.new
51
- @timeout_thread = nil
52
- private_constant :CONDVAR, :QUEUE, :QUEUE_MUTEX, :TIMEOUT_THREAD_MUTEX
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
- Timeout.ensure_timeout_thread_created
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
- QUEUE_MUTEX.synchronize do
184
- QUEUE << request
185
- CONDVAR.signal
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
- module_function :timeout
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.4
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-29 00:00:00.000000000 Z
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: