timeout 0.1.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: 6c2746096c21aa551558e072a40721124c2cecd54af3d4bc4b80905b36e0490e
4
- data.tar.gz: d6a44634d0cc4defe129bb8877364a1b04e7581e300bfb88dd73a479f915baff
3
+ metadata.gz: d65cc31f39a22fdc316cc89f8f8e4adb95131700972fca8f65cb79ab5c844858
4
+ data.tar.gz: 9cb854b05e02484a179c83f56ddb3d85b885806148fd1350223d5406cc20c706
5
5
  SHA512:
6
- metadata.gz: 4b0068d484ba661c40b51f8104823885aafe49be9ce1575439cc0d3b6788e240a17b649e085f0731120ae9ac83d3840f5db6aba73937edd90b68509c6759dd5e
7
- data.tar.gz: 465d96e3e05988a772fd446030c3fc4e91d6e658023c8468154e3118f78651200cb7d6a995c8d55a99170ce44547132d2b7d123c455375f904861103a85cc5df
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,19 +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@master
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: |
21
- gem install bundler --no-document
22
- bundle install
20
+ bundler-cache: true # 'bundle install' and cache gems
23
21
  - name: Run test
24
- run: rake test
22
+ run: bundle exec rake test
data/Rakefile CHANGED
@@ -7,4 +7,11 @@ Rake::TestTask.new(:test) do |t|
7
7
  t.test_files = FileList["test/**/test_*.rb"]
8
8
  end
9
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
+
10
17
  task :default => :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,6 +23,8 @@
23
23
  # Copyright:: (C) 2000 Information-technology Promotion Agency, Japan
24
24
 
25
25
  module Timeout
26
+ VERSION = "0.3.0"
27
+
26
28
  # Raised by Timeout.timeout when the block times out.
27
29
  class Error < RuntimeError
28
30
  attr_reader :thread
@@ -30,6 +32,7 @@ module Timeout
30
32
  def self.catch(*args)
31
33
  exc = new(*args)
32
34
  exc.instance_variable_set(:@thread, Thread.current)
35
+ exc.instance_variable_set(:@catch_value, exc)
33
36
  ::Kernel.catch(exc) {yield exc}
34
37
  end
35
38
 
@@ -38,18 +41,97 @@ module Timeout
38
41
  if self.thread == Thread.current
39
42
  bt = caller
40
43
  begin
41
- throw(self, bt)
44
+ throw(@catch_value, bt)
42
45
  rescue UncaughtThrowError
43
46
  end
44
47
  end
45
- self
48
+ super
46
49
  end
47
50
  end
48
51
 
49
52
  # :stopdoc:
50
- THIS_FILE = /\A#{Regexp.quote(__FILE__)}:/o
51
- CALLER_OFFSET = ((c = caller[0]) && THIS_FILE =~ c) ? 1 : 0
52
- 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
53
135
  # :startdoc:
54
136
 
55
137
  # Perform an operation in a block, raising an error if it takes longer than
@@ -71,62 +153,41 @@ module Timeout
71
153
  # ensure to prevent the handling of the exception. For that reason, this
72
154
  # method cannot be relied on to enforce timeouts for untrusted blocks.
73
155
  #
156
+ # If a scheduler is defined, it will be used to handle the timeout by invoking
157
+ # Scheduler#timeout_after.
158
+ #
74
159
  # Note that this is both a method of module Timeout, so you can <tt>include
75
160
  # Timeout</tt> into your classes so they have a #timeout method, as well as
76
161
  # a module method, so you can call it directly as Timeout.timeout().
77
- def timeout(sec, klass = nil, message = nil) #:yield: +sec+
162
+ def timeout(sec, klass = nil, message = nil, &block) #:yield: +sec+
78
163
  return yield(sec) if sec == nil or sec.zero?
79
- message ||= "execution expired".freeze
80
- from = "from #{caller_locations(1, 1)[0]}" if $DEBUG
81
- e = Error
82
- bl = proc do |exception|
164
+
165
+ message ||= "execution expired"
166
+
167
+ if Fiber.respond_to?(:current_scheduler) && (scheduler = Fiber.current_scheduler)&.respond_to?(:timeout_after)
168
+ return scheduler.timeout_after(sec, klass || Error, message, &block)
169
+ end
170
+
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
83
178
  begin
