From cb37a1a06914f6c1341e5f89939b17bd0cac319a Mon Sep 17 00:00:00 2001 From: Ryunosuke Sato Date: Wed, 11 Jan 2023 10:03:14 +0900 Subject: [PATCH 01/88] Add Ruby 3.2 to CI matrix --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b8f391e..7465b8a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,7 +8,7 @@ jobs: strategy: fail-fast: false matrix: - ruby: [ '3.1', '3.0', 2.7, head, jruby, truffleruby-head ] + ruby: [ '3.2', '3.1', '3.0', '2.7', head, jruby, truffleruby-head ] os: [ ubuntu-latest, macos-latest ] runs-on: ${{ matrix.os }} steps: From eb889d2c8b68a1937a3aaa9d073376c8368d8760 Mon Sep 17 00:00:00 2001 From: Rick Blommers Date: Sun, 12 Feb 2023 15:23:37 +0100 Subject: [PATCH 02/88] Don't move the timer_thread when it's enclosed Don't move the timer_thread to ThreadGroup::Default, when it's created in an enclosed ThreadGroup. Prevents the exception: "add" can't move from the enclosed thread group" --- lib/timeout.rb | 2 +- test/test_timeout.rb | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/lib/timeout.rb b/lib/timeout.rb index 7f40baf..1d092f7 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -120,7 +120,7 @@ def self.create_timeout_thread requests.reject!(&:done?) end end - ThreadGroup::Default.add(watcher) + ThreadGroup::Default.add(watcher) unless watcher.group.enclosed? watcher.name = "Timeout stdlib thread" watcher.thread_variable_set(:"\0__detached_thread__", true) watcher diff --git a/test/test_timeout.rb b/test/test_timeout.rb index 2d3dd16..89fb10a 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -172,4 +172,23 @@ def test_threadgroup end; end + def test_handling_enclosed_threadgroup + # The problem "add: can't move from the enclosed thread group" #24, + # happens when the timeout_thread is created in an enclosed ThreadGroup. + assert_separately(%w[-rtimeout], <<-'end;') + t1 = Thread.new { + Thread.stop + assert_block do + Timeout.timeout(0.1) {} + true + end + } + sleep 0.1 while t1.status != 'sleep' + group = ThreadGroup.new + group.add(t1) + group.enclose + t1.run + t1.join + end; + end end From db017da726463ae7968b96414dc968d073773961 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Wed, 15 Feb 2023 19:26:13 +0100 Subject: [PATCH 03/88] Simplify test --- test/test_timeout.rb | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/test/test_timeout.rb b/test/test_timeout.rb index 89fb10a..c3349d0 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -172,23 +172,17 @@ def test_threadgroup end; end + # https://github.com/ruby/timeout/issues/24 def test_handling_enclosed_threadgroup - # The problem "add: can't move from the enclosed thread group" #24, - # happens when the timeout_thread is created in an enclosed ThreadGroup. assert_separately(%w[-rtimeout], <<-'end;') - t1 = Thread.new { - Thread.stop - assert_block do - Timeout.timeout(0.1) {} - true - end - } - sleep 0.1 while t1.status != 'sleep' - group = ThreadGroup.new - group.add(t1) - group.enclose - t1.run - t1.join + Thread.new { + t = Thread.current + group = ThreadGroup.new + group.add(t) + group.enclose + + assert_equal 42, Timeout.timeout(1) { 42 } + }.join end; end end From 097e895b7b5ffccba51f76e90ab8cdde42838351 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Wed, 15 Feb 2023 19:47:49 +0100 Subject: [PATCH 04/88] Test the truffleruby release too --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7465b8a..0071eb1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,7 +8,7 @@ jobs: strategy: fail-fast: false matrix: - ruby: [ '3.2', '3.1', '3.0', '2.7', head, jruby, truffleruby-head ] + ruby: [ '3.2', '3.1', '3.0', '2.7', head, jruby, truffleruby, truffleruby-head ] os: [ ubuntu-latest, macos-latest ] runs-on: ${{ matrix.os }} steps: From d7dbcbbc2c6b1e58741a9540127181d0e91f592f Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 16 Feb 2023 09:36:31 +0900 Subject: [PATCH 05/88] rename appropriate job name --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0071eb1..ffe55fa 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,7 +3,7 @@ name: test on: [push, pull_request] jobs: - build: + test: name: build (${{ matrix.ruby }} / ${{ matrix.os }}) strategy: fail-fast: false From a7e1d2afc7417e8e0e6b51e0795bf2dda90222d7 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 16 Feb 2023 09:37:47 +0900 Subject: [PATCH 06/88] Use ruby/actions/.github/workflows/ruby_versions.yml@master --- .github/workflows/test.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ffe55fa..ce1a771 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,12 +3,16 @@ name: test on: [push, pull_request] jobs: + ruby-versions: + uses: ruby/actions/.github/workflows/ruby_versions.yml@master + test: + needs: ruby-versions name: build (${{ matrix.ruby }} / ${{ matrix.os }}) strategy: fail-fast: false matrix: - ruby: [ '3.2', '3.1', '3.0', '2.7', head, jruby, truffleruby, truffleruby-head ] + ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} os: [ ubuntu-latest, macos-latest ] runs-on: ${{ matrix.os }} steps: From f953e9611144894ea7f6b1fc5daf7116f4bfc798 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 16 Feb 2023 09:41:38 +0900 Subject: [PATCH 07/88] exclude failing platforms: jruby(-head) and truffleruby with macos-latest --- .github/workflows/test.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ce1a771..93ccb2e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,6 +5,8 @@ on: [push, pull_request] jobs: ruby-versions: uses: ruby/actions/.github/workflows/ruby_versions.yml@master + with: + engine: cruby-truffleruby test: needs: ruby-versions @@ -14,6 +16,9 @@ jobs: matrix: ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} os: [ ubuntu-latest, macos-latest ] + exclude: + - os: macos-latest + ruby: truffleruby runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 From e1b244810138253d93ea3ac0932c8dbdd8ef8dc6 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 16 Feb 2023 09:49:21 +0900 Subject: [PATCH 08/88] bump up 0.3.2 --- lib/timeout.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/timeout.rb b/lib/timeout.rb index 1d092f7..e110776 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -23,7 +23,7 @@ # Copyright:: (C) 2000 Information-technology Promotion Agency, Japan module Timeout - VERSION = "0.3.1" + VERSION = "0.3.2" # Raised by Timeout.timeout when the block times out. class Error < RuntimeError From 1e31a9a35828f0065d76a26bc3f36929025459ef Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 24 Mar 2023 12:51:53 +0900 Subject: [PATCH 09/88] Update test libraries from https://github.com/ruby/ruby/commit/b4e438d8aabaf4bba2b27f374c787543fae07c58 --- test/lib/core_assertions.rb | 152 +++++++++++++++++++++--------------- test/lib/envutil.rb | 25 ++++-- 2 files changed, 109 insertions(+), 68 deletions(-) diff --git a/test/lib/core_assertions.rb b/test/lib/core_assertions.rb index bac3856..1fdc0a3 100644 --- a/test/lib/core_assertions.rb +++ b/test/lib/core_assertions.rb @@ -3,6 +3,10 @@ module Test module Unit module Assertions + def assert_raises(*exp, &b) + raise NoMethodError, "use assert_raise", caller + end + def _assertions= n # :nodoc: @_assertions = n end @@ -16,9 +20,16 @@ def _assertions # :nodoc: def message msg = nil, ending = nil, &default proc { - msg = msg.call.chomp(".") if Proc === msg - custom_message = "#{msg}.\n" unless msg.nil? or msg.to_s.empty? - "#{custom_message}#{default.call}#{ending || "."}" + ending ||= (ending_pattern = /(? e - bt = e.backtrace - as = e.instance_of?(Test::Unit::AssertionFailedError) - if as - ans = /\A#{Regexp.quote(__FILE__)}:#{line}:in /o - bt.reject! {|ln| ans =~ ln} - end - if ((args.empty? && !as) || - args.any? {|a| a.instance_of?(Module) ? e.is_a?(a) : e.class == a }) - msg = message(msg) { - "Exception raised:\n<#{mu_pp(e)}>\n" + - "Backtrace:\n" + - e.backtrace.map{|frame| " #{frame}"}.join("\n") - } - raise Test::Unit::AssertionFailedError, msg.call, bt - else - raise - end + rescue *(args.empty? ? Exception : args) => e + msg = message(msg) { + "Exception raised:\n<#{mu_pp(e)}>\n""Backtrace:\n" << + Test.filter_backtrace(e.backtrace).map{|frame| " #{frame}"}.join("\n") + } + raise Test::Unit::AssertionFailedError, msg.call, e.backtrace end end @@ -244,13 +244,17 @@ def assert_ruby_status(args, test_stdin="", message=nil, **opt) ABORT_SIGNALS = Signal.list.values_at(*%w"ILL ABRT BUS SEGV TERM") - def separated_runner(out = nil) + def separated_runner(token, out = nil) include(*Test::Unit::TestCase.ancestors.select {|c| !c.is_a?(Class) }) out = out ? IO.new(out, 'w') : STDOUT at_exit { - out.puts [Marshal.dump($!)].pack('m'), "assertions=#{self._assertions}" + out.puts "#{token}", [Marshal.dump($!)].pack('m'), "#{token}", "#{token}assertions=#{self._assertions}" } - Test::Unit::Runner.class_variable_set(:@@stop_auto_run, true) if defined?(Test::Unit::Runner) + if defined?(Test::Unit::Runner) + Test::Unit::Runner.class_variable_set(:@@stop_auto_run, true) + elsif defined?(Test::Unit::AutoRunner) + Test::Unit::AutoRunner.need_auto_run = false + end end def assert_separately(args, file = nil, line = nil, src, ignore_stderr: nil, **opt) @@ -260,22 +264,24 @@ def assert_separately(args, file = nil, line = nil, src, ignore_stderr: nil, **o line ||= loc.lineno end capture_stdout = true - unless /mswin|mingw/ =~ RUBY_PLATFORM + unless /mswin|mingw/ =~ RbConfig::CONFIG['host_os'] capture_stdout = false opt[:out] = Test::Unit::Runner.output if defined?(Test::Unit::Runner) res_p, res_c = IO.pipe opt[:ios] = [res_c] end + token_dump, token_re = new_test_token src = <\n\K.*\n(?=#{token_re}<\/error>$)/m].unpack1("m")) rescue => marshal_error ignore_stderr = nil res = nil @@ -463,7 +469,7 @@ def assert_raise_with_message(exception, expected, msg = nil, &block) ex end - MINI_DIR = File.join(File.dirname(File.expand_path(__FILE__)), "minitest") #:nodoc: + TEST_DIR = File.join(__dir__, "test/unit") #:nodoc: # :call-seq: # assert(test, [failure_message]) @@ -483,7 +489,7 @@ def assert(test, *msgs) when nil msgs.shift else - bt = caller.reject { |s| s.start_with?(MINI_DIR) } + bt = caller.reject { |s| s.start_with?(TEST_DIR) } raise ArgumentError, "assertion message must be String or Proc, but #{msg.class} was given.", bt end unless msgs.empty? super @@ -506,7 +512,7 @@ def assert_respond_to(obj, (meth, *priv), msg = nil) return assert obj.respond_to?(meth, *priv), msg end #get rid of overcounting - if caller_locations(1, 1)[0].path.start_with?(MINI_DIR) + if caller_locations(1, 1)[0].path.start_with?(TEST_DIR) return if obj.respond_to?(meth) end super(obj, meth, msg) @@ -529,17 +535,17 @@ def assert_not_respond_to(obj, (meth, *priv), msg = nil) return assert !obj.respond_to?(meth, *priv), msg end #get rid of overcounting - if caller_locations(1, 1)[0].path.start_with?(MINI_DIR) + if caller_locations(1, 1)[0].path.start_with?(TEST_DIR) return unless obj.respond_to?(meth) end refute_respond_to(obj, meth, msg) end - # pattern_list is an array which contains regexp and :*. + # pattern_list is an array which contains regexp, string and :*. # :* means any sequence. # # pattern_list is anchored. - # Use [:*, regexp, :*] for non-anchored match. + # Use [:*, regexp/string, :*] for non-anchored match. def assert_pattern_list(pattern_list, actual, message=nil) rest = actual anchored = true @@ -548,11 +554,13 @@ def assert_pattern_list(pattern_list, actual, message=nil) anchored = false else if anchored - match = /\A#{pattern}/.match(rest) + match = rest.rindex(pattern, 0) else - match = pattern.match(rest) + match = rest.index(pattern) end - unless match + if match + post_match = $~ ? $~.post_match : rest[match+pattern.size..-1] + else msg = message(msg) { expect_msg = "Expected #{mu_pp pattern}\n" if /\n[^\n]/ =~ rest @@ -569,7 +577,7 @@ def assert_pattern_list(pattern_list, actual, message=nil) } assert false, msg end - rest = match.post_match + rest = post_match anchored = true end } @@ -596,14 +604,14 @@ def assert_warn(*args) def assert_deprecated_warning(mesg = /deprecated/) assert_warning(mesg) do - Warning[:deprecated] = true + Warning[:deprecated] = true if Warning.respond_to?(:[]=) yield end end def assert_deprecated_warn(mesg = /deprecated/) assert_warn(mesg) do - Warning[:deprecated] = true + Warning[:deprecated] = true if Warning.respond_to?(:[]=) yield end end @@ -641,7 +649,7 @@ def initialize def for(key) @count += 1 - yield + yield key rescue Exception => e @failures[key] = [@count, e] end @@ -695,7 +703,7 @@ def assert_join_threads(threads, message = nil) msg = "exceptions on #{errs.length} threads:\n" + errs.map {|t, err| "#{t.inspect}:\n" + - RUBY_VERSION >= "2.5.0" ? err.full_message(highlight: false, order: :top) : err.message + (err.respond_to?(:full_message) ? err.full_message(highlight: false, order: :top) : err.message) }.join("\n---\n") if message msg = "#{message}\n#{msg}" @@ -730,21 +738,36 @@ def assert_all_assertions_foreach(msg = nil, *keys, &block) end alias all_assertions_foreach assert_all_assertions_foreach - def message(msg = nil, *args, &default) # :nodoc: - if Proc === msg - super(nil, *args) do - ary = [msg.call, (default.call if default)].compact.reject(&:empty?) - if 1 < ary.length - ary[0...-1] = ary[0...-1].map {|str| str.sub(/(?(n) {n}) + first = seq.first + *arg = pre.call(first) + times = (0..(rehearsal || (2 * first))).map do + st = Process.clock_gettime(Process::CLOCK_MONOTONIC) + yield(*arg) + t = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - st) + assert_operator 0, :<=, t + t.nonzero? + end + times.compact! + tmin, tmax = times.minmax + tmax *= tmax / tmin + tmax = 10**Math.log10(tmax).ceil + + seq.each do |i| + next if i == first + t = tmax * i.fdiv(first) + *arg = pre.call(i) + message = "[#{i}]: in #{t}s" + Timeout.timeout(t, Timeout::Error, message) do + st = Process.clock_gettime(Process::CLOCK_MONOTONIC) + yield(*arg) + assert_operator (Process.clock_gettime(Process::CLOCK_MONOTONIC) - st), :<=, t, message end - else - super end end @@ -763,6 +786,11 @@ def diff(exp, act) end q.output end + + def new_test_token + token = "\e[7;1m#{$$.to_s}:#{Time.now.strftime('%s.%L')}:#{rand(0x10000).to_s(16)}:\e[m" + return token.dump, Regexp.quote(token) + end end end end diff --git a/test/lib/envutil.rb b/test/lib/envutil.rb index 0391b90..728ca70 100644 --- a/test/lib/envutil.rb +++ b/test/lib/envutil.rb @@ -152,7 +152,12 @@ def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr = if RUBYLIB and lib = child_env["RUBYLIB"] child_env["RUBYLIB"] = [lib, RUBYLIB].join(File::PATH_SEPARATOR) end - child_env['ASAN_OPTIONS'] = ENV['ASAN_OPTIONS'] if ENV['ASAN_OPTIONS'] + + # remain env + %w(ASAN_OPTIONS RUBY_ON_BUG).each{|name| + child_env[name] = ENV[name] if ENV[name] + } + args = [args] if args.kind_of?(String) pid = spawn(child_env, *precommand, rubybin, *args, opt) in_c.close @@ -292,16 +297,24 @@ def self.diagnostic_reports(signame, pid, now) cmd = @ruby_install_name if "ruby-runner#{RbConfig::CONFIG["EXEEXT"]}" == cmd path = DIAGNOSTIC_REPORTS_PATH timeformat = DIAGNOSTIC_REPORTS_TIMEFORMAT - pat = "#{path}/#{cmd}_#{now.strftime(timeformat)}[-_]*.crash" + pat = "#{path}/#{cmd}_#{now.strftime(timeformat)}[-_]*.{crash,ips}" first = true 30.times do first ? (first = false) : sleep(0.1) Dir.glob(pat) do |name| log = File.read(name) rescue next - if /\AProcess:\s+#{cmd} \[#{pid}\]$/ =~ log - File.unlink(name) - File.unlink("#{path}/.#{File.basename(name)}.plist") rescue nil - return log + case name + when /\.crash\z/ + if /\AProcess:\s+#{cmd} \[#{pid}\]$/ =~ log + File.unlink(name) + File.unlink("#{path}/.#{File.basename(name)}.plist") rescue nil + return log + end + when /\.ips\z/ + if /^ *"pid" *: *#{pid},/ =~ log + File.unlink(name) + return log + end end end end From 1c91ac24227bb927ce4b341c6184c989a741cf98 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 13 Jun 2023 09:49:41 +0900 Subject: [PATCH 10/88] Use released version of test-unit-ruby-core --- Gemfile | 1 + Rakefile | 7 - test/lib/core_assertions.rb | 796 ------------------------------------ test/lib/envutil.rb | 380 ----------------- test/lib/find_executable.rb | 22 - test/lib/helper.rb | 2 +- 6 files changed, 2 insertions(+), 1206 deletions(-) delete mode 100644 test/lib/core_assertions.rb delete mode 100644 test/lib/envutil.rb delete mode 100644 test/lib/find_executable.rb diff --git a/Gemfile b/Gemfile index 3dc2883..ba909e8 100644 --- a/Gemfile +++ b/Gemfile @@ -6,4 +6,5 @@ group :development do gem "bundler" gem "rake" gem "test-unit" + gem "test-unit-ruby-core" end diff --git a/Rakefile b/Rakefile index 5a7afab..5d512c8 100644 --- a/Rakefile +++ b/Rakefile @@ -7,11 +7,4 @@ Rake::TestTask.new(:test) do |t| t.test_files = FileList["test/**/test_*.rb"] end -task :sync_tool do - require 'fileutils' - FileUtils.cp "../ruby/tool/lib/core_assertions.rb", "./test/lib" - FileUtils.cp "../ruby/tool/lib/envutil.rb", "./test/lib" - FileUtils.cp "../ruby/tool/lib/find_executable.rb", "./test/lib" -end - task :default => :test diff --git a/test/lib/core_assertions.rb b/test/lib/core_assertions.rb deleted file mode 100644 index 1fdc0a3..0000000 --- a/test/lib/core_assertions.rb +++ /dev/null @@ -1,796 +0,0 @@ -# frozen_string_literal: true - -module Test - module Unit - module Assertions - def assert_raises(*exp, &b) - raise NoMethodError, "use assert_raise", caller - end - - def _assertions= n # :nodoc: - @_assertions = n - end - - def _assertions # :nodoc: - @_assertions ||= 0 - end - - ## - # Returns a proc that will output +msg+ along with the default message. - - def message msg = nil, ending = nil, &default - proc { - ending ||= (ending_pattern = /(? 0 and b > 0 - assert_operator(a.fdiv(b), :<, limit, message(message) {"#{n}: #{b} => #{a}"}) - end - rescue LoadError - pend - end - - # :call-seq: - # assert_nothing_raised( *args, &block ) - # - #If any exceptions are given as arguments, the assertion will - #fail if one of those exceptions are raised. Otherwise, the test fails - #if any exceptions are raised. - # - #The final argument may be a failure message. - # - # assert_nothing_raised RuntimeError do - # raise Exception #Assertion passes, Exception is not a RuntimeError - # end - # - # assert_nothing_raised do - # raise Exception #Assertion fails - # end - def assert_nothing_raised(*args) - self._assertions += 1 - if Module === args.last - msg = nil - else - msg = args.pop - end - begin - yield - rescue Test::Unit::PendedError, *(Test::Unit::AssertionFailedError if args.empty?) - raise - rescue *(args.empty? ? Exception : args) => e - msg = message(msg) { - "Exception raised:\n<#{mu_pp(e)}>\n""Backtrace:\n" << - Test.filter_backtrace(e.backtrace).map{|frame| " #{frame}"}.join("\n") - } - raise Test::Unit::AssertionFailedError, msg.call, e.backtrace - end - end - - def prepare_syntax_check(code, fname = nil, mesg = nil, verbose: nil) - fname ||= caller_locations(2, 1)[0] - mesg ||= fname.to_s - verbose, $VERBOSE = $VERBOSE, verbose - case - when Array === fname - fname, line = *fname - when defined?(fname.path) && defined?(fname.lineno) - fname, line = fname.path, fname.lineno - else - line = 1 - end - yield(code, fname, line, message(mesg) { - if code.end_with?("\n") - "```\n#{code}```\n" - else - "```\n#{code}\n```\n""no-newline" - end - }) - ensure - $VERBOSE = verbose - end - - def assert_valid_syntax(code, *args, **opt) - prepare_syntax_check(code, *args, **opt) do |src, fname, line, mesg| - yield if defined?(yield) - assert_nothing_raised(SyntaxError, mesg) do - assert_equal(:ok, syntax_check(src, fname, line), mesg) - end - end - end - - def assert_normal_exit(testsrc, message = '', child_env: nil, **opt) - assert_valid_syntax(testsrc, caller_locations(1, 1)[0]) - if child_env - child_env = [child_env] - else - child_env = [] - end - out, _, status = EnvUtil.invoke_ruby(child_env + %W'-W0', testsrc, true, :merge_to_stdout, **opt) - assert !status.signaled?, FailDesc[status, message, out] - end - - def assert_ruby_status(args, test_stdin="", message=nil, **opt) - out, _, status = EnvUtil.invoke_ruby(args, test_stdin, true, :merge_to_stdout, **opt) - desc = FailDesc[status, message, out] - assert(!status.signaled?, desc) - message ||= "ruby exit status is not success:" - assert(status.success?, desc) - end - - ABORT_SIGNALS = Signal.list.values_at(*%w"ILL ABRT BUS SEGV TERM") - - def separated_runner(token, out = nil) - include(*Test::Unit::TestCase.ancestors.select {|c| !c.is_a?(Class) }) - out = out ? IO.new(out, 'w') : STDOUT - at_exit { - out.puts "#{token}", [Marshal.dump($!)].pack('m'), "#{token}", "#{token}assertions=#{self._assertions}" - } - if defined?(Test::Unit::Runner) - Test::Unit::Runner.class_variable_set(:@@stop_auto_run, true) - elsif defined?(Test::Unit::AutoRunner) - Test::Unit::AutoRunner.need_auto_run = false - end - end - - def assert_separately(args, file = nil, line = nil, src, ignore_stderr: nil, **opt) - unless file and line - loc, = caller_locations(1,1) - file ||= loc.path - line ||= loc.lineno - end - capture_stdout = true - unless /mswin|mingw/ =~ RbConfig::CONFIG['host_os'] - capture_stdout = false - opt[:out] = Test::Unit::Runner.output if defined?(Test::Unit::Runner) - res_p, res_c = IO.pipe - opt[:ios] = [res_c] - end - token_dump, token_re = new_test_token - src = <\n\K.*\n(?=#{token_re}<\/error>$)/m].unpack1("m")) - rescue => marshal_error - ignore_stderr = nil - res = nil - end - if res and !(SystemExit === res) - if bt = res.backtrace - bt.each do |l| - l.sub!(/\A-:(\d+)/){"#{file}:#{line + $1.to_i}"} - end - bt.concat(caller) - else - res.set_backtrace(caller) - end - raise res - end - - # really is it succeed? - unless ignore_stderr - # the body of assert_separately must not output anything to detect error - assert(stderr.empty?, FailDesc[status, "assert_separately failed with error message", stderr]) - end - assert(status.success?, FailDesc[status, "assert_separately failed", stderr]) - raise marshal_error if marshal_error - end - - # Run Ractor-related test without influencing the main test suite - def assert_ractor(src, args: [], require: nil, require_relative: nil, file: nil, line: nil, ignore_stderr: nil, **opt) - return unless defined?(Ractor) - - require = "require #{require.inspect}" if require - if require_relative - dir = File.dirname(caller_locations[0,1][0].absolute_path) - full_path = File.expand_path(require_relative, dir) - require = "#{require}; require #{full_path.inspect}" - end - - assert_separately(args, file, line, <<~RUBY, ignore_stderr: ignore_stderr, **opt) - #{require} - previous_verbose = $VERBOSE - $VERBOSE = nil - Ractor.new {} # trigger initial warning - $VERBOSE = previous_verbose - #{src} - RUBY - end - - # :call-seq: - # assert_throw( tag, failure_message = nil, &block ) - # - #Fails unless the given block throws +tag+, returns the caught - #value otherwise. - # - #An optional failure message may be provided as the final argument. - # - # tag = Object.new - # assert_throw(tag, "#{tag} was not thrown!") do - # throw tag - # end - def assert_throw(tag, msg = nil) - ret = catch(tag) do - begin - yield(tag) - rescue UncaughtThrowError => e - thrown = e.tag - end - msg = message(msg) { - "Expected #{mu_pp(tag)} to have been thrown"\ - "#{%Q[, not #{thrown}] if thrown}" - } - assert(false, msg) - end - assert(true) - ret - end - - # :call-seq: - # assert_raise( *args, &block ) - # - #Tests if the given block raises an exception. Acceptable exception - #types may be given as optional arguments. If the last argument is a - #String, it will be used as the error message. - # - # assert_raise do #Fails, no Exceptions are raised - # end - # - # assert_raise NameError do - # puts x #Raises NameError, so assertion succeeds - # end - def assert_raise(*exp, &b) - case exp.last - when String, Proc - msg = exp.pop - end - - begin - yield - rescue Test::Unit::PendedError => e - return e if exp.include? Test::Unit::PendedError - raise e - rescue Exception => e - expected = exp.any? { |ex| - if ex.instance_of? Module then - e.kind_of? ex - else - e.instance_of? ex - end - } - - assert expected, proc { - flunk(message(msg) {"#{mu_pp(exp)} exception expected, not #{mu_pp(e)}"}) - } - - return e - ensure - unless e - exp = exp.first if exp.size == 1 - - flunk(message(msg) {"#{mu_pp(exp)} expected but nothing was raised"}) - end - end - end - - # :call-seq: - # assert_raise_with_message(exception, expected, msg = nil, &block) - # - #Tests if the given block raises an exception with the expected - #message. - # - # assert_raise_with_message(RuntimeError, "foo") do - # nil #Fails, no Exceptions are raised - # end - # - # assert_raise_with_message(RuntimeError, "foo") do - # raise ArgumentError, "foo" #Fails, different Exception is raised - # end - # - # assert_raise_with_message(RuntimeError, "foo") do - # raise "bar" #Fails, RuntimeError is raised but the message differs - # end - # - # assert_raise_with_message(RuntimeError, "foo") do - # raise "foo" #Raises RuntimeError with the message, so assertion succeeds - # end - def assert_raise_with_message(exception, expected, msg = nil, &block) - case expected - when String - assert = :assert_equal - when Regexp - assert = :assert_match - else - raise TypeError, "Expected #{expected.inspect} to be a kind of String or Regexp, not #{expected.class}" - end - - ex = m = nil - EnvUtil.with_default_internal(expected.encoding) do - ex = assert_raise(exception, msg || proc {"Exception(#{exception}) with message matches to #{expected.inspect}"}) do - yield - end - m = ex.message - end - msg = message(msg, "") {"Expected Exception(#{exception}) was raised, but the message doesn't match"} - - if assert == :assert_equal - assert_equal(expected, m, msg) - else - msg = message(msg) { "Expected #{mu_pp expected} to match #{mu_pp m}" } - assert expected =~ m, msg - block.binding.eval("proc{|_|$~=_}").call($~) - end - ex - end - - TEST_DIR = File.join(__dir__, "test/unit") #:nodoc: - - # :call-seq: - # assert(test, [failure_message]) - # - #Tests if +test+ is true. - # - #+msg+ may be a String or a Proc. If +msg+ is a String, it will be used - #as the failure message. Otherwise, the result of calling +msg+ will be - #used as the message if the assertion fails. - # - #If no +msg+ is given, a default message will be used. - # - # assert(false, "This was expected to be true") - def assert(test, *msgs) - case msg = msgs.first - when String, Proc - when nil - msgs.shift - else - bt = caller.reject { |s| s.start_with?(TEST_DIR) } - raise ArgumentError, "assertion message must be String or Proc, but #{msg.class} was given.", bt - end unless msgs.empty? - super - end - - # :call-seq: - # assert_respond_to( object, method, failure_message = nil ) - # - #Tests if the given Object responds to +method+. - # - #An optional failure message may be provided as the final argument. - # - # assert_respond_to("hello", :reverse) #Succeeds - # assert_respond_to("hello", :does_not_exist) #Fails - def assert_respond_to(obj, (meth, *priv), msg = nil) - unless priv.empty? - msg = message(msg) { - "Expected #{mu_pp(obj)} (#{obj.class}) to respond to ##{meth}#{" privately" if priv[0]}" - } - return assert obj.respond_to?(meth, *priv), msg - end - #get rid of overcounting - if caller_locations(1, 1)[0].path.start_with?(TEST_DIR) - return if obj.respond_to?(meth) - end - super(obj, meth, msg) - end - - # :call-seq: - # assert_not_respond_to( object, method, failure_message = nil ) - # - #Tests if the given Object does not respond to +method+. - # - #An optional failure message may be provided as the final argument. - # - # assert_not_respond_to("hello", :reverse) #Fails - # assert_not_respond_to("hello", :does_not_exist) #Succeeds - def assert_not_respond_to(obj, (meth, *priv), msg = nil) - unless priv.empty? - msg = message(msg) { - "Expected #{mu_pp(obj)} (#{obj.class}) to not respond to ##{meth}#{" privately" if priv[0]}" - } - return assert !obj.respond_to?(meth, *priv), msg - end - #get rid of overcounting - if caller_locations(1, 1)[0].path.start_with?(TEST_DIR) - return unless obj.respond_to?(meth) - end - refute_respond_to(obj, meth, msg) - end - - # pattern_list is an array which contains regexp, string and :*. - # :* means any sequence. - # - # pattern_list is anchored. - # Use [:*, regexp/string, :*] for non-anchored match. - def assert_pattern_list(pattern_list, actual, message=nil) - rest = actual - anchored = true - pattern_list.each_with_index {|pattern, i| - if pattern == :* - anchored = false - else - if anchored - match = rest.rindex(pattern, 0) - else - match = rest.index(pattern) - end - if match - post_match = $~ ? $~.post_match : rest[match+pattern.size..-1] - else - msg = message(msg) { - expect_msg = "Expected #{mu_pp pattern}\n" - if /\n[^\n]/ =~ rest - actual_mesg = +"to match\n" - rest.scan(/.*\n+/) { - actual_mesg << ' ' << $&.inspect << "+\n" - } - actual_mesg.sub!(/\+\n\z/, '') - else - actual_mesg = "to match " + mu_pp(rest) - end - actual_mesg << "\nafter #{i} patterns with #{actual.length - rest.length} characters" - expect_msg + actual_mesg - } - assert false, msg - end - rest = post_match - anchored = true - end - } - if anchored - assert_equal("", rest) - end - end - - def assert_warning(pat, msg = nil) - result = nil - stderr = EnvUtil.with_default_internal(pat.encoding) { - EnvUtil.verbose_warning { - result = yield - } - } - msg = message(msg) {diff pat, stderr} - assert(pat === stderr, msg) - result - end - - def assert_warn(*args) - assert_warning(*args) {$VERBOSE = false; yield} - end - - def assert_deprecated_warning(mesg = /deprecated/) - assert_warning(mesg) do - Warning[:deprecated] = true if Warning.respond_to?(:[]=) - yield - end - end - - def assert_deprecated_warn(mesg = /deprecated/) - assert_warn(mesg) do - Warning[:deprecated] = true if Warning.respond_to?(:[]=) - yield - end - end - - class << (AssertFile = Struct.new(:failure_message).new) - include Assertions - include CoreAssertions - def assert_file_predicate(predicate, *args) - if /\Anot_/ =~ predicate - predicate = $' - neg = " not" - end - result = File.__send__(predicate, *args) - result = !result if neg - mesg = "Expected file ".dup << args.shift.inspect - mesg << "#{neg} to be #{predicate}" - mesg << mu_pp(args).sub(/\A\[(.*)\]\z/m, '(\1)') unless args.empty? - mesg << " #{failure_message}" if failure_message - assert(result, mesg) - end - alias method_missing assert_file_predicate - - def for(message) - clone.tap {|a| a.failure_message = message} - end - end - - class AllFailures - attr_reader :failures - - def initialize - @count = 0 - @failures = {} - end - - def for(key) - @count += 1 - yield key - rescue Exception => e - @failures[key] = [@count, e] - end - - def foreach(*keys) - keys.each do |key| - @count += 1 - begin - yield key - rescue Exception => e - @failures[key] = [@count, e] - end - end - end - - def message - i = 0 - total = @count.to_s - fmt = "%#{total.size}d" - @failures.map {|k, (n, v)| - v = v.message - "\n#{i+=1}. [#{fmt%n}/#{total}] Assertion for #{k.inspect}\n#{v.b.gsub(/^/, ' | ').force_encoding(v.encoding)}" - }.join("\n") - end - - def pass? - @failures.empty? - end - end - - # threads should respond to shift method. - # Array can be used. - def assert_join_threads(threads, message = nil) - errs = [] - values = [] - while th = threads.shift - begin - values << th.value - rescue Exception - errs << [th, $!] - th = nil - end - end - values - ensure - if th&.alive? - th.raise(Timeout::Error.new) - th.join rescue errs << [th, $!] - end - if !errs.empty? - msg = "exceptions on #{errs.length} threads:\n" + - errs.map {|t, err| - "#{t.inspect}:\n" + - (err.respond_to?(:full_message) ? err.full_message(highlight: false, order: :top) : err.message) - }.join("\n---\n") - if message - msg = "#{message}\n#{msg}" - end - raise Test::Unit::AssertionFailedError, msg - end - end - - def assert_all?(obj, m = nil, &blk) - failed = [] - obj.each do |*a, &b| - unless blk.call(*a, &b) - failed << (a.size > 1 ? a : a[0]) - end - end - assert(failed.empty?, message(m) {failed.pretty_inspect}) - end - - def assert_all_assertions(msg = nil) - all = AllFailures.new - yield all - ensure - assert(all.pass?, message(msg) {all.message.chomp(".")}) - end - alias all_assertions assert_all_assertions - - def assert_all_assertions_foreach(msg = nil, *keys, &block) - all = AllFailures.new - all.foreach(*keys, &block) - ensure - assert(all.pass?, message(msg) {all.message.chomp(".")}) - end - alias all_assertions_foreach assert_all_assertions_foreach - - # Expect +seq+ to respond to +first+ and +each+ methods, e.g., - # Array, Range, Enumerator::ArithmeticSequence and other - # Enumerable-s, and each elements should be size factors. - # - # :yield: each elements of +seq+. - def assert_linear_performance(seq, rehearsal: nil, pre: ->(n) {n}) - first = seq.first - *arg = pre.call(first) - times = (0..(rehearsal || (2 * first))).map do - st = Process.clock_gettime(Process::CLOCK_MONOTONIC) - yield(*arg) - t = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - st) - assert_operator 0, :<=, t - t.nonzero? - end - times.compact! - tmin, tmax = times.minmax - tmax *= tmax / tmin - tmax = 10**Math.log10(tmax).ceil - - seq.each do |i| - next if i == first - t = tmax * i.fdiv(first) - *arg = pre.call(i) - message = "[#{i}]: in #{t}s" - Timeout.timeout(t, Timeout::Error, message) do - st = Process.clock_gettime(Process::CLOCK_MONOTONIC) - yield(*arg) - assert_operator (Process.clock_gettime(Process::CLOCK_MONOTONIC) - st), :<=, t, message - end - end - end - - def diff(exp, act) - require 'pp' - q = PP.new(+"") - q.guard_inspect_key do - q.group(2, "expected: ") do - q.pp exp - end - q.text q.newline - q.group(2, "actual: ") do - q.pp act - end - q.flush - end - q.output - end - - def new_test_token - token = "\e[7;1m#{$$.to_s}:#{Time.now.strftime('%s.%L')}:#{rand(0x10000).to_s(16)}:\e[m" - return token.dump, Regexp.quote(token) - end - end - end -end diff --git a/test/lib/envutil.rb b/test/lib/envutil.rb deleted file mode 100644 index 728ca70..0000000 --- a/test/lib/envutil.rb +++ /dev/null @@ -1,380 +0,0 @@ -# -*- coding: us-ascii -*- -# frozen_string_literal: true -require "open3" -require "timeout" -require_relative "find_executable" -begin - require 'rbconfig' -rescue LoadError -end -begin - require "rbconfig/sizeof" -rescue LoadError -end - -module EnvUtil - def rubybin - if ruby = ENV["RUBY"] - return ruby - end - ruby = "ruby" - exeext = RbConfig::CONFIG["EXEEXT"] - rubyexe = (ruby + exeext if exeext and !exeext.empty?) - 3.times do - if File.exist? ruby and File.executable? ruby and !File.directory? ruby - return File.expand_path(ruby) - end - if rubyexe and File.exist? rubyexe and File.executable? rubyexe - return File.expand_path(rubyexe) - end - ruby = File.join("..", ruby) - end - if defined?(RbConfig.ruby) - RbConfig.ruby - else - "ruby" - end - end - module_function :rubybin - - LANG_ENVS = %w"LANG LC_ALL LC_CTYPE" - - DEFAULT_SIGNALS = Signal.list - DEFAULT_SIGNALS.delete("TERM") if /mswin|mingw/ =~ RUBY_PLATFORM - - RUBYLIB = ENV["RUBYLIB"] - - class << self - attr_accessor :timeout_scale - attr_reader :original_internal_encoding, :original_external_encoding, - :original_verbose, :original_warning - - def capture_global_values - @original_internal_encoding = Encoding.default_internal - @original_external_encoding = Encoding.default_external - @original_verbose = $VERBOSE - @original_warning = defined?(Warning.[]) ? %i[deprecated experimental].to_h {|i| [i, Warning[i]]} : nil - end - end - - def apply_timeout_scale(t) - if scale = EnvUtil.timeout_scale - t * scale - else - t - end - end - module_function :apply_timeout_scale - - def timeout(sec, klass = nil, message = nil, &blk) - return yield(sec) if sec == nil or sec.zero? - sec = apply_timeout_scale(sec) - Timeout.timeout(sec, klass, message, &blk) - end - module_function :timeout - - def terminate(pid, signal = :TERM, pgroup = nil, reprieve = 1) - reprieve = apply_timeout_scale(reprieve) if reprieve - - signals = Array(signal).select do |sig| - DEFAULT_SIGNALS[sig.to_s] or - DEFAULT_SIGNALS[Signal.signame(sig)] rescue false - end - signals |= [:ABRT, :KILL] - case pgroup - when 0, true - pgroup = -pid - when nil, false - pgroup = pid - end - - lldb = true if /darwin/ =~ RUBY_PLATFORM - - while signal = signals.shift - - if lldb and [:ABRT, :KILL].include?(signal) - lldb = false - # sudo -n: --non-interactive - # lldb -p: attach - # -o: run command - system(*%W[sudo -n lldb -p #{pid} --batch -o bt\ all -o call\ rb_vmdebug_stack_dump_all_threads() -o quit]) - true - end - - begin - Process.kill signal, pgroup - rescue Errno::EINVAL - next - rescue Errno::ESRCH - break - end - if signals.empty? or !reprieve - Process.wait(pid) - else - begin - Timeout.timeout(reprieve) {Process.wait(pid)} - rescue Timeout::Error - else - break - end - end - end - $? - end - module_function :terminate - - def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr = false, - encoding: nil, timeout: 10, reprieve: 1, timeout_error: Timeout::Error, - stdout_filter: nil, stderr_filter: nil, ios: nil, - signal: :TERM, - rubybin: EnvUtil.rubybin, precommand: nil, - **opt) - timeout = apply_timeout_scale(timeout) - - in_c, in_p = IO.pipe - out_p, out_c = IO.pipe if capture_stdout - err_p, err_c = IO.pipe if capture_stderr && capture_stderr != :merge_to_stdout - opt[:in] = in_c - opt[:out] = out_c if capture_stdout - opt[:err] = capture_stderr == :merge_to_stdout ? out_c : err_c if capture_stderr - if encoding - out_p.set_encoding(encoding) if out_p - err_p.set_encoding(encoding) if err_p - end - ios.each {|i, o = i|opt[i] = o} if ios - - c = "C" - child_env = {} - LANG_ENVS.each {|lc| child_env[lc] = c} - if Array === args and Hash === args.first - child_env.update(args.shift) - end - if RUBYLIB and lib = child_env["RUBYLIB"] - child_env["RUBYLIB"] = [lib, RUBYLIB].join(File::PATH_SEPARATOR) - end - - # remain env - %w(ASAN_OPTIONS RUBY_ON_BUG).each{|name| - child_env[name] = ENV[name] if ENV[name] - } - - args = [args] if args.kind_of?(String) - pid = spawn(child_env, *precommand, rubybin, *args, opt) - in_c.close - out_c&.close - out_c = nil - err_c&.close - err_c = nil - if block_given? - return yield in_p, out_p, err_p, pid - else - th_stdout = Thread.new { out_p.read } if capture_stdout - th_stderr = Thread.new { err_p.read } if capture_stderr && capture_stderr != :merge_to_stdout - in_p.write stdin_data.to_str unless stdin_data.empty? - in_p.close - if (!th_stdout || th_stdout.join(timeout)) && (!th_stderr || th_stderr.join(timeout)) - timeout_error = nil - else - status = terminate(pid, signal, opt[:pgroup], reprieve) - terminated = Time.now - end - stdout = th_stdout.value if capture_stdout - stderr = th_stderr.value if capture_stderr && capture_stderr != :merge_to_stdout - out_p.close if capture_stdout - err_p.close if capture_stderr && capture_stderr != :merge_to_stdout - status ||= Process.wait2(pid)[1] - stdout = stdout_filter.call(stdout) if stdout_filter - stderr = stderr_filter.call(stderr) if stderr_filter - if timeout_error - bt = caller_locations - msg = "execution of #{bt.shift.label} expired timeout (#{timeout} sec)" - msg = failure_description(status, terminated, msg, [stdout, stderr].join("\n")) - raise timeout_error, msg, bt.map(&:to_s) - end - return stdout, stderr, status - end - ensure - [th_stdout, th_stderr].each do |th| - th.kill if th - end - [in_c, in_p, out_c, out_p, err_c, err_p].each do |io| - io&.close - end - [th_stdout, th_stderr].each do |th| - th.join if th - end - end - module_function :invoke_ruby - - def verbose_warning - class << (stderr = "".dup) - alias write concat - def flush; end - end - stderr, $stderr = $stderr, stderr - $VERBOSE = true - yield stderr - return $stderr - ensure - stderr, $stderr = $stderr, stderr - $VERBOSE = EnvUtil.original_verbose - EnvUtil.original_warning&.each {|i, v| Warning[i] = v} - end - module_function :verbose_warning - - def default_warning - $VERBOSE = false - yield - ensure - $VERBOSE = EnvUtil.original_verbose - end - module_function :default_warning - - def suppress_warning - $VERBOSE = nil - yield - ensure - $VERBOSE = EnvUtil.original_verbose - end - module_function :suppress_warning - - def under_gc_stress(stress = true) - stress, GC.stress = GC.stress, stress - yield - ensure - GC.stress = stress - end - module_function :under_gc_stress - - def with_default_external(enc) - suppress_warning { Encoding.default_external = enc } - yield - ensure - suppress_warning { Encoding.default_external = EnvUtil.original_external_encoding } - end - module_function :with_default_external - - def with_default_internal(enc) - suppress_warning { Encoding.default_internal = enc } - yield - ensure - suppress_warning { Encoding.default_internal = EnvUtil.original_internal_encoding } - end - module_function :with_default_internal - - def labeled_module(name, &block) - Module.new do - singleton_class.class_eval { - define_method(:to_s) {name} - alias inspect to_s - alias name to_s - } - class_eval(&block) if block - end - end - module_function :labeled_module - - def labeled_class(name, superclass = Object, &block) - Class.new(superclass) do - singleton_class.class_eval { - define_method(:to_s) {name} - alias inspect to_s - alias name to_s - } - class_eval(&block) if block - end - end - module_function :labeled_class - - if /darwin/ =~ RUBY_PLATFORM - DIAGNOSTIC_REPORTS_PATH = File.expand_path("~/Library/Logs/DiagnosticReports") - DIAGNOSTIC_REPORTS_TIMEFORMAT = '%Y-%m-%d-%H%M%S' - @ruby_install_name = RbConfig::CONFIG['RUBY_INSTALL_NAME'] - - def self.diagnostic_reports(signame, pid, now) - return unless %w[ABRT QUIT SEGV ILL TRAP].include?(signame) - cmd = File.basename(rubybin) - cmd = @ruby_install_name if "ruby-runner#{RbConfig::CONFIG["EXEEXT"]}" == cmd - path = DIAGNOSTIC_REPORTS_PATH - timeformat = DIAGNOSTIC_REPORTS_TIMEFORMAT - pat = "#{path}/#{cmd}_#{now.strftime(timeformat)}[-_]*.{crash,ips}" - first = true - 30.times do - first ? (first = false) : sleep(0.1) - Dir.glob(pat) do |name| - log = File.read(name) rescue next - case name - when /\.crash\z/ - if /\AProcess:\s+#{cmd} \[#{pid}\]$/ =~ log - File.unlink(name) - File.unlink("#{path}/.#{File.basename(name)}.plist") rescue nil - return log - end - when /\.ips\z/ - if /^ *"pid" *: *#{pid},/ =~ log - File.unlink(name) - return log - end - end - end - end - nil - end - else - def self.diagnostic_reports(signame, pid, now) - end - end - - def self.failure_description(status, now, message = "", out = "") - pid = status.pid - if signo = status.termsig - signame = Signal.signame(signo) - sigdesc = "signal #{signo}" - end - log = diagnostic_reports(signame, pid, now) - if signame - sigdesc = "SIG#{signame} (#{sigdesc})" - end - if status.coredump? - sigdesc = "#{sigdesc} (core dumped)" - end - full_message = ''.dup - message = message.call if Proc === message - if message and !message.empty? - full_message << message << "\n" - end - full_message << "pid #{pid}" - full_message << " exit #{status.exitstatus}" if status.exited? - full_message << " killed by #{sigdesc}" if sigdesc - if out and !out.empty? - full_message << "\n" << out.b.gsub(/^/, '| ') - full_message.sub!(/(? Date: Thu, 25 May 2023 15:47:48 +0900 Subject: [PATCH 11/88] Move gemspec files to top of lib directory. They have version.rb files with same directory. But version.rb have been removed at https://github.com/ruby/ruby/pull/3375 There is no reason to locate under the library name of directory. --- timeout.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/timeout.gemspec b/timeout.gemspec index 7449ae1..23c6e5f 100644 --- a/timeout.gemspec +++ b/timeout.gemspec @@ -1,7 +1,7 @@ # frozen_string_literal: true name = File.basename(__FILE__, ".gemspec") -version = ["lib", Array.new(name.count("-")+1, "..").join("/")].find do |dir| +version = ["lib", Array.new(name.count("-")+1, ".").join("/")].find do |dir| break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb")) do |line| /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1 end rescue nil From f16545abe68b908f91074086c3166b4dffc57802 Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Thu, 22 Jun 2023 11:24:40 -0700 Subject: [PATCH 12/88] Raise exception instead of throw/catch for timeouts (#30) throw/catch is used for non-local control flow, not for exceptional situations. For exceptional situations, raise should be used instead. A timeout is an exceptional situation, so it should use raise, not throw/catch. Timeout's implementation that uses throw/catch internally causes serious problems. Consider the following code: ```ruby def handle_exceptions yield rescue Exception => exc handle_error # e.g. ROLLBACK for databases raise ensure handle_exit unless exc # e.g. COMMIT for databases end Timeout.timeout(1) do handle_exceptions do do_something end end ``` This kind of design ensures that all exceptions are handled as errors, and ensures that all exits (normal exit, early return, throw/catch) are not handled as errors. With Timeout's throw/catch implementation, this type of code does not work, since a timeout triggers the normal exit path. See https://github.com/rails/rails/pull/29333 for an example of the damage Timeout's design has caused the Rails ecosystem. This switches Timeout.timeout to use raise/rescue internally. It adds a Timeout::ExitException subclass of exception for the internal raise/rescue, which Timeout.timeout will convert to Timeout::Error for backwards compatibility. Timeout::Error remains a subclass of RuntimeError. This is how timeout used to work in Ruby 2.0. It was changed in Ruby 2.1, after discussion in [Bug #8730] (commit 238c003c921e6e555760f8e96968562a622a99c4 in the timeout repository). I think the change from using raise/rescue to using throw/catch has caused significant harm to the Ruby ecosystem at large, and reverting it is the most sensible choice. From the translation of [Bug #8730], it appears the issue was that someone could rescue Exception and not reraise the exception, causing timeout errors to be swallowed. However, such code is broken anyway. Using throw/catch causes far worse problems, because then it becomes impossible to differentiate between normal control flow and exceptional control flow. Also related to this is [Bug #11344], which changed how Thread.handle_interrupt interacted with Timeout. Co-authored-by: Nobuyoshi Nakada --- lib/timeout.rb | 34 +++++++++++++++------------------- test/test_timeout.rb | 40 +++++++++++++++++++++++++++++++++------- 2 files changed, 48 insertions(+), 26 deletions(-) diff --git a/lib/timeout.rb b/lib/timeout.rb index e110776..49217d5 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -25,27 +25,24 @@ module Timeout VERSION = "0.3.2" + # Internal error raised to when a timeout is triggered. + class ExitException < Exception + def exception(*) + self + end + end + # Raised by Timeout.timeout when the block times out. class Error < RuntimeError - attr_reader :thread + def self.handle_timeout(message) + exc = ExitException.new(message) - def self.catch(*args) - exc = new(*args) - exc.instance_variable_set(:@thread, Thread.current) - exc.instance_variable_set(:@catch_value, exc) - ::Kernel.catch(exc) {yield exc} - end - - def exception(*) - # TODO: use Fiber.current to see if self can be thrown - if self.thread == Thread.current - bt = caller - begin - throw(@catch_value, bt) - rescue UncaughtThrowError - end + begin + yield exc + rescue ExitException => e + raise new(message) if exc.equal?(e) + raise end - super end end @@ -195,8 +192,7 @@ def timeout(sec, klass = nil, message = nil, &block) #:yield: +sec+ if klass perform.call(klass) else - backtrace = Error.catch(&perform) - raise Error, message, backtrace + Error.handle_timeout(message, &perform) end end module_function :timeout diff --git a/test/test_timeout.rb b/test/test_timeout.rb index c3349d0..236883a 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -41,25 +41,51 @@ def test_timeout end end + def test_nested_timeout + a = nil + assert_raise(Timeout::Error) do + Timeout.timeout(0.1) { + Timeout.timeout(1) { + nil while true + } + a = 1 + } + end + assert_nil a + end + def test_cannot_convert_into_time_interval bug3168 = '[ruby-dev:41010]' def (n = Object.new).zero?; false; end assert_raise(TypeError, bug3168) {Timeout.timeout(n) { sleep 0.1 }} end - def test_skip_rescue - bug8730 = '[Bug #8730]' + def test_skip_rescue_standarderror e = nil - assert_raise_with_message(Timeout::Error, /execution expired/, bug8730) do + assert_raise_with_message(Timeout::Error, /execution expired/) do Timeout.timeout 0.01 do begin sleep 3 - rescue Exception => e + rescue => e flunk "should not see any exception but saw #{e.inspect}" end end end - assert_nil(e, bug8730) + end + + def test_raises_exception_internally + e = nil + assert_raise_with_message(Timeout::Error, /execution expired/) do + Timeout.timeout 0.01 do + begin + sleep 3 + rescue Exception => exc + e = exc + raise + end + end + end + assert_equal Timeout::ExitException, e.class end def test_rescue_exit @@ -127,11 +153,11 @@ def test_handle_interrupt bug11344 = '[ruby-dev:49179] [Bug #11344]' ok = false assert_raise(Timeout::Error) { - Thread.handle_interrupt(Timeout::Error => :never) { + Thread.handle_interrupt(Timeout::ExitException => :never) { Timeout.timeout(0.01) { sleep 0.2 ok = true - Thread.handle_interrupt(Timeout::Error => :on_blocking) { + Thread.handle_interrupt(Timeout::ExitException => :on_blocking) { sleep 0.2 } } From 413194f8d2650c6ca7b2a45b38f8c9aca53ba846 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 23 Jun 2023 12:51:31 +0900 Subject: [PATCH 13/88] Bump up v0.4.0 --- lib/timeout.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/timeout.rb b/lib/timeout.rb index 49217d5..39c47cf 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -23,7 +23,7 @@ # Copyright:: (C) 2000 Information-technology Promotion Agency, Japan module Timeout - VERSION = "0.3.2" + VERSION = "0.4.0" # Internal error raised to when a timeout is triggered. class ExitException < Exception From 949445f59188656395d04a0285e430798a94ba6f Mon Sep 17 00:00:00 2001 From: John Bachir Date: Sat, 1 Jul 2023 22:33:07 -0400 Subject: [PATCH 14/88] require ruby version in gemspec --- timeout.gemspec | 2 ++ 1 file changed, 2 insertions(+) diff --git a/timeout.gemspec b/timeout.gemspec index 23c6e5f..4b52e44 100644 --- a/timeout.gemspec +++ b/timeout.gemspec @@ -21,6 +21,8 @@ Gem::Specification.new do |spec| spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage + spec.required_ruby_version = '>= 2.5.0' + spec.files = Dir.chdir(__dir__) do `git ls-files -z`.split("\x0").reject do |f| (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features|rakelib)/|\.(?:git|travis|circleci)|appveyor|Rakefile)}) From 2d2959c1e561413f1f9502b2185cf76e7fdb5e05 Mon Sep 17 00:00:00 2001 From: John Bachir Date: Mon, 3 Jul 2023 05:26:40 -0400 Subject: [PATCH 15/88] Test that work is done in the same thread/fiber as the caller (#34) * see discussion in https://github.com/ruby/timeout/pull/30#issuecomment-1616179651 --- test/test_timeout.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/test_timeout.rb b/test/test_timeout.rb index 236883a..0a8dc45 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -4,6 +4,15 @@ class TestTimeout < Test::Unit::TestCase + def test_work_is_done_in_same_thread_as_caller + assert_equal Thread.current, Timeout.timeout(10){ Thread.current } + end + + def test_work_is_done_in_same_fiber_as_caller + require 'fiber' # needed for ruby 3.0 and lower + assert_equal Fiber.current, Timeout.timeout(10){ Fiber.current } + end + def test_non_timing_out_code_is_successful assert_nothing_raised do assert_equal :ok, Timeout.timeout(1){ :ok } From 03873a9237dd55757ed149603f9dea7af3f56788 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Mon, 3 Jul 2023 11:53:55 +0200 Subject: [PATCH 16/88] Require Ruby >= 2.6 for the timeout gem * The test suite fails on 2.5. * See https://github.com/ruby/timeout/pull/35 --- .github/workflows/test.yml | 1 + timeout.gemspec | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 93ccb2e..03f1e5e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,6 +7,7 @@ jobs: uses: ruby/actions/.github/workflows/ruby_versions.yml@master with: engine: cruby-truffleruby + min_version: 2.6 test: needs: ruby-versions diff --git a/timeout.gemspec b/timeout.gemspec index 4b52e44..258517c 100644 --- a/timeout.gemspec +++ b/timeout.gemspec @@ -21,7 +21,7 @@ Gem::Specification.new do |spec| spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage - spec.required_ruby_version = '>= 2.5.0' + spec.required_ruby_version = '>= 2.6.0' spec.files = Dir.chdir(__dir__) do `git ls-files -z`.split("\x0").reject do |f| From 3e42aa4d847ed9cf5ea4dab390ef2dc33fbb352e Mon Sep 17 00:00:00 2001 From: John Bachir Date: Tue, 4 Jul 2023 15:45:28 -0400 Subject: [PATCH 17/88] nested exception tests for discussion --- test/test_timeout.rb | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/test/test_timeout.rb b/test/test_timeout.rb index 0a8dc45..27b34de 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -63,6 +63,42 @@ def test_nested_timeout assert_nil a end + class MyNewErrorOuter < StandardError; end + class MyNewErrorInner < StandardError; end + + # DOES NOT fail with + # - raise new(message) if exc.equal?(e) + # + raise new(message) if exc.class == e.class + def test_nested_timeout_error_identity + begin + Timeout.timeout(0.1, MyNewErrorOuter) { + Timeout.timeout(1, MyNewErrorInner) { + nil while true + } + } + rescue => e + assert e.class == MyNewErrorOuter + end + end + + # DOES fail with + # - raise new(message) if exc.equal?(e) + # + raise new(message) if exc.class == e.class + def test_nested_timeout_which_error_bubbles_up + raised_exception = nil + begin + Timeout.timeout(0.1) { + Timeout.timeout(1) { + raise Timeout::ExitException.new("inner message") + } + } + rescue Exception => e + raised_exception = e + end + + assert_equal 'inner message', e.message + end + def test_cannot_convert_into_time_interval bug3168 = '[ruby-dev:41010]' def (n = Object.new).zero?; false; end From 54bc7639d297db1b691323b5d53ca993dc46c729 Mon Sep 17 00:00:00 2001 From: John Bachir Date: Sun, 9 Jul 2023 02:03:55 -0400 Subject: [PATCH 18/88] tests for blank seconds --- test/test_timeout.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/test_timeout.rb b/test/test_timeout.rb index 0a8dc45..8bfe80c 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -19,6 +19,18 @@ def test_non_timing_out_code_is_successful end end + def test_allows_zero_seconds + assert_nothing_raised do + assert_equal :ok, Timeout.timeout(0){:ok} + end + end + + def test_allows_nil_seconds + assert_nothing_raised do + assert_equal :ok, Timeout.timeout(nil){:ok} + end + end + def test_included c = Class.new do include Timeout From 8a20479b7192a86445eca5fbb158c462c88a39db Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Sep 2023 03:23:00 +0000 Subject: [PATCH 19/88] Bump actions/checkout from 3 to 4 Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 03f1e5e..e29634b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,7 +22,7 @@ jobs: ruby: truffleruby runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: From a65e49cc31bcdaad892330cdd93ab8e5481e1fc7 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 7 Nov 2023 13:44:16 +0900 Subject: [PATCH 20/88] Bump up 0.4.1 --- lib/timeout.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/timeout.rb b/lib/timeout.rb index 39c47cf..d8806e2 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -23,7 +23,7 @@ # Copyright:: (C) 2000 Information-technology Promotion Agency, Japan module Timeout - VERSION = "0.4.0" + VERSION = "0.4.1" # Internal error raised to when a timeout is triggered. class ExitException < Exception From 3acb0ed9aea779e3d05b156c817e0a0e47738101 Mon Sep 17 00:00:00 2001 From: John Bachir Date: Tue, 7 Nov 2023 09:45:15 -0500 Subject: [PATCH 21/88] additional check for error bubble up test --- test/test_timeout.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test_timeout.rb b/test/test_timeout.rb index b1a93a7..f8e3fb8 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -109,6 +109,7 @@ def test_nested_timeout_which_error_bubbles_up end assert_equal 'inner message', e.message + assert_equal 'inner message', raised_exception.message end def test_cannot_convert_into_time_interval From 56fde15b13ec87534213aa2f07b753d1dcd99d42 Mon Sep 17 00:00:00 2001 From: John Bachir Date: Tue, 7 Nov 2023 14:24:30 -0500 Subject: [PATCH 22/88] Update test/test_timeout.rb Co-authored-by: Benoit Daloze --- test/test_timeout.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/test/test_timeout.rb b/test/test_timeout.rb index f8e3fb8..e900b10 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -108,7 +108,6 @@ def test_nested_timeout_which_error_bubbles_up raised_exception = e end - assert_equal 'inner message', e.message assert_equal 'inner message', raised_exception.message end From b41f33d160c754cbe53220d4406fa639e6d53b2f Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 18 Dec 2023 14:02:04 +0900 Subject: [PATCH 23/88] [DOC] Exclude non-documented files --- .document | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .document diff --git a/.document b/.document new file mode 100644 index 0000000..5035202 --- /dev/null +++ b/.document @@ -0,0 +1,3 @@ +LICENSE.txt +README.md +lib/ From 301ad4cfdccee978904da8b89ed193e92c00dc1b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 18 Dec 2023 14:02:15 +0900 Subject: [PATCH 24/88] [DOC] Missing documents --- lib/timeout.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/timeout.rb b/lib/timeout.rb index d8806e2..c67a748 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -23,18 +23,19 @@ # Copyright:: (C) 2000 Information-technology Promotion Agency, Japan module Timeout + # The version VERSION = "0.4.1" # Internal error raised to when a timeout is triggered. class ExitException < Exception - def exception(*) + def exception(*) # :nodoc: self end end # Raised by Timeout.timeout when the block times out. class Error < RuntimeError - def self.handle_timeout(message) + def self.handle_timeout(message) # :nodoc: exc = ExitException.new(message) begin From 47a6f0313b14ce1b9601322dda3acf6461b1684f Mon Sep 17 00:00:00 2001 From: Mark Young Date: Wed, 20 Dec 2023 14:44:09 +0000 Subject: [PATCH 25/88] Provide a 'Changelog' link on rubygems.org/gems/timeout By providing a 'changelog_uri' in the metadata of the gemspec a 'Changelog' link will be shown on https://rubygems.org/gems/timeout which makes it quick and easy for someone to check on the changes introduced with a new version. Details of this functionality can be found on https://guides.rubygems.org/specification-reference/ --- timeout.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/timeout.gemspec b/timeout.gemspec index 258517c..5449494 100644 --- a/timeout.gemspec +++ b/timeout.gemspec @@ -20,6 +20,7 @@ Gem::Specification.new do |spec| spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage + spec.metadata["changelog_uri"] = spec.homepage + "/releases" spec.required_ruby_version = '>= 2.6.0' From cb5ee35b536a67c516f858206a678602f7f425c0 Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Tue, 28 May 2024 12:04:34 +0900 Subject: [PATCH 26/88] Make test_nested_timeouts less flaky This test randomly fails due to the bug reported in [Bug #20314], where the two timeouts are too close so that they can expire at the same time. As a workaround, this change increases the time difference between timeouts. This will reduce the probability of simultaneous expirations and lower flakiness. --- test/test_timeout.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_timeout.rb b/test/test_timeout.rb index e900b10..34966f9 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -66,7 +66,7 @@ def test_nested_timeout a = nil assert_raise(Timeout::Error) do Timeout.timeout(0.1) { - Timeout.timeout(1) { + Timeout.timeout(30) { nil while true } a = 1 @@ -84,7 +84,7 @@ class MyNewErrorInner < StandardError; end def test_nested_timeout_error_identity begin Timeout.timeout(0.1, MyNewErrorOuter) { - Timeout.timeout(1, MyNewErrorInner) { + Timeout.timeout(30, MyNewErrorInner) { nil while true } } From 6c99460887e820c54ee7c19350b93bdc58785fca Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 30 May 2024 11:54:19 +0900 Subject: [PATCH 27/88] Update license files with ruby/ruby --- LICENSE.txt => BSDL | 6 ++--- COPYING | 56 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 3 deletions(-) rename LICENSE.txt => BSDL (83%) create mode 100644 COPYING diff --git a/LICENSE.txt b/BSDL similarity index 83% rename from LICENSE.txt rename to BSDL index a009cae..66d9359 100644 --- a/LICENSE.txt +++ b/BSDL @@ -4,10 +4,10 @@ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. + notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright -notice, this list of conditions and the following disclaimer in the -documentation and/or other materials provided with the distribution. + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..48e5a96 --- /dev/null +++ b/COPYING @@ -0,0 +1,56 @@ +Ruby is copyrighted free software by Yukihiro Matsumoto . +You can redistribute it and/or modify it under either the terms of the +2-clause BSDL (see the file BSDL), or the conditions below: + +1. You may make and give away verbatim copies of the source form of the + software without restriction, provided that you duplicate all of the + original copyright notices and associated disclaimers. + +2. You may modify your copy of the software in any way, provided that + you do at least ONE of the following: + + a. place your modifications in the Public Domain or otherwise + make them Freely Available, such as by posting said + modifications to Usenet or an equivalent medium, or by allowing + the author to include your modifications in the software. + + b. use the modified software only within your corporation or + organization. + + c. give non-standard binaries non-standard names, with + instructions on where to get the original software distribution. + + d. make other distribution arrangements with the author. + +3. You may distribute the software in object code or binary form, + provided that you do at least ONE of the following: + + a. distribute the binaries and library files of the software, + together with instructions (in the manual page or equivalent) + on where to get the original distribution. + + b. accompany the distribution with the machine-readable source of + the software. + + c. give non-standard binaries non-standard names, with + instructions on where to get the original software distribution. + + d. make other distribution arrangements with the author. + +4. You may modify and include the part of the software into any other + software (possibly commercial). But some files in the distribution + are not written by the author, so that they are not under these terms. + + For the list of those files and their copying conditions, see the + file LEGAL. + +5. The scripts and library files supplied as input to or produced as + output from the software do not automatically fall under the + copyright of the software, but belong to whomever generated them, + and may be sold commercially, and may be aggregated with this + software. + +6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE. From 213f4e25dd2a3169570ceb2ca4f51f38bdd8d995 Mon Sep 17 00:00:00 2001 From: JP Camara Date: Tue, 27 Aug 2024 18:48:12 +0000 Subject: [PATCH 28/88] Global #timeout was remove 5 years ago * Also update Timeout::timeout to the more common format of Timeout.timeout --- README.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/README.md b/README.md index db1ad0a..61fe2d3 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,6 @@ Timeout provides a way to auto-terminate a potentially long-running operation if it hasn't finished in a fixed amount of time. -Previous versions didn't use a module for namespacing, however -#timeout is provided for backwards compatibility. You -should prefer Timeout.timeout instead. - ## Installation Add this line to your application's Gemfile: @@ -27,7 +23,7 @@ Or install it yourself as: ```ruby require 'timeout' -status = Timeout::timeout(5) { +status = Timeout.timeout(5) { # Something that should be interrupted if it takes more than 5 seconds... } ``` From 683fdb45ee4aa747a3e791cc65aa7d98034be46b Mon Sep 17 00:00:00 2001 From: Olle Jonsson Date: Wed, 28 Aug 2024 12:23:33 +0200 Subject: [PATCH 29/88] timeout.rb: Update documentation This is a followup to #49. --- lib/timeout.rb | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/timeout.rb b/lib/timeout.rb index c67a748..6448f4f 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -4,7 +4,7 @@ # == Synopsis # # require 'timeout' -# status = Timeout::timeout(5) { +# status = Timeout.timeout(5) { # # Something that should be interrupted if it takes more than 5 seconds... # } # @@ -13,10 +13,6 @@ # Timeout provides a way to auto-terminate a potentially long-running # operation if it hasn't finished in a fixed amount of time. # -# Previous versions didn't use a module for namespacing, however -# #timeout is provided for backwards compatibility. You -# should prefer Timeout.timeout instead. -# # == Copyright # # Copyright:: (C) 2000 Network Applied Communication Laboratory, Inc. From 1f0a0fb2b7b042f83da3e7daf97bd5120ea4630c Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 10 Sep 2024 17:53:56 +0900 Subject: [PATCH 30/88] Exclude truffleruby-head from ubuntu-latest https://github.com/ruby/timeout/actions/runs/10595331126/job/29912392672 --- .github/workflows/test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e29634b..23f46cf 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,6 +20,8 @@ jobs: exclude: - os: macos-latest ruby: truffleruby + - os: ubuntu-latest + ruby: truffleruby-head runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 From 8e57887eee70e62245b8fd0eb6d95227a8baea61 Mon Sep 17 00:00:00 2001 From: CosmicOppai Date: Mon, 14 Oct 2024 14:45:33 +0530 Subject: [PATCH 31/88] added the check for negative sec --- lib/timeout.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/timeout.rb b/lib/timeout.rb index 6448f4f..17d7b21 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -164,6 +164,7 @@ def self.ensure_timeout_thread_created # Timeout into your classes so they have a #timeout method, as well as # a module method, so you can call it directly as Timeout.timeout(). def timeout(sec, klass = nil, message = nil, &block) #:yield: +sec+ + raise ArgumentError, "Timeout sec must be a positive number" unless sec.is_a?(Numeric) && sec >= 0 return yield(sec) if sec == nil or sec.zero? message ||= "execution expired" From 834254497970b75e2a9376efdedc5b27f8a57723 Mon Sep 17 00:00:00 2001 From: CosmicOppai Date: Mon, 14 Oct 2024 16:23:53 +0530 Subject: [PATCH 32/88] refactor the change to keep the compatability with nil and type-errror and added tests --- lib/timeout.rb | 2 +- test/test_timeout.rb | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/timeout.rb b/lib/timeout.rb index 17d7b21..d7baa9a 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -164,7 +164,7 @@ def self.ensure_timeout_thread_created # Timeout into your classes so they have a #timeout method, as well as # a module method, so you can call it directly as Timeout.timeout(). def timeout(sec, klass = nil, message = nil, &block) #:yield: +sec+ - raise ArgumentError, "Timeout sec must be a positive number" unless sec.is_a?(Numeric) && sec >= 0 + raise ArgumentError, "Timeout sec must be a positive number" if sec.is_a?(Numeric) && sec < 0 return yield(sec) if sec == nil or sec.zero? message ||= "execution expired" diff --git a/test/test_timeout.rb b/test/test_timeout.rb index 34966f9..888489f 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -1,6 +1,6 @@ # frozen_string_literal: false require 'test/unit' -require 'timeout' +require_relative '../lib/timeout' class TestTimeout < Test::Unit::TestCase @@ -31,6 +31,12 @@ def test_allows_nil_seconds end end + def test_raise_for_neg_second + assert_raise(ArgumentError) do + Timeout.timeout(-1) { sleep(0.01) } + end + end + def test_included c = Class.new do include Timeout From e8a7dbdb874ebfb8e15cb960ebdaff665f561cf8 Mon Sep 17 00:00:00 2001 From: CosmicOppai Date: Mon, 14 Oct 2024 16:24:21 +0530 Subject: [PATCH 33/88] refactor the change to keep the compatability with nil and type-errror and added tests --- test/test_timeout.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_timeout.rb b/test/test_timeout.rb index 888489f..d6c1a1f 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -1,6 +1,6 @@ # frozen_string_literal: false require 'test/unit' -require_relative '../lib/timeout' +require 'timeout' class TestTimeout < Test::Unit::TestCase From ffc8d7c003c88fa2fecdf3efcb7be2c9ad5c01f2 Mon Sep 17 00:00:00 2001 From: CosmicOppai Date: Tue, 15 Oct 2024 06:21:18 +0530 Subject: [PATCH 34/88] refactor the change to raise for nil and type-errror and added tests --- lib/timeout.rb | 4 ++-- test/test_timeout.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/timeout.rb b/lib/timeout.rb index d7baa9a..f2495d9 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -164,8 +164,8 @@ def self.ensure_timeout_thread_created # Timeout into your classes so they have a #timeout method, as well as # a module method, so you can call it directly as Timeout.timeout(). def timeout(sec, klass = nil, message = nil, &block) #:yield: +sec+ - raise ArgumentError, "Timeout sec must be a positive number" if sec.is_a?(Numeric) && sec < 0 - return yield(sec) if sec == nil or sec.zero? + raise ArgumentError, "Timeout sec must be a positive number" unless sec.is_a?(Numeric) && sec >= 0 + return yield(sec) if sec.zero? message ||= "execution expired" diff --git a/test/test_timeout.rb b/test/test_timeout.rb index d6c1a1f..bc30363 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -26,7 +26,7 @@ def test_allows_zero_seconds end def test_allows_nil_seconds - assert_nothing_raised do + assert_raise(ArgumentError) do assert_equal :ok, Timeout.timeout(nil){:ok} end end @@ -120,7 +120,7 @@ def test_nested_timeout_which_error_bubbles_up def test_cannot_convert_into_time_interval bug3168 = '[ruby-dev:41010]' def (n = Object.new).zero?; false; end - assert_raise(TypeError, bug3168) {Timeout.timeout(n) { sleep 0.1 }} + assert_raise(ArgumentError, bug3168) {Timeout.timeout(n) { sleep 0.1 }} end def test_skip_rescue_standarderror From f992632cf396b659572384f0c171e2a1f2dfba09 Mon Sep 17 00:00:00 2001 From: CosmicOppai Date: Fri, 18 Oct 2024 01:08:38 +0530 Subject: [PATCH 35/88] updated doc and kept the nil compatiability --- lib/timeout.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/timeout.rb b/lib/timeout.rb index f2495d9..def80df 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -141,9 +141,10 @@ def self.ensure_timeout_thread_created # Perform an operation in a block, raising an error if it takes longer than # +sec+ seconds to complete. # - # +sec+:: Number of seconds to wait for the block to terminate. Any number - # may be used, including Floats to specify fractional seconds. A + # +sec+:: Number of seconds to wait for the block to terminate. Any non-negative number + # or nil may be used, including Floats to specify fractional seconds. A # value of 0 or +nil+ will execute the block without any timeout. + # Any negative value will raise the ArgumentError # +klass+:: Exception Class to raise if the block fails to terminate # in +sec+ seconds. Omitting will use the default, Timeout::Error # +message+:: Error message to raise with Exception Class. @@ -164,8 +165,8 @@ def self.ensure_timeout_thread_created # Timeout into your classes so they have a #timeout method, as well as # a module method, so you can call it directly as Timeout.timeout(). def timeout(sec, klass = nil, message = nil, &block) #:yield: +sec+ - raise ArgumentError, "Timeout sec must be a positive number" unless sec.is_a?(Numeric) && sec >= 0 - return yield(sec) if sec.zero? + raise ArgumentError, "Timeout sec must be a non-negative number" if sec && !(sec.is_a?(Numeric) && sec >= 0) + return yield(sec) if sec == nil or sec.zero? message ||= "execution expired" From c6d121aa1878d1c977d741c5cf17d4146dc69d22 Mon Sep 17 00:00:00 2001 From: CosmicOppai Date: Fri, 18 Oct 2024 01:10:34 +0530 Subject: [PATCH 36/88] updated tests --- test/test_timeout.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_timeout.rb b/test/test_timeout.rb index bc30363..0115686 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -26,7 +26,7 @@ def test_allows_zero_seconds end def test_allows_nil_seconds - assert_raise(ArgumentError) do + assert_nothing_raised do assert_equal :ok, Timeout.timeout(nil){:ok} end end From 87edf958923b669402416b77f2d071ee8aaf2ed4 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 6 Nov 2024 17:25:03 +0900 Subject: [PATCH 37/88] Enabled trusted publisher for rubygems.org --- .github/workflows/push_gem.yml | 46 ++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 .github/workflows/push_gem.yml diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml new file mode 100644 index 0000000..6e453aa --- /dev/null +++ b/.github/workflows/push_gem.yml @@ -0,0 +1,46 @@ +name: Publish gem to rubygems.org + +on: + push: + tags: + - 'v*' + +permissions: + contents: read + +jobs: + push: + if: github.repository == 'ruby/timeout' + runs-on: ubuntu-latest + + environment: + name: rubygems.org + url: https://rubygems.org/gems/timeout + + permissions: + contents: write + id-token: write + + steps: + - name: Harden Runner + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + with: + egress-policy: audit + + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + + - name: Set up Ruby + uses: ruby/setup-ruby@a6e6f86333f0a2523ece813039b8b4be04560854 # v1.190.0 + with: + bundler-cache: true + ruby-version: ruby + + - name: Publish to RubyGems + uses: rubygems/release-gem@612653d273a73bdae1df8453e090060bb4db5f31 # v1 + + - name: Create GitHub release + run: | + tag_name="$(git describe --tags --abbrev=0)" + gh release create "${tag_name}" --verify-tag --generate-notes + env: + GITHUB_TOKEN: ${{ secrets.MATZBOT_GITHUB_WORKFLOW_TOKEN }} From 2f5252299403e00135b694455fb31a2bded32cd5 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 6 Nov 2024 17:26:49 +0900 Subject: [PATCH 38/88] Bump up v0.4.2 --- lib/timeout.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/timeout.rb b/lib/timeout.rb index 6448f4f..bf12f37 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -20,7 +20,7 @@ module Timeout # The version - VERSION = "0.4.1" + VERSION = "0.4.2" # Internal error raised to when a timeout is triggered. class ExitException < Exception From 4be6423de4fb144a9aa255ba541683b9c0fb16a4 Mon Sep 17 00:00:00 2001 From: Cosmic Oppai Date: Mon, 18 Nov 2024 15:35:47 +0530 Subject: [PATCH 39/88] updated doc string Co-authored-by: Jeremy Evans --- lib/timeout.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/timeout.rb b/lib/timeout.rb index def80df..23fbc3b 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -144,7 +144,7 @@ def self.ensure_timeout_thread_created # +sec+:: Number of seconds to wait for the block to terminate. Any non-negative number # or nil may be used, including Floats to specify fractional seconds. A # value of 0 or +nil+ will execute the block without any timeout. - # Any negative value will raise the ArgumentError + # Any negative number will raise an ArgumentError. # +klass+:: Exception Class to raise if the block fails to terminate # in +sec+ seconds. Omitting will use the default, Timeout::Error # +message+:: Error message to raise with Exception Class. From 7d2af46a00861322150a61bc1557e966c68dd102 Mon Sep 17 00:00:00 2001 From: Cosmic Oppai Date: Tue, 19 Nov 2024 01:56:21 +0530 Subject: [PATCH 40/88] removed the non numeric check Co-authored-by: Jeremy Evans --- lib/timeout.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/timeout.rb b/lib/timeout.rb index 23fbc3b..a1595a8 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -165,8 +165,8 @@ def self.ensure_timeout_thread_created # Timeout into your classes so they have a #timeout method, as well as # a module method, so you can call it directly as Timeout.timeout(). def timeout(sec, klass = nil, message = nil, &block) #:yield: +sec+ - raise ArgumentError, "Timeout sec must be a non-negative number" if sec && !(sec.is_a?(Numeric) && sec >= 0) return yield(sec) if sec == nil or sec.zero? + raise ArgumentError, "Timeout sec must be a non-negative number" if 0 > sec message ||= "execution expired" From b201b08f74f7504c2b4ea9a1fd20d529e1ee5222 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Nov 2024 03:13:56 +0000 Subject: [PATCH 41/88] Bump rubygems/release-gem Bumps [rubygems/release-gem](https://github.com/rubygems/release-gem) from 612653d273a73bdae1df8453e090060bb4db5f31 to 9e85cb11501bebc2ae661c1500176316d3987059. - [Release notes](https://github.com/rubygems/release-gem/releases) - [Commits](https://github.com/rubygems/release-gem/compare/612653d273a73bdae1df8453e090060bb4db5f31...9e85cb11501bebc2ae661c1500176316d3987059) --- updated-dependencies: - dependency-name: rubygems/release-gem dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- .github/workflows/push_gem.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index 6e453aa..0c6cbef 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -36,7 +36,7 @@ jobs: ruby-version: ruby - name: Publish to RubyGems - uses: rubygems/release-gem@612653d273a73bdae1df8453e090060bb4db5f31 # v1 + uses: rubygems/release-gem@9e85cb11501bebc2ae661c1500176316d3987059 # v1 - name: Create GitHub release run: | From 5c1533c806c26d325fa39ffa077485b2a54059a8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Nov 2024 03:13:59 +0000 Subject: [PATCH 42/88] Bump step-security/harden-runner from 2.10.1 to 2.10.2 Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.10.1 to 2.10.2. - [Release notes](https://github.com/step-security/harden-runner/releases) - [Commits](https://github.com/step-security/harden-runner/compare/91182cccc01eb5e619899d80e4e971d6181294a7...0080882f6c36860b6ba35c610c98ce87d4e2f26f) --- updated-dependencies: - dependency-name: step-security/harden-runner dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/push_gem.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index 6e453aa..5bb999e 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -23,7 +23,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 with: egress-policy: audit From 7d10160a49a80d7404dc6b44ecb274bf9c538707 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 26 Nov 2024 12:13:04 +0900 Subject: [PATCH 43/88] Fixed version number of rubygems/release-gem --- .github/workflows/push_gem.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index 9d64e5e..1c9a726 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -36,7 +36,7 @@ jobs: ruby-version: ruby - name: Publish to RubyGems - uses: rubygems/release-gem@9e85cb11501bebc2ae661c1500176316d3987059 # v1 + uses: rubygems/release-gem@9e85cb11501bebc2ae661c1500176316d3987059 # v1.1.0 - name: Create GitHub release run: | From 607d8c6fbe4d86db1cf22846e89198b47cec7161 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 16 Dec 2024 12:53:10 +0900 Subject: [PATCH 44/88] Bump up v0.4.3 --- lib/timeout.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/timeout.rb b/lib/timeout.rb index e403eee..4fd1fa4 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -20,7 +20,7 @@ module Timeout # The version - VERSION = "0.4.2" + VERSION = "0.4.3" # Internal error raised to when a timeout is triggered. class ExitException < Exception From a60b4c248f28d11f1b807c491b6532171dbaf2b9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Dec 2024 03:18:35 +0000 Subject: [PATCH 45/88] Bump rubygems/release-gem from 1.1.0 to 1.1.1 Bumps [rubygems/release-gem](https://github.com/rubygems/release-gem) from 1.1.0 to 1.1.1. - [Release notes](https://github.com/rubygems/release-gem/releases) - [Commits](https://github.com/rubygems/release-gem/compare/9e85cb11501bebc2ae661c1500176316d3987059...a25424ba2ba8b387abc8ef40807c2c85b96cbe32) --- updated-dependencies: - dependency-name: rubygems/release-gem dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/push_gem.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index 1c9a726..0bf786f 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -36,7 +36,7 @@ jobs: ruby-version: ruby - name: Publish to RubyGems - uses: rubygems/release-gem@9e85cb11501bebc2ae661c1500176316d3987059 # v1.1.0 + uses: rubygems/release-gem@a25424ba2ba8b387abc8ef40807c2c85b96cbe32 # v1.1.1 - name: Create GitHub release run: | From a30a85bd1113b6671b268f687c97075cd8ebf5eb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 03:44:29 +0000 Subject: [PATCH 46/88] Bump step-security/harden-runner from 2.10.2 to 2.10.3 Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.10.2 to 2.10.3. - [Release notes](https://github.com/step-security/harden-runner/releases) - [Commits](https://github.com/step-security/harden-runner/compare/0080882f6c36860b6ba35c610c98ce87d4e2f26f...c95a14d0e5bab51a9f56296a4eb0e416910cd350) --- updated-dependencies: - dependency-name: step-security/harden-runner dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/push_gem.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index 0bf786f..005a52c 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -23,7 +23,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 + uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3 with: egress-policy: audit From ec88ffd8e9c5539111fc7d94971b2d0aed576d68 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Jan 2025 03:34:55 +0000 Subject: [PATCH 47/88] Bump step-security/harden-runner from 2.10.3 to 2.10.4 Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.10.3 to 2.10.4. - [Release notes](https://github.com/step-security/harden-runner/releases) - [Commits](https://github.com/step-security/harden-runner/compare/c95a14d0e5bab51a9f56296a4eb0e416910cd350...cb605e52c26070c328afc4562f0b4ada7618a84e) --- updated-dependencies: - dependency-name: step-security/harden-runner dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/push_gem.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index 005a52c..23fa64f 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -23,7 +23,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3 + uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4 with: egress-policy: audit From 284b047396e8d01bbb937a9b804ac900170e7b19 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Feb 2025 03:11:06 +0000 Subject: [PATCH 48/88] Bump step-security/harden-runner from 2.10.4 to 2.11.0 Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.10.4 to 2.11.0. - [Release notes](https://github.com/step-security/harden-runner/releases) - [Commits](https://github.com/step-security/harden-runner/compare/cb605e52c26070c328afc4562f0b4ada7618a84e...4d991eb9b905ef189e4c376166672c3f2f230481) --- updated-dependencies: - dependency-name: step-security/harden-runner dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/push_gem.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index 23fa64f..2a47167 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -23,7 +23,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4 + uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 with: egress-policy: audit From 0799d527696a9f3238b4a48a3514c9d3e31d5aab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Apr 2025 03:52:08 +0000 Subject: [PATCH 49/88] Bump step-security/harden-runner from 2.11.0 to 2.11.1 Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.11.0 to 2.11.1. - [Release notes](https://github.com/step-security/harden-runner/releases) - [Commits](https://github.com/step-security/harden-runner/compare/4d991eb9b905ef189e4c376166672c3f2f230481...c6295a65d1254861815972266d5933fd6e532bdf) --- updated-dependencies: - dependency-name: step-security/harden-runner dependency-version: 2.11.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/push_gem.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index 2a47167..8d9ae50 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -23,7 +23,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 + uses: step-security/harden-runner@c6295a65d1254861815972266d5933fd6e532bdf # v2.11.1 with: egress-policy: audit From 6974c48d030640ca0f578fc3bdb4adfd19f1cef5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Apr 2025 03:06:16 +0000 Subject: [PATCH 50/88] Bump step-security/harden-runner from 2.11.1 to 2.12.0 Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.11.1 to 2.12.0. - [Release notes](https://github.com/step-security/harden-runner/releases) - [Commits](https://github.com/step-security/harden-runner/compare/c6295a65d1254861815972266d5933fd6e532bdf...0634a2670c59f64b4a01f0f96f84700a4088b9f0) --- updated-dependencies: - dependency-name: step-security/harden-runner dependency-version: 2.12.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/push_gem.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index 8d9ae50..dc26a5d 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -23,7 +23,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@c6295a65d1254861815972266d5933fd6e532bdf # v2.11.1 + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 with: egress-policy: audit From a7b89214c91c2fa5f0b2ed85ece035fcce6e3092 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 03:20:14 +0000 Subject: [PATCH 51/88] Bump step-security/harden-runner from 2.12.0 to 2.12.1 Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.12.0 to 2.12.1. - [Release notes](https://github.com/step-security/harden-runner/releases) - [Commits](https://github.com/step-security/harden-runner/compare/0634a2670c59f64b4a01f0f96f84700a4088b9f0...002fdce3c6a235733a90a27c80493a3241e56863) --- updated-dependencies: - dependency-name: step-security/harden-runner dependency-version: 2.12.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/push_gem.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index dc26a5d..abd91a8 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -23,7 +23,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 with: egress-policy: audit From 063cb211bf6ecddb630b2625c16a51f0701d18a2 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 20 Jun 2025 13:43:17 +0900 Subject: [PATCH 52/88] Use GITHUB_TOKEN instead of admin credential --- .github/workflows/push_gem.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index abd91a8..3d8b89e 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -43,4 +43,4 @@ jobs: tag_name="$(git describe --tags --abbrev=0)" gh release create "${tag_name}" --verify-tag --generate-notes env: - GITHUB_TOKEN: ${{ secrets.MATZBOT_GITHUB_WORKFLOW_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 7a48e1c079e8c5e14f45661bba00bef195afef90 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Tue, 24 Jun 2025 22:37:18 +0200 Subject: [PATCH 53/88] Gracefully handle a call to ensure_timeout_thread_created in a signal handler * Fixes the issue described in https://github.com/ruby/timeout/issues/17#issuecomment-1461498517 for TruffleRuby and JRuby. * CRuby is currently unable to use Timeout in a signal handler due to https://bugs.ruby-lang.org/issues/19473. --- lib/timeout.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/timeout.rb b/lib/timeout.rb index 4fd1fa4..f5f232a 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -123,6 +123,9 @@ def self.create_timeout_thread def self.ensure_timeout_thread_created unless @timeout_thread and @timeout_thread.alive? + # If the Mutex is already owned we are in a signal handler. + # In that case, just return and let the main thread create the @timeout_thread. + return if TIMEOUT_THREAD_MUTEX.owned? TIMEOUT_THREAD_MUTEX.synchronize do unless @timeout_thread and @timeout_thread.alive? @timeout_thread = create_timeout_thread From ff53052ead55d61947a13a01710e557ddd87388f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Jul 2025 04:19:29 +0000 Subject: [PATCH 54/88] Bump step-security/harden-runner from 2.12.1 to 2.12.2 Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.12.1 to 2.12.2. - [Release notes](https://github.com/step-security/harden-runner/releases) - [Commits](https://github.com/step-security/harden-runner/compare/002fdce3c6a235733a90a27c80493a3241e56863...6c439dc8bdf85cadbbce9ed30d1c7b959517bc49) --- updated-dependencies: - dependency-name: step-security/harden-runner dependency-version: 2.12.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/push_gem.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index 3d8b89e..3993991 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -23,7 +23,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 + uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2 with: egress-policy: audit From fdc7ec0a195b578d7161471a42b5faa0c7710ef9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Jul 2025 03:48:15 +0000 Subject: [PATCH 55/88] Bump step-security/harden-runner from 2.12.2 to 2.13.0 --- updated-dependencies: - dependency-name: step-security/harden-runner dependency-version: 2.13.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/push_gem.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index 3993991..15395f1 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -23,7 +23,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2 + uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 with: egress-policy: audit From fe31a48254f595dafbde7db86202bc95c4d7c12f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Aug 2025 04:26:49 +0000 Subject: [PATCH 56/88] Bump actions/checkout from 4 to 5 Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/push_gem.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index 15395f1..018c948 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -27,7 +27,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up Ruby uses: ruby/setup-ruby@a6e6f86333f0a2523ece813039b8b4be04560854 # v1.190.0 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 23f46cf..292d57d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,7 +24,7 @@ jobs: ruby: truffleruby-head runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: From a0f9ae609fc42a9dc090f591130294a88c111b52 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Sep 2025 03:01:56 +0000 Subject: [PATCH 57/88] Bump step-security/harden-runner from 2.13.0 to 2.13.1 Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.13.0 to 2.13.1. - [Release notes](https://github.com/step-security/harden-runner/releases) - [Commits](https://github.com/step-security/harden-runner/compare/ec9f2d5744a09debf3a187a3f4f675c53b671911...f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a) --- updated-dependencies: - dependency-name: step-security/harden-runner dependency-version: 2.13.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/push_gem.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index 018c948..1cdff24 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -23,7 +23,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 with: egress-policy: audit From 5e4073f4b39ea8069d410b18137c9bd6594e0a54 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 6 Oct 2025 17:04:16 -0700 Subject: [PATCH 58/88] Add a workflow to sync commits to ruby/ruby (#69) --- .github/workflows/sync-ruby.yml | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .github/workflows/sync-ruby.yml diff --git a/.github/workflows/sync-ruby.yml b/.github/workflows/sync-ruby.yml new file mode 100644 index 0000000..29c4344 --- /dev/null +++ b/.github/workflows/sync-ruby.yml @@ -0,0 +1,33 @@ +name: Sync ruby +on: + push: + branches: [master] +jobs: + sync: + name: Sync ruby + runs-on: ubuntu-latest + if: ${{ github.repository_owner == 'ruby' }} + steps: + - uses: actions/checkout@v5 + + - name: Create GitHub App token + id: app-token + uses: actions/create-github-app-token@v2 + with: + app-id: 2060836 + private-key: ${{ secrets.RUBY_SYNC_DEFAULT_GEMS_PRIVATE_KEY }} + owner: ruby + repositories: ruby + + - name: Sync to ruby/ruby + uses: convictional/trigger-workflow-and-wait@v1.6.5 + with: + owner: ruby + repo: ruby + workflow_file_name: sync_default_gems.yml + github_token: ${{ steps.app-token.outputs.token }} + ref: master + client_payload: | + {"gem":"${{ github.event.repository.name }}","before":"${{ github.event.before }}","after":"${{ github.event.after }}"} + propagate_failure: true + wait_interval: 10 From ec53a95015d2e50606bcd2c314a6b96cbf18aa5c Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 29 Oct 2025 15:48:42 +0900 Subject: [PATCH 59/88] Update the latest versions of actions --- .github/workflows/push_gem.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index 1cdff24..f54f8f9 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -30,13 +30,13 @@ jobs: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up Ruby - uses: ruby/setup-ruby@a6e6f86333f0a2523ece813039b8b4be04560854 # v1.190.0 + uses: ruby/setup-ruby@d5126b9b3579e429dd52e51e68624dda2e05be25 # v1.267.0 with: bundler-cache: true ruby-version: ruby - name: Publish to RubyGems - uses: rubygems/release-gem@a25424ba2ba8b387abc8ef40807c2c85b96cbe32 # v1.1.1 + uses: rubygems/release-gem@1c162a739e8b4cb21a676e97b087e8268d8fc40b # v1.1.2 - name: Create GitHub release run: | From f42b47d383f65180dbe3abf45fff6335773da060 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 29 Oct 2025 15:49:48 +0900 Subject: [PATCH 60/88] v0.4.4 --- lib/timeout.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/timeout.rb b/lib/timeout.rb index f5f232a..e1f0a4a 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -20,7 +20,7 @@ module Timeout # The version - VERSION = "0.4.3" + VERSION = "0.4.4" # Internal error raised to when a timeout is triggered. class ExitException < Exception From 983cbf636a173bbea516c4cc2354b970108bd137 Mon Sep 17 00:00:00 2001 From: Olle Jonsson Date: Mon, 10 Nov 2025 09:27:07 +0100 Subject: [PATCH 61/88] Suppress warnings in two tests Failed build in #70. Pre-3.0 versions of Ruby didn't support pattern matching, and power_assert warned. --- test/test_timeout.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_timeout.rb b/test/test_timeout.rb index 0115686..71d8e1f 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -250,7 +250,7 @@ def test_fork end def test_threadgroup - assert_separately(%w[-rtimeout], <<-'end;') + assert_separately(%w[-W0 -rtimeout], <<-'end;') tg = ThreadGroup.new thr = Thread.new do tg.add(Thread.current) @@ -263,7 +263,7 @@ def test_threadgroup # https://github.com/ruby/timeout/issues/24 def test_handling_enclosed_threadgroup - assert_separately(%w[-rtimeout], <<-'end;') + assert_separately(%w[-W0 -rtimeout], <<-'end;') Thread.new { t = Thread.current group = ThreadGroup.new From 095207f27088d07d0e6d0f16b7c502a460bc900d Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 26 Nov 2025 18:40:10 +0900 Subject: [PATCH 62/88] Revert "Suppress warnings in two tests" This reverts commit 983cbf636a173bbea516c4cc2354b970108bd137, that is fixed by test-unit 3.7.3. --- test/test_timeout.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_timeout.rb b/test/test_timeout.rb index 71d8e1f..0115686 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -250,7 +250,7 @@ def test_fork end def test_threadgroup - assert_separately(%w[-W0 -rtimeout], <<-'end;') + assert_separately(%w[-rtimeout], <<-'end;') tg = ThreadGroup.new thr = Thread.new do tg.add(Thread.current) @@ -263,7 +263,7 @@ def test_threadgroup # https://github.com/ruby/timeout/issues/24 def test_handling_enclosed_threadgroup - assert_separately(%w[-W0 -rtimeout], <<-'end;') + assert_separately(%w[-rtimeout], <<-'end;') Thread.new { t = Thread.current group = ThreadGroup.new From 45b9db0c879f699ae26afb7279df62ef35d48c47 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Nov 2025 09:56:43 +0000 Subject: [PATCH 63/88] Bump step-security/harden-runner from 2.13.1 to 2.13.2 Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.13.1 to 2.13.2. - [Release notes](https://github.com/step-security/harden-runner/releases) - [Commits](https://github.com/step-security/harden-runner/compare/f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a...95d9a5deda9de15063e7595e9719c11c38c90ae2) --- updated-dependencies: - dependency-name: step-security/harden-runner dependency-version: 2.13.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/push_gem.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index f54f8f9..e8a04ed 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -23,7 +23,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 + uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 with: egress-policy: audit From 56d530533a18fe98de962297f9bda7b691412cb1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Nov 2025 09:59:05 +0000 Subject: [PATCH 64/88] Bump actions/checkout from 5 to 6 Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6. - [Release notes](https://github.com/actions/checkout/releases) - [Commits](https://github.com/actions/checkout/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/push_gem.yml | 2 +- .github/workflows/sync-ruby.yml | 2 +- .github/workflows/test.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index e8a04ed..261376b 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -27,7 +27,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Set up Ruby uses: ruby/setup-ruby@d5126b9b3579e429dd52e51e68624dda2e05be25 # v1.267.0 diff --git a/.github/workflows/sync-ruby.yml b/.github/workflows/sync-ruby.yml index 29c4344..78e94eb 100644 --- a/.github/workflows/sync-ruby.yml +++ b/.github/workflows/sync-ruby.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest if: ${{ github.repository_owner == 'ruby' }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Create GitHub App token id: app-token diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 292d57d..f3f76ab 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,7 +24,7 @@ jobs: ruby: truffleruby-head runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: From cd51eac3ca21830f30092ae4f8f30e3179f2895b Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Fri, 5 Dec 2025 12:32:35 +0100 Subject: [PATCH 65/88] Only the timeout method should be public on the Timeout module --- lib/timeout.rb | 10 +++++++--- test/test_timeout.rb | 6 ++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/timeout.rb b/lib/timeout.rb index e1f0a4a..f173d02 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -133,6 +133,7 @@ def self.ensure_timeout_thread_created end end end + private_class_method :ensure_timeout_thread_created # We keep a private reference so that time mocking libraries won't break # Timeout. @@ -167,7 +168,7 @@ def self.ensure_timeout_thread_created # Note that this is both a method of module Timeout, so you can include # Timeout into your classes so they have a #timeout method, as well as # a module method, so you can call it directly as Timeout.timeout(). - def timeout(sec, klass = nil, message = nil, &block) #:yield: +sec+ + def self.timeout(sec, klass = nil, message = nil, &block) #:yield: +sec+ return yield(sec) if sec == nil or sec.zero? raise ArgumentError, "Timeout sec must be a non-negative number" if 0 > sec @@ -177,7 +178,7 @@ def timeout(sec, klass = nil, message = nil, &block) #:yield: +sec+ return scheduler.timeout_after(sec, klass || Error, message, &block) end - Timeout.ensure_timeout_thread_created + ensure_timeout_thread_created perform = Proc.new do |exc| request = Request.new(Thread.current, sec, exc, message) QUEUE_MUTEX.synchronize do @@ -197,5 +198,8 @@ def timeout(sec, klass = nil, message = nil, &block) #:yield: +sec+ Error.handle_timeout(message, &perform) end end - module_function :timeout + + private def timeout(*args, &block) + Timeout.timeout(*args, &block) + end end diff --git a/test/test_timeout.rb b/test/test_timeout.rb index 0115686..e367df7 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -4,6 +4,12 @@ class TestTimeout < Test::Unit::TestCase + def test_public_methods + assert_equal [:timeout], Timeout.private_instance_methods(false) + assert_equal [], Timeout.public_instance_methods(false) + assert_equal [:timeout], Timeout.singleton_class.public_instance_methods(false) + end + def test_work_is_done_in_same_thread_as_caller assert_equal Thread.current, Timeout.timeout(10){ Thread.current } end From 54ff671c6cba28fc25766b9d0910222c96b84080 Mon Sep 17 00:00:00 2001 From: Koichi Sasada Date: Fri, 5 Dec 2025 01:49:31 +0900 Subject: [PATCH 66/88] support Ractor 1. Introduce State to store all status. 2. Store State instance to the Ractor local storage if possible 3. Make `GET_TIME` (Method object) shareable if possible 3 is supporeted Ruby 4.0 and later, so the Rator support is works only on Ruby 4.0 and later. --- lib/timeout.rb | 166 ++++++++++++++++++++++++++++--------------- test/test_timeout.rb | 20 ++++++ 2 files changed, 127 insertions(+), 59 deletions(-) diff --git a/lib/timeout.rb b/lib/timeout.rb index f173d02..2bf3e75 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -44,12 +44,107 @@ def self.handle_timeout(message) # :nodoc: end # :stopdoc: - CONDVAR = ConditionVariable.new - QUEUE = Queue.new - QUEUE_MUTEX = Mutex.new - TIMEOUT_THREAD_MUTEX = Mutex.new - @timeout_thread = nil - private_constant :CONDVAR, :QUEUE, :QUEUE_MUTEX, :TIMEOUT_THREAD_MUTEX + + # We keep a private reference so that time mocking libraries won't break + # Timeout. + GET_TIME = + if defined?(Ractor.make_shareable) + begin + Ractor.make_shareable(Process.method(:clock_gettime)) + rescue # failed on Ruby 3.4 + Process.method(:clock_gettime) + end + else + Process.method(:clock_gettime) + end + + private_constant :GET_TIME + + class State + attr_reader :condvar, :queue, :queue_mutex # shared with Timeout.timeout() + + def initialize + @condvar = ConditionVariable.new + @queue = Queue.new + @queue_mutex = Mutex.new + + @timeout_thread = nil + @timeout_thread_mutex = Mutex.new + end + + if defined?(Ractor.store_if_absent) && + defined?(Ractor.shareble?) && Ractor.shareable?(GET_TIME) + + # Ractor support if + # 1. Ractor.store_if_absent is available + # 2. Method object can be shareable (4.0~) + + Ractor.store_if_absent :timeout_gem_state do + State.new + end + + def self.instance + Ractor[:timeout_gem_state] + end + + ::Timeout::RACTOR_SUPPORT = true # for test + else + @GLOBAL_STATE = State.new + + def self.instance + @GLOBAL_STATE + end + end + + def create_timeout_thread + watcher = Thread.new do + requests = [] + while true + until @queue.empty? and !requests.empty? # wait to have at least one request + req = @queue.pop + requests << req unless req.done? + end + closest_deadline = requests.min_by(&:deadline).deadline + + now = 0.0 + @queue_mutex.synchronize do + while (now = GET_TIME.call(Process::CLOCK_MONOTONIC)) < closest_deadline and @queue.empty? + @condvar.wait(@queue_mutex, closest_deadline - now) + end + end + + requests.each do |req| + req.interrupt if req.expired?(now) + end + requests.reject!(&:done?) + end + end + + if !watcher.group.enclosed? && (!defined?(Ractor.main?) || Ractor.main?) + ThreadGroup::Default.add(watcher) + end + + watcher.name = "Timeout stdlib thread" + watcher.thread_variable_set(:"\0__detached_thread__", true) + watcher + end + + def ensure_timeout_thread_created + unless @timeout_thread&.alive? + # If the Mutex is already owned we are in a signal handler. + # In that case, just return and let the main thread create the Timeout thread. + return if @timeout_thread_mutex.owned? + + @timeout_thread_mutex.synchronize do + unless @timeout_thread&.alive? + @timeout_thread = create_timeout_thread + end + end + end + end + end + + private_constant :State class Request attr_reader :deadline @@ -91,55 +186,6 @@ def finished end private_constant :Request - def self.create_timeout_thread - watcher = Thread.new do - requests = [] - while true - until QUEUE.empty? and !requests.empty? # wait to have at least one request - req = QUEUE.pop - requests << req unless req.done? - end - closest_deadline = requests.min_by(&:deadline).deadline - - now = 0.0 - QUEUE_MUTEX.synchronize do - while (now = GET_TIME.call(Process::CLOCK_MONOTONIC)) < closest_deadline and QUEUE.empty? - CONDVAR.wait(QUEUE_MUTEX, closest_deadline - now) - end - end - - requests.each do |req| - req.interrupt if req.expired?(now) - end - requests.reject!(&:done?) - end - end - ThreadGroup::Default.add(watcher) unless watcher.group.enclosed? - watcher.name = "Timeout stdlib thread" - watcher.thread_variable_set(:"\0__detached_thread__", true) - watcher - end - private_class_method :create_timeout_thread - - def self.ensure_timeout_thread_created - unless @timeout_thread and @timeout_thread.alive? - # If the Mutex is already owned we are in a signal handler. - # In that case, just return and let the main thread create the @timeout_thread. - return if TIMEOUT_THREAD_MUTEX.owned? - TIMEOUT_THREAD_MUTEX.synchronize do - unless @timeout_thread and @timeout_thread.alive? - @timeout_thread = create_timeout_thread - end - end - end - end - private_class_method :ensure_timeout_thread_created - - # We keep a private reference so that time mocking libraries won't break - # Timeout. - GET_TIME = Process.method(:clock_gettime) - private_constant :GET_TIME - # :startdoc: # Perform an operation in a block, raising an error if it takes longer than @@ -178,12 +224,14 @@ def self.timeout(sec, klass = nil, message = nil, &block) #:yield: +sec+ return scheduler.timeout_after(sec, klass || Error, message, &block) end - ensure_timeout_thread_created + state = State.instance + state.ensure_timeout_thread_created + perform = Proc.new do |exc| request = Request.new(Thread.current, sec, exc, message) - QUEUE_MUTEX.synchronize do - QUEUE << request - CONDVAR.signal + state.queue_mutex.synchronize do + state.queue << request + state.condvar.signal end begin return yield(sec) diff --git a/test/test_timeout.rb b/test/test_timeout.rb index e367df7..233f54e 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -280,4 +280,24 @@ def test_handling_enclosed_threadgroup }.join end; end + + def test_ractor + assert_separately(%w[-rtimeout -W0], <<-'end;') + r = Ractor.new do + Timeout.timeout(1) { 42 } + end.value + + assert_equal 42, r + + r = Ractor.new do + begin + Timeout.timeout(0.1) { sleep } + rescue Timeout::Error + :ok + end + end.value + + assert_equal :ok, r + end; + end if Timeout.const_defined?(:RACTOR_SUPPORT) end From daab9a2193a2f591c9e49b8839a592aa06f4d711 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Fri, 5 Dec 2025 19:14:28 +0100 Subject: [PATCH 67/88] Minor tweaks --- lib/timeout.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/timeout.rb b/lib/timeout.rb index 2bf3e75..3ad0193 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -57,7 +57,6 @@ def self.handle_timeout(message) # :nodoc: else Process.method(:clock_gettime) end - private_constant :GET_TIME class State @@ -89,10 +88,10 @@ def self.instance ::Timeout::RACTOR_SUPPORT = true # for test else - @GLOBAL_STATE = State.new + GLOBAL_STATE = State.new def self.instance - @GLOBAL_STATE + GLOBAL_STATE end end @@ -143,7 +142,6 @@ def ensure_timeout_thread_created end end end - private_constant :State class Request From 82fb6f69259580b34f273fc051bd4fdce50d91ef Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Fri, 5 Dec 2025 19:19:13 +0100 Subject: [PATCH 68/88] Fix condition and fix test to catch that broken condition --- lib/timeout.rb | 4 +--- test/test_timeout.rb | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/timeout.rb b/lib/timeout.rb index 3ad0193..47410af 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -72,7 +72,7 @@ def initialize end if defined?(Ractor.store_if_absent) && - defined?(Ractor.shareble?) && Ractor.shareable?(GET_TIME) + defined?(Ractor.shareable?) && Ractor.shareable?(GET_TIME) # Ractor support if # 1. Ractor.store_if_absent is available @@ -85,8 +85,6 @@ def initialize def self.instance Ractor[:timeout_gem_state] end - - ::Timeout::RACTOR_SUPPORT = true # for test else GLOBAL_STATE = State.new diff --git a/test/test_timeout.rb b/test/test_timeout.rb index 233f54e..3f94134 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -299,5 +299,5 @@ def test_ractor assert_equal :ok, r end; - end if Timeout.const_defined?(:RACTOR_SUPPORT) + end if defined?(::Ractor) && RUBY_VERSION >= '4.0' end From a1d784cb66e217e83a5e2f42e70c4b67a29cd7ed Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Fri, 5 Dec 2025 19:21:37 +0100 Subject: [PATCH 69/88] Fix logic for Ractor support * Fix indentation to stay a multiple of 2 spaces. --- lib/timeout.rb | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/lib/timeout.rb b/lib/timeout.rb index 47410af..eab1a1b 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -45,8 +45,7 @@ def self.handle_timeout(message) # :nodoc: # :stopdoc: - # We keep a private reference so that time mocking libraries won't break - # Timeout. + # We keep a private reference so that time mocking libraries won't break Timeout. GET_TIME = if defined?(Ractor.make_shareable) begin @@ -71,20 +70,15 @@ def initialize @timeout_thread_mutex = Mutex.new end - if defined?(Ractor.store_if_absent) && - defined?(Ractor.shareable?) && Ractor.shareable?(GET_TIME) - - # Ractor support if - # 1. Ractor.store_if_absent is available - # 2. Method object can be shareable (4.0~) - - Ractor.store_if_absent :timeout_gem_state do - State.new - end - - def self.instance - Ractor[:timeout_gem_state] - end + if defined?(Ractor.store_if_absent) && defined?(Ractor.shareable?) && Ractor.shareable?(GET_TIME) + # Ractor support if + # 1. Ractor.store_if_absent is available + # 2. Method object can be shareable (4.0~) + def self.instance + Ractor.store_if_absent :timeout_gem_state do + State.new + end + end else GLOBAL_STATE = State.new From 281b2507e7cd01d8b72be745fc58013168c75f2a Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Fri, 5 Dec 2025 19:25:22 +0100 Subject: [PATCH 70/88] Simplify logic to make GET_TIME shareable --- lib/timeout.rb | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/lib/timeout.rb b/lib/timeout.rb index eab1a1b..36cd0f9 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -46,16 +46,11 @@ def self.handle_timeout(message) # :nodoc: # :stopdoc: # We keep a private reference so that time mocking libraries won't break Timeout. - GET_TIME = - if defined?(Ractor.make_shareable) - begin - Ractor.make_shareable(Process.method(:clock_gettime)) - rescue # failed on Ruby 3.4 - Process.method(:clock_gettime) - end - else - Process.method(:clock_gettime) - end + GET_TIME = Process.method(:clock_gettime) + if defined?(Ractor.make_shareable) + # Ractor.make_shareable(Method) only works on Ruby 4+ + Ractor.make_shareable(GET_TIME) rescue nil + end private_constant :GET_TIME class State From 45816b1b2602278b6d6e069d36b24fd5e3437bdd Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Fri, 5 Dec 2025 23:08:48 +0100 Subject: [PATCH 71/88] Exclude constantly-failing test on x86_64-darwin * https://github.com/ruby/ruby-dev-builder/actions/runs/19973218359/job/57293388626 --- test/test_timeout.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test_timeout.rb b/test/test_timeout.rb index 3f94134..d6ae0c9 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -299,5 +299,6 @@ def test_ractor assert_equal :ok, r end; - end if defined?(::Ractor) && RUBY_VERSION >= '4.0' + end if defined?(::Ractor) && RUBY_VERSION >= '4.0' && !RUBY_PLATFORM.include?('x86_64-darwin') + # Exclude on x86_64-darwin as it failed 4 times out of 4 tries in the CI of ruby/ruby-dev-builder end From 4de4b4759c711eda7220bd96546af41ab20d82b4 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Sat, 6 Dec 2025 11:09:27 +0100 Subject: [PATCH 72/88] Test that Timeout does not expose extra constants --- test/test_timeout.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/test_timeout.rb b/test/test_timeout.rb index d6ae0c9..3be9013 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -7,7 +7,10 @@ class TestTimeout < Test::Unit::TestCase def test_public_methods assert_equal [:timeout], Timeout.private_instance_methods(false) assert_equal [], Timeout.public_instance_methods(false) + assert_equal [:timeout], Timeout.singleton_class.public_instance_methods(false) + + assert_equal [:Error, :ExitException, :VERSION], Timeout.constants.sort end def test_work_is_done_in_same_thread_as_caller From b5ad6903c79a316b8382d75147a5264d63f60933 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 03:01:58 +0000 Subject: [PATCH 73/88] Bump step-security/harden-runner from 2.13.2 to 2.13.3 Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.13.2 to 2.13.3. - [Release notes](https://github.com/step-security/harden-runner/releases) - [Commits](https://github.com/step-security/harden-runner/compare/95d9a5deda9de15063e7595e9719c11c38c90ae2...df199fb7be9f65074067a9eb93f12bb4c5547cf2) --- updated-dependencies: - dependency-name: step-security/harden-runner dependency-version: 2.13.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/push_gem.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index 261376b..048b141 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -23,7 +23,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit From 80d2e0728a81a20aa80b1e444b3582bbf04b6fe1 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 8 Dec 2025 12:04:35 +0900 Subject: [PATCH 74/88] Exclude dependabot updates from release note --- .github/release.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .github/release.yml diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 0000000..db1d8e9 --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,4 @@ +changelog: + exclude: + labels: + - dependencies # Added by Dependabot From 837d5aac73a2323d36fc03f29576e55a6a8725ce Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 8 Dec 2025 12:05:54 +0900 Subject: [PATCH 75/88] v0.5.0 --- lib/timeout.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/timeout.rb b/lib/timeout.rb index 36cd0f9..1640542 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -20,7 +20,7 @@ module Timeout # The version - VERSION = "0.4.4" + VERSION = "0.5.0" # Internal error raised to when a timeout is triggered. class ExitException < Exception From b54f91e9ddaa80e8a9407201c603d66b2fcfb392 Mon Sep 17 00:00:00 2001 From: Koichi Sasada Date: Tue, 9 Dec 2025 00:50:32 +0900 Subject: [PATCH 76/88] Revert "Exclude constantly-failing test on x86_64-darwin" This reverts commit 45816b1b2602278b6d6e069d36b24fd5e3437bdd. --- test/test_timeout.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/test_timeout.rb b/test/test_timeout.rb index 3be9013..51666b7 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -302,6 +302,5 @@ def test_ractor assert_equal :ok, r end; - end if defined?(::Ractor) && RUBY_VERSION >= '4.0' && !RUBY_PLATFORM.include?('x86_64-darwin') - # Exclude on x86_64-darwin as it failed 4 times out of 4 tries in the CI of ruby/ruby-dev-builder + end if defined?(::Ractor) && RUBY_VERSION >= '4.0' end From a52720e82a9b66850650c822967c6d59c8e3bfd1 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Thu, 11 Dec 2025 11:20:43 +0100 Subject: [PATCH 77/88] Reset the interrupt mask when creating the Timeout thread * Add tests related to Thread.handle_interrupt * Fixes https://github.com/ruby/timeout/issues/41 --- lib/timeout.rb | 50 ++++++++++--------- test/test_timeout.rb | 113 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+), 23 deletions(-) diff --git a/lib/timeout.rb b/lib/timeout.rb index 1640542..0260fd1 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -83,36 +83,40 @@ def self.instance end def create_timeout_thread - watcher = Thread.new do - requests = [] - while true - until @queue.empty? and !requests.empty? # wait to have at least one request - req = @queue.pop - requests << req unless req.done? - end - closest_deadline = requests.min_by(&:deadline).deadline + # Threads unexpectedly inherit the interrupt mask: https://github.com/ruby/timeout/issues/41 + # So reset the interrupt mask to the default one for the timeout thread + Thread.handle_interrupt(Object => :immediate) do + watcher = Thread.new do + requests = [] + while true + until @queue.empty? and !requests.empty? # wait to have at least one request + req = @queue.pop + requests << req unless req.done? + end + closest_deadline = requests.min_by(&:deadline).deadline - now = 0.0 - @queue_mutex.synchronize do - while (now = GET_TIME.call(Process::CLOCK_MONOTONIC)) < closest_deadline and @queue.empty? - @condvar.wait(@queue_mutex, closest_deadline - now) + now = 0.0 + @queue_mutex.synchronize do + while (now = GET_TIME.call(Process::CLOCK_MONOTONIC)) < closest_deadline and @queue.empty? + @condvar.wait(@queue_mutex, closest_deadline - now) + end end - end - requests.each do |req| - req.interrupt if req.expired?(now) + requests.each do |req| + req.interrupt if req.expired?(now) + end + requests.reject!(&:done?) end - requests.reject!(&:done?) end - end - if !watcher.group.enclosed? && (!defined?(Ractor.main?) || Ractor.main?) - ThreadGroup::Default.add(watcher) - end + if !watcher.group.enclosed? && (!defined?(Ractor.main?) || Ractor.main?) + ThreadGroup::Default.add(watcher) + end - watcher.name = "Timeout stdlib thread" - watcher.thread_variable_set(:"\0__detached_thread__", true) - watcher + watcher.name = "Timeout stdlib thread" + watcher.thread_variable_set(:"\0__detached_thread__", true) + watcher + end end def ensure_timeout_thread_created diff --git a/test/test_timeout.rb b/test/test_timeout.rb index 51666b7..01beadb 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -4,6 +4,12 @@ class TestTimeout < Test::Unit::TestCase + private def kill_timeout_thread + thread = Timeout.const_get(:State).instance.instance_variable_get(:@timeout_thread) + thread.kill + thread.join + end + def test_public_methods assert_equal [:timeout], Timeout.private_instance_methods(false) assert_equal [], Timeout.public_instance_methods(false) @@ -221,6 +227,24 @@ def o.each end end + def test_handle_interrupt_with_exception_class + bug11344 = '[ruby-dev:49179] [Bug #11344]' + ok = false + assert_raise(Timeout::Error) { + Thread.handle_interrupt(Timeout::Error => :never) { + Timeout.timeout(0.01, Timeout::Error) { + sleep 0.2 + ok = true + Thread.handle_interrupt(Timeout::Error => :on_blocking) { + sleep 0.2 + raise "unreachable" + } + } + } + } + assert(ok, bug11344) + end + def test_handle_interrupt bug11344 = '[ruby-dev:49179] [Bug #11344]' ok = false @@ -231,6 +255,7 @@ def test_handle_interrupt ok = true Thread.handle_interrupt(Timeout::ExitException => :on_blocking) { sleep 0.2 + raise "unreachable" } } } @@ -238,6 +263,94 @@ def test_handle_interrupt assert(ok, bug11344) end + def test_handle_interrupt_with_interrupt_mask_inheritance + issue = 'https://github.com/ruby/timeout/issues/41' + + [ + -> {}, # not blocking so no opportunity to interrupt + -> { sleep 5 } + ].each_with_index do |body, idx| + # We need to create a new Timeout thread + kill_timeout_thread + + # Create the timeout thread under a handle_interrupt(:never) + # due to the interrupt mask being inherited + Thread.handle_interrupt(Object => :never) { + assert_equal :ok, Timeout.timeout(1) { :ok } + } + + # Ensure a simple timeout works and the interrupt mask was not inherited + assert_raise(Timeout::Error) { + Timeout.timeout(0.001) { sleep 1 } + } + + r = [] + # This raises Timeout::ExitException and not Timeout::Error for the non-blocking body + # because of the handle_interrupt(:never) which delays raising Timeout::ExitException + # on the main thread until getting outside of that handle_interrupt(:never) call. + # For this reason we document handle_interrupt(Timeout::ExitException) should not be used. + exc = idx == 0 ? Timeout::ExitException : Timeout::Error + assert_raise(exc) { + Thread.handle_interrupt(Timeout::ExitException => :never) { + Timeout.timeout(0.1) do + sleep 0.2 + r << :sleep_before_done + Thread.handle_interrupt(Timeout::ExitException => :on_blocking) { + r << :body + body.call + } + ensure + sleep 0.2 + r << :ensure_sleep_done + end + } + } + assert_equal([:sleep_before_done, :body, :ensure_sleep_done], r, issue) + end + end + + # Same as above but with an exception class + def test_handle_interrupt_with_interrupt_mask_inheritance_with_exception_class + issue = 'https://github.com/ruby/timeout/issues/41' + + [ + -> {}, # not blocking so no opportunity to interrupt + -> { sleep 5 } + ].each do |body| + # We need to create a new Timeout thread + kill_timeout_thread + + # Create the timeout thread under a handle_interrupt(:never) + # due to the interrupt mask being inherited + Thread.handle_interrupt(Object => :never) { + assert_equal :ok, Timeout.timeout(1) { :ok } + } + + # Ensure a simple timeout works and the interrupt mask was not inherited + assert_raise(Timeout::Error) { + Timeout.timeout(0.001) { sleep 1 } + } + + r = [] + assert_raise(Timeout::Error) { + Thread.handle_interrupt(Timeout::Error => :never) { + Timeout.timeout(0.1, Timeout::Error) do + sleep 0.2 + r << :sleep_before_done + Thread.handle_interrupt(Timeout::Error => :on_blocking) { + r << :body + body.call + } + ensure + sleep 0.2 + r << :ensure_sleep_done + end + } + } + assert_equal([:sleep_before_done, :body, :ensure_sleep_done], r, issue) + end + end + def test_fork omit 'fork not supported' unless Process.respond_to?(:fork) r, w = IO.pipe From 7cfa5a677838908180a299349cfb6e1977caae63 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Thu, 11 Dec 2025 12:29:09 +0100 Subject: [PATCH 78/88] Revise Timeout.timeout docs and add a section about `ensure` --- lib/timeout.rb | 59 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 52 insertions(+), 7 deletions(-) diff --git a/lib/timeout.rb b/lib/timeout.rb index 0260fd1..4281048 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -22,7 +22,7 @@ module Timeout # The version VERSION = "0.5.0" - # Internal error raised to when a timeout is triggered. + # Internal exception raised to when a timeout is triggered. class ExitException < Exception def exception(*) # :nodoc: self @@ -177,7 +177,7 @@ def finished # :startdoc: - # Perform an operation in a block, raising an error if it takes longer than + # Perform an operation in a block, raising an exception if it takes longer than # +sec+ seconds to complete. # # +sec+:: Number of seconds to wait for the block to terminate. Any non-negative number @@ -190,12 +190,18 @@ def finished # Omitting will use the default, "execution expired" # # Returns the result of the block *if* the block completed before - # +sec+ seconds, otherwise throws an exception, based on the value of +klass+. + # +sec+ seconds, otherwise raises an exception, based on the value of +klass+. # - # The exception thrown to terminate the given block cannot be rescued inside - # the block unless +klass+ is given explicitly. However, the block can use - # ensure to prevent the handling of the exception. For that reason, this - # method cannot be relied on to enforce timeouts for untrusted blocks. + # The exception raised to terminate the given block is the given +klass+, or + # Timeout::ExitException if +klass+ is not given. The reason for that behavior + # is that Timeout::Error inherits from RuntimeError and might be caught unexpectedly by `rescue`. + # Timeout::ExitException inherits from Exception so it will only be rescued by `rescue Exception`. + # Note that the Timeout::ExitException is translated to a Timeout::Error once it reaches the Timeout.timeout call, + # so outside that call it will be a Timeout::Error. + # + # In general, be aware that the code block may rescue the exception, and in such a case not respect the timeout. + # Also, the block can use +ensure+ to prevent the handling of the exception. + # For those reasons, this method cannot be relied on to enforce timeouts for untrusted blocks. # # If a scheduler is defined, it will be used to handle the timeout by invoking # Scheduler#timeout_after. @@ -203,6 +209,45 @@ def finished # Note that this is both a method of module Timeout, so you can include # Timeout into your classes so they have a #timeout method, as well as # a module method, so you can call it directly as Timeout.timeout(). + # + # ==== Ensuring the exception does not fire inside ensure blocks + # + # When using Timeout.timeout it can be desirable to ensure the timeout exception does not fire inside an +ensure+ block. + # The simplest and best way to do so it to put the Timeout.timeout call inside the body of the begin/ensure/end: + # + # begin + # Timeout.timeout(sec) { some_long_operation } + # ensure + # cleanup # safe, cannot be interrupt by timeout + # end + # + # If that is not feasible, e.g. if there are +ensure+ blocks inside +some_long_operation+, + # they need to not be interrupted by timeout, and it's not possible to move these ensure blocks outside, + # one can use Thread.handle_interrupt to delay the timeout exception like so: + # + # Thread.handle_interrupt(Timeout::Error => :never) { + # Timeout.timeout(sec, Timeout::Error) do + # setup # timeout cannot happen here, no matter how long it takes + # Thread.handle_interrupt(Timeout::Error => :immediate) { + # some_long_operation # timeout can happen here + # } + # ensure + # cleanup # timeout cannot happen here, no matter how long it takes + # end + # } + # + # An important thing to note is the need to pass an exception klass to Timeout.timeout, + # otherwise it does not work. Specifically, using +Thread.handle_interrupt(Timeout::ExitException => ...)+ + # is unsupported and causes subtle errors like raising the wrong exception outside the block, do not use that. + # + # Note that Thread.handle_interrupt is somewhat dangerous because if setup or cleanup hangs + # then the current thread will hang too and the timeout will never fire. + # Also note the block might run for longer than +sec+ seconds: + # e.g. some_long_operation executes for +sec+ seconds + whatever time cleanup takes. + # + # If you want the timeout to only happen on blocking operations one can use :on_blocking + # instead of :immediate. However, that means if the block uses no blocking operations after +sec+ seconds, + # the block will not be interrupted. def self.timeout(sec, klass = nil, message = nil, &block) #:yield: +sec+ return yield(sec) if sec == nil or sec.zero? raise ArgumentError, "Timeout sec must be a non-negative number" if 0 > sec From cb2ba88fedb46cb5352d859fb31d308867aa4240 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Thu, 11 Dec 2025 14:26:08 +0100 Subject: [PATCH 79/88] Encapsulate adding a timeout Request --- lib/timeout.rb | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/timeout.rb b/lib/timeout.rb index 4281048..6aa938c 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -54,8 +54,6 @@ def self.handle_timeout(message) # :nodoc: private_constant :GET_TIME class State - attr_reader :condvar, :queue, :queue_mutex # shared with Timeout.timeout() - def initialize @condvar = ConditionVariable.new @queue = Queue.new @@ -132,6 +130,13 @@ def ensure_timeout_thread_created end end end + + def add_request(request) + @queue_mutex.synchronize do + @queue << request + @condvar.signal + end + end end private_constant :State @@ -263,10 +268,7 @@ def self.timeout(sec, klass = nil, message = nil, &block) #:yield: +sec+ perform = Proc.new do |exc| request = Request.new(Thread.current, sec, exc, message) - state.queue_mutex.synchronize do - state.queue << request - state.condvar.signal - end + state.add_request(request) begin return yield(sec) ensure From 1a499a8f9647064878fe184052b8a2ea721749e4 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Thu, 11 Dec 2025 14:16:49 +0100 Subject: [PATCH 80/88] Make Timeout.timeout work in a trap handler on CRuby * Fixes https://github.com/ruby/timeout/issues/17 --- lib/timeout.rb | 28 +++++++++++++++++++++++++--- test/test_timeout.rb | 29 +++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/lib/timeout.rb b/lib/timeout.rb index 6aa938c..9969fa2 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -123,7 +123,7 @@ def ensure_timeout_thread_created # In that case, just return and let the main thread create the Timeout thread. return if @timeout_thread_mutex.owned? - @timeout_thread_mutex.synchronize do + Sync.synchronize @timeout_thread_mutex do unless @timeout_thread&.alive? @timeout_thread = create_timeout_thread end @@ -132,7 +132,7 @@ def ensure_timeout_thread_created end def add_request(request) - @queue_mutex.synchronize do + Sync.synchronize @queue_mutex do @queue << request @condvar.signal end @@ -153,6 +153,7 @@ def initialize(thread, timeout, exception_class, message) @done = false # protected by @mutex end + # Only called by the timeout thread, so does not need Sync.synchronize def done? @mutex.synchronize do @done @@ -163,6 +164,7 @@ def expired?(now) now >= @deadline end + # Only called by the timeout thread, so does not need Sync.synchronize def interrupt @mutex.synchronize do unless @done @@ -173,13 +175,33 @@ def interrupt end def finished - @mutex.synchronize do + Sync.synchronize @mutex do @done = true end end end private_constant :Request + module Sync + # Calls mutex.synchronize(&block) but if that fails on CRuby due to being in a trap handler, + # run mutex.synchronize(&block) in a separate Thread instead. + def self.synchronize(mutex, &block) + begin + mutex.synchronize(&block) + rescue ThreadError => e + raise e unless e.message == "can't be called from trap context" + # Workaround CRuby issue https://bugs.ruby-lang.org/issues/19473 + # which raises on Mutex#synchronize in trap handler. + # It's expensive to create a Thread just for this, + # but better than failing. + Thread.new { + mutex.synchronize(&block) + }.join + end + end + end + private_constant :Sync + # :startdoc: # Perform an operation in a block, raising an exception if it takes longer than diff --git a/test/test_timeout.rb b/test/test_timeout.rb index 01beadb..7421b5b 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -416,4 +416,33 @@ def test_ractor assert_equal :ok, r end; end if defined?(::Ractor) && RUBY_VERSION >= '4.0' + + def test_timeout_in_trap_handler + # https://github.com/ruby/timeout/issues/17 + + # Test as if this was the first timeout usage + kill_timeout_thread + + rd, wr = IO.pipe + + trap("SIGUSR1") do + begin + Timeout.timeout(0.1) do + sleep 1 + end + rescue Timeout::Error + wr.write "OK" + wr.close + else + wr.write "did not raise" + ensure + wr.close + end + end + + Process.kill :USR1, Process.pid + + assert_equal "OK", rd.read + rd.close + end end From b19043e8d03811f6a2a0d67e07a9da302c2ad352 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Thu, 11 Dec 2025 21:19:25 +0100 Subject: [PATCH 81/88] Skip signal test on windows Windows has no SIGUSR1. There might be another usable signal, but this is breaking ruby master so I just want a quick fix for now. --- test/test_timeout.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_timeout.rb b/test/test_timeout.rb index 7421b5b..199b18e 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -444,5 +444,5 @@ def test_timeout_in_trap_handler assert_equal "OK", rd.read rd.close - end + end if Signal.list["USR1"] # Windows has no SIGUSR1 end From c8d63ce3fecc201cc19303f766cfc81f0cada921 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Thu, 11 Dec 2025 21:24:19 +0100 Subject: [PATCH 82/88] Add windows to CI matrix --- .github/workflows/test.yml | 5 +++++ test/test_timeout.rb | 8 +++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f3f76ab..21f7f47 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,6 +17,11 @@ jobs: matrix: ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} os: [ ubuntu-latest, macos-latest ] + include: + - os: windows-latest + ruby: 2.6 + - os: windows-latest + ruby: 3.4 exclude: - os: macos-latest ruby: truffleruby diff --git a/test/test_timeout.rb b/test/test_timeout.rb index 199b18e..42db4eb 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -425,7 +425,9 @@ def test_timeout_in_trap_handler rd, wr = IO.pipe - trap("SIGUSR1") do + signal = Signal.list["USR1"] ? :USR1 : :TERM + + trap(signal) do begin Timeout.timeout(0.1) do sleep 1 @@ -440,9 +442,9 @@ def test_timeout_in_trap_handler end end - Process.kill :USR1, Process.pid + Process.kill signal, Process.pid assert_equal "OK", rd.read rd.close - end if Signal.list["USR1"] # Windows has no SIGUSR1 + end end From e5bc1de901ed40227c462863a16f1e13715f9228 Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Thu, 11 Dec 2025 17:12:03 -0500 Subject: [PATCH 83/88] Fix failing timeout test ``` Run options: "--ruby=./miniruby -I../ruby/lib -I. -I.ext/common ../ruby/tool/runruby.rb --extout=.ext -- --disable-gems" --excludes-dir=../ruby/test/.excludes --name=!/memory_leak/ --seed=9843 [ 1/31] TestTimeout#test_timeout_in_trap_handler = 0.00 s 1) Error: TestTimeout#test_timeout_in_trap_handler: NoMethodError: undefined method 'kill' for nil /Users/luke/workspace/ruby-dev/ruby/test/test_timeout.rb:9:in 'TestTimeout#kill_timeout_thread' /Users/luke/workspace/ruby-dev/ruby/test/test_timeout.rb:424:in 'TestTimeout#test_timeout_in_trap_handler' Finished tests in 2.715032s, 11.4179 tests/s, 52.3014 assertions/s. 31 tests, 142 assertions, 0 failures, 1 errors, 0 skips ruby -v: ruby 4.0.0dev (2025-12-11T21:56:23Z fix_timeout_test 1c5eacbf9a) +PRISM [arm64-darwin24] make: *** [yes-test-all] Error 1 ``` --- test/test_timeout.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/test_timeout.rb b/test/test_timeout.rb index 42db4eb..9194008 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -6,8 +6,10 @@ class TestTimeout < Test::Unit::TestCase private def kill_timeout_thread thread = Timeout.const_get(:State).instance.instance_variable_get(:@timeout_thread) - thread.kill - thread.join + if thread + thread.kill + thread.join + end end def test_public_methods From 4ae8631acfe8fb94964b8f3c56c1d3b4e8a92be7 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Fri, 12 Dec 2025 09:35:55 +0100 Subject: [PATCH 84/88] Restore original signal handler in test_timeout_in_trap_handler --- test/test_timeout.rb | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/test/test_timeout.rb b/test/test_timeout.rb index 9194008..fead81f 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -427,9 +427,9 @@ def test_timeout_in_trap_handler rd, wr = IO.pipe - signal = Signal.list["USR1"] ? :USR1 : :TERM + signal = :TERM - trap(signal) do + original_handler = trap(signal) do begin Timeout.timeout(0.1) do sleep 1 @@ -444,9 +444,13 @@ def test_timeout_in_trap_handler end end - Process.kill signal, Process.pid + begin + Process.kill signal, Process.pid - assert_equal "OK", rd.read - rd.close + assert_equal "OK", rd.read + rd.close + ensure + trap(signal, original_handler) + end end end From 549605db39602a4fc351165d061e70953b03bb9c Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Thu, 11 Dec 2025 23:06:59 +0100 Subject: [PATCH 85/88] Run on Windows for all versions and remove old excludes --- .github/workflows/test.yml | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 21f7f47..a0de3ed 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,16 +16,11 @@ jobs: fail-fast: false matrix: ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} - os: [ ubuntu-latest, macos-latest ] - include: - - os: windows-latest - ruby: 2.6 - - os: windows-latest - ruby: 3.4 + os: [ ubuntu-latest, macos-latest, windows-latest ] exclude: - - os: macos-latest + - os: windows-latest ruby: truffleruby - - os: ubuntu-latest + - os: windows-latest ruby: truffleruby-head runs-on: ${{ matrix.os }} steps: From 179ea36ef1849c7fe1bd72c4ecd418f2bba5bb3e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 03:01:53 +0000 Subject: [PATCH 86/88] Bump step-security/harden-runner from 2.13.3 to 2.14.0 Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.13.3 to 2.14.0. - [Release notes](https://github.com/step-security/harden-runner/releases) - [Commits](https://github.com/step-security/harden-runner/compare/df199fb7be9f65074067a9eb93f12bb4c5547cf2...20cf305ff2072d973412fa9b1e3a4f227bda3c76) --- updated-dependencies: - dependency-name: step-security/harden-runner dependency-version: 2.14.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/push_gem.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index 048b141..9b94bbd 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -23,7 +23,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0 with: egress-policy: audit From ab79dfff47092008ce08520763c846eba3a3a5f1 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 17 Dec 2025 13:23:04 +0900 Subject: [PATCH 87/88] v0.6.0 --- lib/timeout.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/timeout.rb b/lib/timeout.rb index 9969fa2..5d1f61d 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -20,7 +20,7 @@ module Timeout # The version - VERSION = "0.5.0" + VERSION = "0.6.0" # Internal exception raised to when a timeout is triggered. class ExitException < Exception From fef9d07f44d00ad9f0b04faa1adfeee15de5b539 Mon Sep 17 00:00:00 2001 From: t-mangoe Date: Sun, 21 Dec 2025 09:16:58 +0900 Subject: [PATCH 88/88] add test case for string argument --- test/test_timeout.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/test_timeout.rb b/test/test_timeout.rb index fead81f..b11fc92 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -54,6 +54,12 @@ def test_raise_for_neg_second end end + def test_raise_for_string_argument + assert_raise(NoMethodError) do + Timeout.timeout("1") { sleep(0.01) } + end + end + def test_included c = Class.new do include Timeout