timeout 0.2.0 → 0.3.2

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: 201c519af67a105d6bd6e83e6f983cb7ab41c2968be1433ab9421498a1a1cfc3
4
+ data.tar.gz: 0d9b5e39dda0333010013508b000eb95c584f5959932665b5fa7c3cb23d64112
5
5
  SHA512:
6
- metadata.gz: 07f0da54b3057e69eef0a421fb5203710e5fe1837ba3c9ec234d81f77736fc524a18c57cd93ed07c403bdd585f588b764fd77bb71c39fed0bfd6af60b1bd9038
7
- data.tar.gz: dcb89670a2e4593c64e39c6e6c1c50ef9df4d5810bab6272a82f7038a01b52efd45903489d587f40e60f609a39befe84be4b8bac333132406b14c87444f1c606
6
+ metadata.gz: 754ad7872462dcc555b91c1c589662e3e11f0bdbd2057b336e813e20743bb24e9ac74604c1dafea0159690f9cd480b57039fa878b5d523fb3662499560f99f2a
7
+ data.tar.gz: 17a3815d1a1fd685e2b24a3b41717292b9e6c5e370156d6083788fb4594179a29ec9933df106b955a1bd1cfb479097aac462b161d429b55c4cea240927f62583
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.2"
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) unless watcher.group.enclosed?
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.2
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: 2023-02-16 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.5.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