84
- x = Thread.current
85
- y = Thread.start {
86
- Thread.current.name = from
87
- begin
88
- sleep sec
89
- rescue => e
90
- x.raise e
91
- else
92
- x.raise exception, message
93
- end
94
- }
95
179
  return yield(sec)
96
180
  ensure
97
- if y
98
- y.kill
99
- y.join # make sure y is dead.
100
- end
181
+ request.finished
101
182
  end
102
183
  end
184
+
103
185
  if klass
104
- begin
105
- bl.call(klass)
106
- rescue klass => e
107
- bt = e.backtrace
108
- end
186
+ perform.call(klass)
109
187
  else
110
- bt = Error.catch(message, &bl)
188
+ backtrace = Error.catch(&perform)
189
+ raise Error, message, backtrace
111
190
  end
112
- level = -caller(CALLER_OFFSET).size-2
113
- while THIS_FILE =~ bt[level]
114
- bt.delete_at(level)
115
- end
116
- raise(e, message, bt)
117
191
  end
118
-
119
192
  module_function :timeout
120
193
  end
121
-
122
- def timeout(*args, &block)
123
- warn "Object##{__method__} is deprecated, use Timeout.timeout instead.", uplevel: 1
124
- Timeout.timeout(*args, &block)
125
- end
126
-
127
- # Another name for Timeout::Error, defined for backwards compatibility with
128
- # earlier versions of timeout.rb.
129
- TimeoutError = Timeout::Error
130
- class Object
131
- deprecate_constant :TimeoutError
132
- end
data/timeout.gemspec CHANGED
@@ -1,25 +1,28 @@
1
- begin
2
- require_relative "lib/timeout/version"
3
- rescue LoadError # Fallback to load version file in ruby core repository
4
- require_relative "version"
1
+ # frozen_string_literal: true
2
+
3
+ name = File.basename(__FILE__, ".gemspec")
4
+ version = ["lib", Array.new(name.count("-")+1, "..").join("/")].find do |dir|
5
+ break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb")) do |line|
6
+ /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1
7
+ end rescue nil
5
8
  end
6
9
 
7
10
  Gem::Specification.new do |spec|
8
- spec.name = "timeout"
9
- spec.version = Timeout::VERSION
11
+ spec.name = name
12
+ spec.version = version
10
13
  spec.authors = ["Yukihiro Matsumoto"]
11
14
  spec.email = ["matz@ruby-lang.org"]
12
15
 
13
16
  spec.summary = %q{Auto-terminate potentially long-running operations in Ruby.}
14
17
  spec.description = %q{Auto-terminate potentially long-running operations in Ruby.}
15
18
  spec.homepage = "https://github.com/ruby/timeout"
16
- spec.license = "BSD-2-Clause"
19
+ spec.licenses = ["Ruby", "BSD-2-Clause"]
17
20
 
18
21
  spec.metadata["homepage_uri"] = spec.homepage
19
22
  spec.metadata["source_code_uri"] = spec.homepage
20
23
 
21
24
  spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
22
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
25
+ `git ls-files -z 2>/dev/null`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
23
26
  end
24
27
  spec.bindir = "exe"
25
28
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
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.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yukihiro Matsumoto
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-04-01 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
@@ -26,15 +27,15 @@ files:
26
27
  - bin/console
27
28
  - bin/setup
28
29
  - lib/timeout.rb
29
- - lib/timeout/version.rb
30
30
  - timeout.gemspec
31
31
  homepage: https://github.com/ruby/timeout
32
32
  licenses:
33
+ - Ruby
33
34
  - BSD-2-Clause
34
35
  metadata:
35
36
  homepage_uri: https://github.com/ruby/timeout
36
37
  source_code_uri: https://github.com/ruby/timeout
37
- post_install_message:
38
+ post_install_message:
38
39
  rdoc_options: []
39
40
  require_paths:
40
41
  - lib
@@ -49,8 +50,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
49
50
  - !ruby/object:Gem::Version
50
51
  version: '0'
51
52
  requirements: []
52
- rubygems_version: 3.2.0.pre1
53
- signing_key:
53
+ rubygems_version: 3.3.7
54
+ signing_key:
54
55
  specification_version: 4
55
56
  summary: Auto-terminate potentially long-running operations in Ruby.
56
57
  test_files: []
@@ -1,3 +0,0 @@
1
- module Timeout
2
- VERSION = "0.1.0"
3
- end