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 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.