timeout 0.2.0 → 0.3.1

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: 7a6594a086deee17da9d946b2f6068596050273da5171289b9f89d0ceb77a7b6
4
+ data.tar.gz: 6fee2749f06aa755490e9865ce0e4a398c3f06a1b93d1542851b496f7b85d423
5
5
  SHA512:
6
- metadata.gz: 07f0da54b3057e69eef0a421fb5203710e5fe1837ba3c9ec234d81f77736fc524a18c57cd93ed07c403bdd585f588b764fd77bb71c39fed0bfd6af60b1bd9038
7
- data.tar.gz: dcb89670a2e4593c64e39c6e6c1c50ef9df4d5810bab6272a82f7038a01b52efd45903489d587f40e60f609a39befe84be4b8bac333132406b14c87444f1c606
6
+ metadata.gz: c99b16bbdcfb0c08b46394ffc02da7df7e0f6377ff41f4d4ff6283301e68e316ed38b9a1efbca97a42308f49537cc68ee9a616c2b440bde671c92a26fdd4cfb1
7
+ data.tar.gz: 7af440551ea27f95a2769dd046339073e2d15bd0ae0e0c6a2922666b7091775083121a575b9178c939acdb0d903d1b591c725dc9bd804a95cc1954ae61af8b30
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.1"
27
27
 
28
28
  # Raised by Timeout.timeout when the block times out.
29
29
  class Error < RuntimeError
@@ -50,9 +50,98 @@ 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 = GET_TIME.call(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
+ watcher = 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 = GET_TIME.call(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
+ ThreadGroup::Default.add(watcher)
124
+ watcher.name = "Timeout stdlib thread"
125
+ watcher.thread_variable_set(:"\0__detached_thread__", true)
126
+ watcher
127
+ end
128
+ private_class_method :create_timeout_thread
129
+
130
+ def self.ensure_timeout_thread_created
131
+ unless @timeout_thread and @timeout_thread.alive?
132
+ TIMEOUT_THREAD_MUTEX.synchronize do
133
+ unless @timeout_thread and @timeout_thread.alive?
134
+ @timeout_thread = create_timeout_thread
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ # We keep a private reference so that time mocking libraries won't break
141
+ # Timeout.
142
+ GET_TIME = Process.method(:clock_gettime)
143
+ private_constant :GET_TIME
144
+
56
145
  # :startdoc:
57
146
 
58
147
  # Perform an operation in a block, raising an error if it takes longer than
@@ -83,51 +172,32 @@ module Timeout
83
172
  def timeout(sec, klass = nil, message = nil, &block) #:yield: +sec+
84
173
  return yield(sec) if sec == nil or sec.zero?
85
174
 
86
- message ||= "execution expired".freeze
175
+ message ||= "execution expired"
87
176
 
88
177
  if Fiber.respond_to?(:current_scheduler) && (scheduler = Fiber.current_scheduler)&.respond_to?(:timeout_after)
89
178
  return scheduler.timeout_after(sec, klass || Error, message, &block)
90
179
  end
91
180
 
92
- from = "from #{caller_locations(1, 1)[0]}" if $DEBUG
93
- e = Error
94
- bl = proc do |exception|
181
+ Timeout.ensure_timeout_thread_created
182
+ perform = Proc.new do |exc|
183
+ request = Request.new(Thread.current, sec, exc, message)
184
+ QUEUE_MUTEX.synchronize do
185
+ QUEUE << request
186
+ CONDVAR.signal
187
+ end
95
188
  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
189
  return yield(sec)
108
190
  ensure
109
- if y
110
- y.kill
111
- y.join # make sure y is dead.
112
- end
191
+ request.finished
113
192
  end
114
193
  end
194
+
115
195
  if klass
116
- begin
117
- bl.call(klass)
118
- rescue klass => e
119
- message = e.message
120
- bt = e.backtrace
121
- end
196
+ perform.call(klass)
122
197
  else
123
- bt = Error.catch(message, &bl)
198
+ backtrace = Error.catch(&perform)
199
+ raise Error, message, backtrace
124
200
  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
201
  end
131
-
132
202
  module_function :timeout
133
203
  end
data/timeout.gemspec CHANGED
@@ -21,10 +21,10 @@ Gem::Specification.new do |spec|
21
21
  spec.metadata["homepage_uri"] = spec.homepage
22
22
  spec.metadata["source_code_uri"] = spec.homepage
23
23
 
24
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
25
- `git ls-files -z 2>/dev/null`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
24
+ spec.files = Dir.chdir(__dir__) do
25
+ `git ls-files -z`.split("\x0").reject do |f|
26
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features|rakelib)/|\.(?:git|travis|circleci)|appveyor|Rakefile)})
27
+ end
26
28
  end
