summaryrefslogtreecommitdiff
path: root/test/ruby
AgeCommit message (Collapse)Author
2025-05-31skip flaky testKoichi Sasada
``` 1) Failure: TestProcess#test_warmup_frees_pages [/tmp/ruby/src/trunk-random1/test/ruby/test_process.rb:2772]: <164348> expected but was <165985>. ``` can someone investigate it? Notes: Merged: https://github.com/ruby/ruby/pull/13478
2025-05-31`Ractor::Port`Koichi Sasada
* Added `Ractor::Port` * `Ractor::Port#receive` (support multi-threads) * `Rcator::Port#close` * `Ractor::Port#closed?` * Added some methods * `Ractor#join` * `Ractor#value` * `Ractor#monitor` * `Ractor#unmonitor` * Removed some methods * `Ractor#take` * `Ractor.yield` * Change the spec * `Racotr.select` You can wait for multiple sequences of messages with `Ractor::Port`. ```ruby ports = 3.times.map{ Ractor::Port.new } ports.map.with_index do |port, ri| Ractor.new port,ri do |port, ri| 3.times{|i| port << "r#{ri}-#{i}"} end end p ports.each{|port| pp 3.times.map{port.receive}} ``` In this example, we use 3 ports, and 3 Ractors send messages to them respectively. We can receive a series of messages from each port. You can use `Ractor#value` to get the last value of a Ractor's block: ```ruby result = Ractor.new do heavy_task() end.value ``` You can wait for the termination of a Ractor with `Ractor#join` like this: ```ruby Ractor.new do some_task() end.join ``` `#value` and `#join` are similar to `Thread#value` and `Thread#join`. To implement `#join`, `Ractor#monitor` (and `Ractor#unmonitor`) is introduced. This commit changes `Ractor.select()` method. It now only accepts ports or Ractors, and returns when a port receives a message or a Ractor terminates. We removes `Ractor.yield` and `Ractor#take` because: * `Ractor::Port` supports most of similar use cases in a simpler manner. * Removing them significantly simplifies the code. We also change the internal thread scheduler code (thread_pthread.c): * During barrier synchronization, we keep the `ractor_sched` lock to avoid deadlocks. This lock is released by `rb_ractor_sched_barrier_end()` which is called at the end of operations that require the barrier. * fix potential deadlock issues by checking interrupts just before setting UBF. https://bugs.ruby-lang.org/issues/21262 Notes: Merged: https://github.com/ruby/ruby/pull/13445
2025-05-29Fix memory leak with invalid yield in prismPeter Zhu
[Bug #21383] The following script leaks memory: 10.times do 20_000.times do eval("class C; yield; end") rescue SyntaxError end puts `ps -o rss= -p #{$$}` end Before: 16464 25536 29424 35904 39552 44576 46736 51600 56096 59824 After: 13488 16160 18240 20528 19760 21808 21680 22272 22064 22336 Notes: Merged: https://github.com/ruby/ruby/pull/13464
2025-05-29[Bug #21380] Prohibit modification in String#split blockNobuyoshi Nakada
Reported at https://hackerone.com/reports/3163876 Notes: Merged: https://github.com/ruby/ruby/pull/13462
2025-05-23Change test to avoid stack overflow with MN threadsÉtienne Barrié
When using MN threads (such as running the test in a ractor), this test failed because it was raising a SystemStackError: stack level too deep. This is because the machine stack is smaller under MN threads than on the native main thread. Notes: Merged: https://github.com/ruby/ruby/pull/13427
2025-05-23Fix 'require' from a ractor when the required file raises an errorLuke Gruber
If you catch an error that was raised from a file you required in a ractor, that error did not have its belonging reset from the main ractor to the current ractor, so you hit assertion errors in debug mode. Notes: Merged: https://github.com/ruby/ruby/pull/13428
2025-05-23skip the test now we couldn't fixKoichi Sasada
The following error is reported repeatedly on my riscv64-linux machine, so just skipt it. I hope someone investigate it. ``` 1) Error: TestStruct::SubStruct#test_named_structs_are_not_rooted: Test::Unit::ProxyError: execution of Test::Unit::CoreAssertions#assert_no_memory_leak expired timeout (10 sec) pid 1113858 killed by SIGTERM (signal 15) | ruby 3.5.0dev (2025-05-22T21:05:12Z master 9583b7af8f) +PRISM [riscv64-linux] | | [7;1m1096282:1747967727.622:d70f:[mSTART={peak:453828608,size:453763072,lck:0,pin:0,hwm:9601024,rss:9601024,data:445943808,stk:135168,exe:4096,lib:7450624,pte:77824,swap:0} | [7;1m1096282:1747967727.622:d70f:[mFINAL={peak:457502720,size:457498624,lck:0,pin:0,hwm:13312000,rss:13312000,data:449679360,stk:135168,exe:4096,lib:7450624,pte:86016,swap:0} ``` Notes: Merged: https://github.com/ruby/ruby/pull/13422
2025-05-22[Bug #21357] Fix crash in Hash#merge with blockDaniel Colson
Prior to https://github.com/ruby/ruby/commit/49b306ecb9e2e9e06e0b1590bacc5f4b38169c3c the `optional_arg` passed from `rb_hash_update_block_i` to `tbl_update` was a hash value (i.e. a VALUE). After that commit it changed to an `update_call_args`. If the block sets or changes the value, `tbl_update_modify` will set the `arg.value` back to an actual value and we won't crash. But in the case where the block returns the original value we end up calling `RB_OBJ_WRITTEN` with the `update_call_args` which is not expected and may crash. `arg.value` appears to only be used to pass to `RB_OBJ_WRITTEN` (others who need the `update_call_args` get it from `arg.arg`), so I don't think it needs to be set to anything upfront. And `tbl_update_modify` will set the `arg.value` in the cases we need the write barrier. Notes: Merged: https://github.com/ruby/ruby/pull/13404
2025-05-21Use rb_inspect for Ractor errorJohn Hawthorn
Previously the object was used directly, which calls `to_s` if defined. We should use rb_inspect to get a value suitable for display to the programmer. Notes: Merged: https://github.com/ruby/ruby/pull/13389
2025-05-21Fix one-by-one error of numbered parameter IDRichard Böhme
Notes: Merged: https://github.com/ruby/ruby/pull/13395
2025-05-16rb_gc_impl_copy_finalizer: generate a new object idJean Boussier
Fix a regression introduced by: https://github.com/ruby/ruby/pull/13155 Notes: Merged: https://github.com/ruby/ruby/pull/13350
2025-05-16Prevent enabling yjit when zjit enabled (GH-13358)Daniel Colson
`ruby --yjit --zjit` already warns and exits, but it was still possible to enable both with `ruby --zjit -e 'RubyVM:YJIT.enable`. This commit prevents that with a warning and an early return. (We could also exit, but that seems a bit unfriendly once we're already running the program.) Co-authored-by: ywenc <ywenc@github.com> Notes: Merged: https://github.com/ruby/ruby/pull/13358 Merged-By: XrXr
2025-05-17Add a test case for `it` in a regex.Nick Dower
Co-authored-by: Alan Wu <XrXr@users.noreply.github.com> Notes: Merged: https://github.com/ruby/ruby/pull/13360
2025-05-17[Bug #21313] Handle `it` in rescue and ensure blocks.Nick Dower
The following is crashing for me: ```shell ruby --yjit --yjit-call-threshold=1 -e '1.tap { raise rescue p it }' ruby: YJIT has panicked. More info to follow... thread '<unnamed>' panicked at ./yjit/src/codegen.rs:2402:14: ... ``` It seems `it` sometimes points to the wrong value: ```shell ruby -e '1.tap { raise rescue p it }' false ruby -e '1.tap { begin; raise; ensure; p it; end } rescue nil' false ``` But only when `$!` is set: ```shell ruby -e '1.tap { begin; nil; ensure; p it; end }' 1 ruby -e '1.tap { begin; nil; rescue; ensure; p it; end }' 1 ruby -e '1.tap { begin; raise; rescue; ensure; p it; end }' 1 ``` Notes: Merged: https://github.com/ruby/ruby/pull/13360
2025-05-16[Bug #20009] Support marshaling non-ASCII name class/moduleNobuyoshi Nakada
Notes: Merged: https://github.com/ruby/ruby/pull/13362
2025-05-16Prevent double free for too big repetition quantifiers (#13332)Hiroya Fujinami
Prevent double free for too big repetition quantifiers The previous implementation calls `free(node)` twice (on parsing and compiling a regexp) when it has an error, so it leads to a double-free issue. This commit enforces `free(node)` once by introducing a temporal pointer to hold parsing nodes. Notes: Merged-By: makenowjust <make.just.on@gmail.com>
2025-05-15Maintain same behavior regardless of tracepoint stateAaron Patterson
Always use opt_new behavior regardless of tracepoint state. Notes: Merged: https://github.com/ruby/ruby/pull/13232
2025-05-15Ensure that forked process do not see invalid blocking operations. (#13343)Samuel Williams
Notes: Merged-By: ioquatix <samuel@codeotaku.com>
2025-05-15[Bug #21333] Prohibit hash modification inside Hash#update blockNobuyoshi Nakada
Notes: Merged: https://github.com/ruby/ruby/pull/13344
2025-05-14[Bug #21331] Prohibit hash modification during stlike loopNobuyoshi Nakada
Notes: Merged: https://github.com/ruby/ruby/pull/13329
2025-05-14Revert "[Bug #21331] Prohibit modification during stlike loop"Nobuyoshi Nakada
This reverts commit bb180b87b43c45e17ff49735a26d7a188d5c8396, which caused "malloc during GC" error on wasm. Notes: Merged: https://github.com/ruby/ruby/pull/13329
2025-05-13[Bug #21331] Prohibit modification during stlike loopNobuyoshi Nakada
Notes: Merged: https://github.com/ruby/ruby/pull/13317
2025-05-13Make `waiting_fd` behaviour per-IO. (#13127)Samuel Williams
- `rb_thread_fd_close` is deprecated and now a no-op. - IO operations (including close) no longer take a vm-wide lock. Notes: Merged-By: ioquatix <samuel@codeotaku.com>
2025-05-13variable.c: Refactor rb_obj_field_* to take shape_id_tJean Boussier
Notes: Merged: https://github.com/ruby/ruby/pull/13314
2025-05-12Remove respond_to check from Class#bind_callJohn Hawthorn
Notes: Merged: https://github.com/ruby/ruby/pull/13116
2025-05-12test_object_id.rb: use better randomnessJean Boussier
When the test is repeated 20 or more times in the same process it's not that unlikely for `rand(100_000)` to return the same thing twice, causing `TestObjectIdTooComplexClass` to fail. ``` 1) Failure: TestObjectIdTooComplexClass#test_dup_with_id_and_ivar [/tmp/ruby/src/trunk-repeat20-asserts/test/ruby/test_object_id.rb:172]: Expected #<struct RubyVM::Shape id=6783, parent_id=6774, edge_name=:@___26417, next_field_index=2, heap_index=0, type=1, capacity=7> to be too_complex?. ```
2025-05-11Allow T_CLASS and generic types to be too_complexJean Boussier
The intial complex shape implementation never allowed objects other than T_OBJECT to become too complex, unless we run out of shapes. I don't see any reason to prevent that. Ref: https://github.com/ruby/ruby/pull/6931 Notes: Merged: https://github.com/ruby/ruby/pull/13301
2025-05-11Skip mmtk/i686 tests for a whileSatoshi Tagomori
2025-05-11avoid calling ZALLOC after NEWOBJ_OF for RClass: need to return RClass not ↵Satoshi Tagomori
promoted
2025-05-11namespace on readSatoshi Tagomori
2025-05-09Fix warnings in tests for ObjectSpace._id2refPeter Zhu
There's a lot of warnings when running test_objectspace.rb because of ObjectSpace._id2ref. For example: test_objectspace.rb:19: warning: ObjectSpace._id2ref is deprecated Notes: Merged: https://github.com/ruby/ruby/pull/13293
2025-05-09ZJIT: Fix a splitting condition for LHSTakashi Kokubun
Notes: Merged: https://github.com/ruby/ruby/pull/13279
2025-05-08Fix `remove_instance_variable` on complex objectsJean Boussier
Introduced in: https://github.com/ruby/ruby/pull/13159 Now that there is no longer a unique TOO_COMPLEX shape with no children, checking `shape->type == TOO_COMPLEX` is incorrect. Notes: Merged: https://github.com/ruby/ruby/pull/13280
2025-05-08Make test/ruby/test_env.rb#test_delete_if_in_ractor easier to debugJean Boussier
2025-05-08Move `object_id` in object fields.Jean Boussier
And get rid of the `obj_to_id_tbl` It's no longer needed, the `object_id` is now stored inline in the object alongside instance variables. We still need the inverse table in case `_id2ref` is invoked, but we lazily build it by walking the heap if that happens. The `object_id` concern is also no longer a GC implementation concern, but a generic implementation. Co-Authored-By: Matt Valentine-House <matt@eightbitraptor.com> Notes: Merged: https://github.com/ruby/ruby/pull/13159
2025-05-08Rename `ivptr` -> `fields`, `next_iv_index` -> `next_field_index`Jean Boussier
Ivars will longer be the only thing stored inline via shapes, so keeping the `iv_index` and `ivptr` names would be confusing. Instance variables won't be the only thing stored inline via shapes, so keeping the `ivptr` name would be confusing. `field` encompass anything that can be stored in a VALUE array. Similarly, `gen_ivtbl` becomes `gen_fields_tbl`. Notes: Merged: https://github.com/ruby/ruby/pull/13159
2025-05-04Handle mutating of array passed to Set.new during iterationJeremy Evans
This avoids a heap-use-after-free. Fixes [Bug #21306] Notes: Merged: https://github.com/ruby/ruby/pull/13253
2025-05-04Handle mutation of array being merged into setJeremy Evans
Check length of array during every iteration, as a #hash method could truncate the array, resulting in heap-use-after-free. Fixes [Bug #21305] Notes: Merged: https://github.com/ruby/ruby/pull/13253
2025-05-01Omit tests using ISeq#to_binary under coverage measurementYusuke Endoh
... because ISeq#to_binary does not work Notes: Merged: https://github.com/ruby/ruby/pull/13225
2025-04-29test/ruby/test_set.rb: mmtk doesn't have GC.compactJean Boussier
2025-04-29opt_new needs to happen after safe navigationAaron Patterson
If safe navigation instructions happen first, we get a stack inconsistency error. Notes: Merged: https://github.com/ruby/ruby/pull/13205
2025-04-29Don't call hash tombstone compaction from GC compactionAaron Patterson
Tombstone removal may possibly require allocation, and we're not allowed to allocate during GC. This commit also renames `set_compact` to `set_update_references` to differentiate tombstone removal compaction with GC object compaction. Co-Authored-By: Max Bernstein <max.bernstein@shopify.com> Co-authored-by: Jean Boussier <jean.boussier@gmail.com> Notes: Merged: https://github.com/ruby/ruby/pull/13206
2025-04-29ZJIT: Disable ZJIT instructions when USE_ZJIT is 0 (#13199)Takashi Kokubun
* ZJIT: Disable ZJIT instructions when USE_ZJIT is 0 * Test the order of ZJIT instructions * Add more jobs that disable JITs * Show instruction names in the message Notes: Merged-By: k0kubun <takashikkbn@gmail.com>
2025-04-29Add testsMax Bernstein
Notes: Merged: https://github.com/ruby/ruby/pull/13198
2025-04-28Support Marshal.{dump,load} for core SetJeremy Evans
This was missed when adding core Set, because it's handled implicitly for T_OBJECT. Keep marshal compatibility between core Set and stdlib Set, so you can unmarshal core Set with stdlib Set and vice versa. Co-authored-by: Nobuyoshi Nakada <nobu@ruby-lang.org> Notes: Merged: https://github.com/ruby/ruby/pull/13185 Merged-By: jeremyevans <code@jeremyevans.net>
2025-04-28Fix nondeterministic failure in test_latest_gc_info_weak_references_countJeremy Evans
Clear the ary variable before setting it to nil. Otherwise, if the previous ary value was somewhere on the stack, all references in it would be considered live, and the wmap size would be 10000.
2025-04-26Implement Set as a core classJeremy Evans
Set has been an autoloaded standard library since Ruby 3.2. The standard library Set is less efficient than it could be, as it uses Hash for storage, which stores unnecessary values for each key. Implementation details: * Core Set uses a modified version of `st_table`, named `set_table`. than `s/st_/set_/`, the main difference is that the stored records do not have values, making them 1/3 smaller. `st_table_entry` stores `hash`, `key`, and `record` (value), while `set_table_entry` only stores `hash` and `key`. This results in large sets using ~33% less memory compared to stdlib Set. For small sets, core Set uses 12% more memory (160 byte object slot and 64 malloc bytes, while stdlib set uses 40 for Set and 160 for Hash). More memory is used because the set_table is embedded and 72 bytes in the object slot are currently wasted. Hopefully we can make this more efficient and have it stored in an 80 byte object slot in the future. * All methods are implemented as cfuncs, except the pretty_print methods, which were moved to `lib/pp.rb` (which is where the pretty_print methods for other core classes are defined). As is typical for core classes, internal calls call C functions and not Ruby methods. For example, to check if something is a Set, `rb_obj_is_kind_of` is used, instead of calling `is_a?(Set)` on the related object. * Almost all methods use the same algorithm that the pure-Ruby implementation used. The exception is when calling `Set#divide` with a block with 2-arity. The pure-Ruby method used tsort to implement this. I developed an algorithm that only allocates a single intermediate hash and does not need tsort. * The `flatten_merge` protected method is no longer necessary, so it is not implemented (it could be). * Similar to Hash/Array, subclasses of Set are no longer reflected in `inspect` output. * RDoc from stdlib Set was moved to core Set, with minor updates. This includes a comprehensive benchmark suite for all public Set methods. As you would expect, the native version is faster in the vast majority of cases, and multiple times faster in many cases. There are a few cases where it is significantly slower: * Set.new with no arguments (~1.6x) * Set#compare_by_identity for small sets (~1.3x) * Set#clone for small sets (~1.5x) * Set#dup for small sets (~1.7x) These are slower as Set does not currently use the AR table optimization that Hash does, so a new set_table is initialized for each call. I'm not sure it's worth the complexity to have an AR table-like optimization for small sets (for hashes it makes sense, as small hashes are used everywhere in Ruby). The rbs and repl_type_completor bundled gems will need updates to support core Set. The pull request marks them as allowed failures. This passes all set tests with no changes. The following specs needed modification: * Modifying frozen set error message (changed for the better) * `Set#divide` when passed a 2-arity block no longer yields the same object as both the first and second argument (this seems like an issue with the previous implementation). * Set-like objects that override `is_a?` such that `is_a?(Set)` return `true` are no longer treated as Set instances. * `Set.allocate.hash` is no longer the same as `nil.hash` * `Set#join` no longer calls `Set#to_a` (it calls the underlying C function). * `Set#flatten_merge` protected method is not implemented. Previously, `set.rb` added a `SortedSet` autoload, which loads `set/sorted_set.rb`. This replaces the `Set` autoload in `prelude.rb` with a `SortedSet` autoload, but I recommend removing it and `set/sorted_set.rb`. This moves `test/set/test_set.rb` to `test/ruby/test_set.rb`, reflecting that switch to a core class. This does not move the spec files, as I'm not sure how they should be handled. Internally, this uses the st_* types and functions as much as possible, and only adds set_* types and functions as needed. The underlying set_table implementation is stored in st.c, but there is no public C-API for it, nor is there one planned, in order to keep the ability to change the internals going forward. For internal uses of st_table with Qtrue values, those can probably be replaced with set_table. To do that, include internal/set_table.h. To handle symbol visibility (rb_ prefix), internal/set_table.h uses the same macro approach that include/ruby/st.h uses. The Set class (rb_cSet) and all methods are defined in set.c. There isn't currently a C-API for the Set class, though C-API functions can be added as needed going forward. Implements [Feature #21216] Co-authored-by: Jean Boussier <jean.boussier@gmail.com> Co-authored-by: Oliver Nutter <mrnoname1000@riseup.net>
2025-04-25Deopt if iseq trace events are enabledAaron Patterson
2025-04-25Inline Class#new.Aaron Patterson
This commit inlines instructions for Class#new. To make this work, we added a new YARV instructions, `opt_new`. `opt_new` checks whether or not the `new` method is the default allocator method. If it is, it allocates the object, and pushes the instance on the stack. If not, the instruction jumps to the "slow path" method call instructions. Old instructions: ``` > ruby --dump=insns -e'Object.new' == disasm: #<ISeq:<main>@-e:1 (1,0)-(1,10)> 0000 opt_getconstant_path <ic:0 Object> ( 1)[Li] 0002 opt_send_without_block <calldata!mid:new, argc:0, ARGS_SIMPLE> 0004 leave ``` New instructions: ``` > ./miniruby --dump=insns -e'Object.new' == disasm: #<ISeq:<main>@-e:1 (1,0)-(1,10)> 0000 opt_getconstant_path <ic:0 Object> ( 1)[Li] 0002 putnil 0003 swap 0004 opt_new <calldata!mid:new, argc:0, ARGS_SIMPLE>, 11 0007 opt_send_without_block <calldata!mid:initialize, argc:0, FCALL|ARGS_SIMPLE> 0009 jump 14 0011 opt_send_without_block <calldata!mid:new, argc:0, ARGS_SIMPLE> 0013 swap 0014 pop 0015 leave ``` This commit speeds up basic object allocation (`Foo.new`) by 60%, but classes that take keyword parameters see an even bigger benefit because no hash is allocated when instantiating the object (3x to 6x faster). Here is an example that uses `Hash.new(capacity: 0)`: ``` > hyperfine "ruby --disable-gems -e'i = 0; while i < 10_000_000; Hash.new(capacity: 0); i += 1; end'" "./ruby --disable-gems -e'i = 0; while i < 10_000_000; Hash.new(capacity: 0); i += 1; end'" Benchmark 1: ruby --disable-gems -e'i = 0; while i < 10_000_000; Hash.new(capacity: 0); i += 1; end' Time (mean ± σ): 1.082 s ± 0.004 s [User: 1.074 s, System: 0.008 s] Range (min … max): 1.076 s … 1.088 s 10 runs Benchmark 2: ./ruby --disable-gems -e'i = 0; while i < 10_000_000; Hash.new(capacity: 0); i += 1; end' Time (mean ± σ): 627.9 ms ± 3.5 ms [User: 622.7 ms, System: 4.8 ms] Range (min … max): 622.7 ms … 633.2 ms 10 runs Summary ./ruby --disable-gems -e'i = 0; while i < 10_000_000; Hash.new(capacity: 0); i += 1; end' ran 1.72 ± 0.01 times faster than ruby --disable-gems -e'i = 0; while i < 10_000_000; Hash.new(capacity: 0); i += 1; end' ``` This commit changes the backtrace for `initialize`: ``` aaron@tc ~/g/ruby (inline-new)> cat test.rb class Foo def initialize puts caller end end def hello Foo.new end hello aaron@tc ~/g/ruby (inline-new)> ruby -v test.rb ruby 3.4.2 (2025-02-15 revision d2930f8e7a) +PRISM [arm64-darwin24] test.rb:8:in 'Class#new' test.rb:8:in 'Object#hello' test.rb:11:in '<main>' aaron@tc ~/g/ruby (inline-new)> ./miniruby -v test.rb ruby 3.5.0dev (2025-03-28T23:59:40Z inline-new c4157884e4) +PRISM [arm64-darwin24] test.rb:8:in 'Object#hello' test.rb:11:in '<main>' ``` It also increases memory usage for calls to `new` by 122 bytes: ``` aaron@tc ~/g/ruby (inline-new)> cat test.rb require "objspace" class Foo def initialize puts caller end end def hello Foo.new end puts ObjectSpace.memsize_of(RubyVM::InstructionSequence.of(method(:hello))) aaron@tc ~/g/ruby (inline-new)> make runruby RUBY_ON_BUG='gdb -x ./.gdbinit -p' ./miniruby -I./lib -I. -I.ext/common ./tool/runruby.rb --extout=.ext -- --disable-gems ./test.rb 656 aaron@tc ~/g/ruby (inline-new)> ruby -v test.rb ruby 3.4.2 (2025-02-15 revision d2930f8e7a) +PRISM [arm64-darwin24] 544 ``` Thanks to @ko1 for coming up with this idea! Co-Authored-By: John Hawthorn <john@hawthorn.email>
2025-04-24Fix a flaky test by making sure that a test thread stopsYusuke Endoh
``` 1) Failure: TestThread#test_join_argument_conversion [D:/a/ruby/ruby/src/test/ruby/test_thread.rb:249]: Expected nil (oid=4) to be the same as #<TestThread::Thread:0x000001e9e13bbc18 D:/a/ruby/ruby/src/test/ruby/test_thread.rb:245 run> (oid=3856). ``` https://github.com/ruby/ruby/actions/runs/14636019219/job/41067199813?pr=13169 Notes: Merged: https://github.com/ruby/ruby/pull/13170