timeout 0.2.0 → 0.3.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/.github/dependabot.yml +6 -0
- data/.github/workflows/test.yml +6 -6
- data/lib/timeout.rb +97 -37
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d65cc31f39a22fdc316cc89f8f8e4adb95131700972fca8f65cb79ab5c844858
|
4
|
+
data.tar.gz: 9cb854b05e02484a179c83f56ddb3d85b885806148fd1350223d5406cc20c706
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d88ad9e1965efed376d164a1dd992f3aa423aab9bf3f6c0fdc482affc9895991ff542831ce6d8159771fa000d5e3e5c0679fedbfa73f94fe8c6548fe9f387714
|
7
|
+
data.tar.gz: '00529ed0c665d1f24c466461ab4c9298d79db2a00f9d12993e1cda402543006f3cf9e88c6f5a36286bef5b97fbaa4f0894cd4b1715b5691bb883bb7e48f37d4f'
|
data/.github/workflows/test.yml
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
name:
|
1
|
+
name: test
|
2
2
|
|
3
3
|
on: [push, pull_request]
|
4
4
|
|
@@ -6,17 +6,17 @@ jobs:
|
|
6
6
|
build:
|
7
7
|
name: build (${{ matrix.ruby }} / ${{ matrix.os }})
|
8
8
|
strategy:
|
9
|
+
fail-fast: false
|
9
10
|
matrix:
|
10
|
-
ruby: [ 2.7, 2.6, 2.5, 2.4, head ]
|
11
|
+
ruby: [ '3.0', 2.7, 2.6, 2.5, 2.4, head, jruby, truffleruby-head ]
|
11
12
|
os: [ ubuntu-latest, macos-latest ]
|
12
13
|
runs-on: ${{ matrix.os }}
|
13
14
|
steps:
|
14
|
-
- uses: actions/checkout@
|
15
|
+
- uses: actions/checkout@v3
|
15
16
|
- name: Set up Ruby
|
16
17
|
uses: ruby/setup-ruby@v1
|
17
18
|
with:
|
18
19
|
ruby-version: ${{ matrix.ruby }}
|
19
|
-
|
20
|
-
run: bundle install
|
20
|
+
bundler-cache: true # 'bundle install' and cache gems
|
21
21
|
- name: Run test
|
22
|
-
run: rake test
|
22
|
+
run: bundle exec rake test
|
data/lib/timeout.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# frozen_string_literal:
|
1
|
+
# frozen_string_literal: true
|
2
2
|
# Timeout long-running blocks
|
3
3
|
#
|
4
4
|
# == Synopsis
|
@@ -23,7 +23,7 @@
|
|
23
23
|
# Copyright:: (C) 2000 Information-technology Promotion Agency, Japan
|
24
24
|
|
25
25
|
module Timeout
|
26
|
-
VERSION = "0.
|
26
|
+
VERSION = "0.3.0"
|
27
27
|
|
28
28
|
# Raised by Timeout.timeout when the block times out.
|
29
29
|
class Error < RuntimeError
|
@@ -50,9 +50,88 @@ module Timeout
|
|
50
50
|
end
|
51
51
|
|
52
52
|
# :stopdoc:
|
53
|
-
|
54
|
-
|
55
|
-
|
53
|
+
CONDVAR = ConditionVariable.new
|
54
|
+
QUEUE = Queue.new
|
55
|
+
QUEUE_MUTEX = Mutex.new
|
56
|
+
TIMEOUT_THREAD_MUTEX = Mutex.new
|
57
|
+
@timeout_thread = nil
|
58
|
+
private_constant :CONDVAR, :QUEUE, :QUEUE_MUTEX, :TIMEOUT_THREAD_MUTEX
|
59
|
+
|
60
|
+
class Request
|
61
|
+
attr_reader :deadline
|
62
|
+
|
63
|
+
def initialize(thread, timeout, exception_class, message)
|
64
|
+
@thread = thread
|
65
|
+
@deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout
|
66
|
+
@exception_class = exception_class
|
67
|
+
@message = message
|
68
|
+
|
69
|
+
@mutex = Mutex.new
|
70
|
+
@done = false # protected by @mutex
|
71
|
+
end
|
72
|
+
|
73
|
+
def done?
|
74
|
+
@mutex.synchronize do
|
75
|
+
@done
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def expired?(now)
|
80
|
+
now >= @deadline
|
81
|
+
end
|
82
|
+
|
83
|
+
def interrupt
|
84
|
+
@mutex.synchronize do
|
85
|
+
unless @done
|
86
|
+
@thread.raise @exception_class, @message
|
87
|
+
@done = true
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def finished
|
93
|
+
@mutex.synchronize do
|
94
|
+
@done = true
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
private_constant :Request
|
99
|
+
|
100
|
+
def self.create_timeout_thread
|
101
|
+
Thread.new do
|
102
|
+
requests = []
|
103
|
+
while true
|
104
|
+
until QUEUE.empty? and !requests.empty? # wait to have at least one request
|
105
|
+
req = QUEUE.pop
|
106
|
+
requests << req unless req.done?
|
107
|
+
end
|
108
|
+
closest_deadline = requests.min_by(&:deadline).deadline
|
109
|
+
|
110
|
+
now = 0.0
|
111
|
+
QUEUE_MUTEX.synchronize do
|
112
|
+
while (now = Process.clock_gettime(Process::CLOCK_MONOTONIC)) < closest_deadline and QUEUE.empty?
|
113
|
+
CONDVAR.wait(QUEUE_MUTEX, closest_deadline - now)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
requests.each do |req|
|
118
|
+
req.interrupt if req.expired?(now)
|
119
|
+
end
|
120
|
+
requests.reject!(&:done?)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
private_class_method :create_timeout_thread
|
125
|
+
|
126
|
+
def self.ensure_timeout_thread_created
|
127
|
+
unless @timeout_thread and @timeout_thread.alive?
|
128
|
+
TIMEOUT_THREAD_MUTEX.synchronize do
|
129
|
+
unless @timeout_thread and @timeout_thread.alive?
|
130
|
+
@timeout_thread = create_timeout_thread
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
56
135
|
# :startdoc:
|
57
136
|
|
58
137
|
# Perform an operation in a block, raising an error if it takes longer than
|
@@ -83,51 +162,32 @@ module Timeout
|
|
83
162
|
def timeout(sec, klass = nil, message = nil, &block) #:yield: +sec+
|
84
163
|
return yield(sec) if sec == nil or sec.zero?
|
85
164
|
|
86
|
-
message ||= "execution expired"
|
165
|
+
message ||= "execution expired"
|
87
166
|
|
88
167
|
if Fiber.respond_to?(:current_scheduler) && (scheduler = Fiber.current_scheduler)&.respond_to?(:timeout_after)
|
89
168
|
return scheduler.timeout_after(sec, klass || Error, message, &block)
|
90
169
|
end
|
91
170
|
|
92
|
-
|
93
|
-
|
94
|
-
|
171
|
+
Timeout.ensure_timeout_thread_created
|
172
|
+
perform = Proc.new do |exc|
|
173
|
+
request = Request.new(Thread.current, sec, exc, message)
|
174
|
+
QUEUE_MUTEX.synchronize do
|
175
|
+
QUEUE << request
|
176
|
+
CONDVAR.signal
|
177
|
+
end
|
95
178
|
begin
|
96
|
-
x = Thread.current
|
97
|
-
y = Thread.start {
|
98
|
-
Thread.current.name = from
|
99
|
-
begin
|
100
|
-
sleep sec
|
101
|
-
rescue => e
|
102
|
-
x.raise e
|
103
|
-
else
|
104
|
-
x.raise exception, message
|
105
|
-
end
|
106
|
-
}
|
107
179
|
return yield(sec)
|
108
180
|
ensure
|
109
|
-
|
110
|
-
y.kill
|
111
|
-
y.join # make sure y is dead.
|
112
|
-
end
|
181
|
+
request.finished
|
113
182
|
end
|
114
183
|
end
|
184
|
+
|
115
185
|
if klass
|
116
|
-
|
117
|
-
bl.call(klass)
|
118
|
-
rescue klass => e
|
119
|
-
message = e.message
|
120
|
-
bt = e.backtrace
|
121
|
-
end
|
186
|
+
perform.call(klass)
|
122
187
|
else
|
123
|
-
|
188
|
+
backtrace = Error.catch(&perform)
|
189
|
+
raise Error, message, backtrace
|
124
190
|
end
|
125
|
-
level = -caller(CALLER_OFFSET).size-2
|
126
|
-
while THIS_FILE =~ bt[level]
|
127
|
-
bt.delete_at(level)
|
128
|
-
end
|
129
|
-
raise(e, message, bt)
|
130
191
|
end
|
131
|
-
|
132
192
|
module_function :timeout
|
133
193
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: timeout
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yukihiro Matsumoto
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-05-25 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Auto-terminate potentially long-running operations in Ruby.
|
14
14
|
email:
|
@@ -17,6 +17,7 @@ executables: []
|
|
17
17
|
extensions: []
|
18
18
|
extra_rdoc_files: []
|
19
19
|
files:
|
20
|
+
- ".github/dependabot.yml"
|
20
21
|
- ".github/workflows/test.yml"
|
21
22
|
- ".gitignore"
|
22
23
|
- Gemfile
|
@@ -49,7 +50,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
49
50
|
- !ruby/object:Gem::Version
|
50
51
|
version: '0'
|
51
52
|
requirements: []
|
52
|
-
rubygems_version: 3.3.
|
53
|
+
rubygems_version: 3.3.7
|
53
54
|
signing_key:
|
54
55
|
specification_version: 4
|
55
56
|
summary: Auto-terminate potentially long-running operations in Ruby.
|