27
- spec.bindir = "exe"
28
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
29
29
  spec.require_paths = ["lib"]
30
30
  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.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yukihiro Matsumoto
8
8
  autorequire:
9
- bindir: exe
9
+ bindir: bin
10
10
  cert_chain: []
11
- date: 2021-10-14 00:00:00.000000000 Z
11
+ date: 2022-12-05 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Auto-terminate potentially long-running operations in Ruby.
14
14
  email:
@@ -17,14 +17,9 @@ executables: []
17
17
  extensions: []
18
18
  extra_rdoc_files: []
19
19
  files:
20
- - ".github/workflows/test.yml"
21
- - ".gitignore"
22
20
  - Gemfile
23
21
  - LICENSE.txt
24
22
  - README.md
25
- - Rakefile
26
- - bin/console
27
- - bin/setup
28
23
  - lib/timeout.rb
29
24
  - timeout.gemspec
30
25
  homepage: https://github.com/ruby/timeout
@@ -49,7 +44,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
49
44
  - !ruby/object:Gem::Version
50
45
  version: '0'
51
46
  requirements: []
52
- rubygems_version: 3.3.0.dev
47
+ rubygems_version: 3.4.0.dev
53
48
  signing_key:
54
49
  specification_version: 4
55
50
  summary: Auto-terminate potentially long-running operations in Ruby.
@@ -1,22 +0,0 @@
1
- name: ubuntu
2
-
3
- on: [push, pull_request]
4
-
5
- jobs:
6
- build:
7
- name: build (${{ matrix.ruby }} / ${{ matrix.os }})
8
- strategy:
9
- matrix:
10
- ruby: [ 2.7, 2.6, 2.5, 2.4, head ]
11
- os: [ ubuntu-latest, macos-latest ]
12
- runs-on: ${{ matrix.os }}
13
- steps:
14
- - uses: actions/checkout@v2
15
- - name: Set up Ruby
16
- uses: ruby/setup-ruby@v1
17
- with:
18
- ruby-version: ${{ matrix.ruby }}
19
- - name: Install dependencies
20
- run: bundle install
21
- - name: Run test
22
- run: rake test
data/.gitignore DELETED
@@ -1,9 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /_yardoc/
4
- /coverage/
5
- /doc/
6
- /pkg/
7
- /spec/reports/
8
- /tmp/
9
- Gemfile.lock
data/Rakefile DELETED
@@ -1,17 +0,0 @@
1
- require "bundler/gem_tasks"
2
- require "rake/testtask"
3
-
4
- Rake::TestTask.new(:test) do |t|
5
- t.libs << "test/lib"
6
- t.ruby_opts << "-rhelper"
7
- t.test_files = FileList["test/**/test_*.rb"]
8
- end
9
-
10
- task :sync_tool do
11
- require 'fileutils'
12
- FileUtils.cp "../ruby/tool/lib/core_assertions.rb", "./test/lib"
13
- FileUtils.cp "../ruby/tool/lib/envutil.rb", "./test/lib"
14
- FileUtils.cp "../ruby/tool/lib/find_executable.rb", "./test/lib"
15
- end
16
-
17
- task :default => :test
data/bin/console DELETED
@@ -1,7 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require "bundler/setup"
4
- require "timeout"
5
-
6
- require "irb"
7
- IRB.start(__FILE__)
data/bin/setup DELETED
@@ -1,6 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- set -vx
5
-
6
- bundle install