timeout 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 62c9c45a4b7b1b7d4d0ee380d1827ee0718b895c368651be2c1a14acd5722e5a
4
- data.tar.gz: 99344f3c5fd6808bc63fee9f5e96e21ffbb0ad495c1a5f0015cf5041e17ef74f
3
+ metadata.gz: d65cc31f39a22fdc316cc89f8f8e4adb95131700972fca8f65cb79ab5c844858
4
+ data.tar.gz: 9cb854b05e02484a179c83f56ddb3d85b885806148fd1350223d5406cc20c706
5
5
  SHA512:
6
- metadata.gz: 07f0da54b3057e69eef0a421fb5203710e5fe1837ba3c9ec234d81f77736fc524a18c57cd93ed07c403bdd585f588b764fd77bb71c39fed0bfd6af60b1bd9038
7
- data.tar.gz: dcb89670a2e4593c64e39c6e6c1c50ef9df4d5810bab6272a82f7038a01b52efd45903489d587f40e60f609a39befe84be4b8bac333132406b14c87444f1c606
6
+ metadata.gz: d88ad9e1965efed376d164a1dd992f3aa423aab9bf3f6c0fdc482affc9895991ff542831ce6d8159771fa000d5e3e5c0679fedbfa73f94fe8c6548fe9f387714
7
+ data.tar.gz: '00529ed0c665d1f24c466461ab4c9298d79db2a00f9d12993e1cda402543006f3cf9e88c6f5a36286bef5b97fbaa4f0894cd4b1715b5691bb883bb7e48f37d4f'
@@ -0,0 +1,6 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: 'github-actions'
4
+ directory: '/'
5
+ schedule:
6
+ interval: 'weekly'
@@ -1,4 +1,4 @@
1
- name: ubuntu
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@v2
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
- - name: Install dependencies
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: false
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.2.0".freeze
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
- THIS_FILE = /\A#{Regexp.quote(__FILE__)}:/o
54
- CALLER_OFFSET = ((c = caller[0]) && THIS_FILE =~ c) ? 1 : 0
55
- private_constant :THIS_FILE, :CALLER_OFFSET
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".freeze
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
- from = "from #{caller_locations(1, 1)[0]}" if $DEBUG
93
- e = Error
94
- bl = proc do |exception|
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
- if y
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
- begin
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
- bt = Error.catch(message, &bl)
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.2.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: 2021-10-14 00:00:00.000000000 Z
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.0.dev
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.