ml.ruby-lang.org
Sign In Sign Up
Manage this list Sign In Sign Up

Keyboard Shortcuts

Thread View

  • j: Next unread message
  • k: Previous unread message
  • j a: Jump to all threads
  • j l: Jump to MailingList overview

ruby-core

Thread Start a new thread
Download
Threads by month
  • ----- 2026 -----
  • September
  • August
  • July
  • June
  • May
  • April
  • March
  • February
  • January
  • ----- 2025 -----
  • December
  • November
  • October
  • September
  • August
  • July
  • June
  • May
  • April
  • March
  • February
  • January
  • ----- 2024 -----
  • December
  • November
  • October
  • September
  • August
  • July
  • June
  • May
  • April
  • March
  • February
  • January
  • ----- 2023 -----
  • December
  • November
  • October
  • September
  • August
  • July
  • June
  • May
  • April
  • March
  • February
  • January
  • ----- 2022 -----
  • December
  • November
ruby-core@ml.ruby-lang.org

  • 2 participants
  • 4151 discussions
[ruby-core:126717] [Ruby Bug#22218] Line TracePoint misses executed loop condition after a guard
by ioquatix (Samuel Williams) 14 Sep '26

14 Sep '26
Issue #22218 has been updated by ioquatix (Samuel Williams). Thanks for the back ports! ---------------------------------------- Bug #22218: Line TracePoint misses executed loop condition after a guard https://bugs.ruby-lang.org/issues/22218#change-119015 * Author: ioquatix (Samuel Williams) * Status: Closed * Assignee: ioquatix (Samuel Williams) * Backport: 3.3: REQUIRED, 3.4: DONE, 4.0: DONE ---------------------------------------- `TracePoint.new(:line)` can miss the line event for an executed loop condition when the loop follows a conditional guard. This is related to [Bug #15980](https://bugs.ruby-lang.org/issues/15980), which fixed the same control-flow problem for line coverage. The coverage-specific workaround does not apply to ordinary line TracePoint consumers, so TracePoint and Coverage can still disagree. ## Affected versions This affects Ruby 3.3 and later. The behavior was reproduced on Ruby 3.4.4 and Ruby 4.0.5, both of which report `[1, 3]`. Ruby 3.3.0 through 3.3.11 retain the same coverage-only compiler guard; the source explicitly notes that the TracePoint line event does not occur. Ruby 4.1 development versions are also affected without the proposed patch. ## Reproduction ```ruby source = <<~RUBY raise if 1 == 2 while true break end RUBY lines = [] TracePoint.new(:line) do |trace| lines << trace.lineno if trace.path == "example.rb" end.enable do eval(source, binding, "example.rb") end p lines ``` ### Actual result ```ruby [1, 3] ``` ### Expected result ```ruby [1, 2, 3] ``` The same problem occurs with a non-constant predicate, for example: ```ruby def read return if @finished while chunk = super consume(chunk) end end ``` ## Cause Jump-to-jump peephole optimization retargets the conditional branch past the synthetic jump carrying the line event. As a result, execution reaches the loop body without visiting the event-bearing instruction. ## Proposed Fix The proposed fix is [ruby/ruby#18122](https://github.com/ruby/ruby/pull/18122). Ruby already stops this jump-to-jump optimization when line coverage is enabled and the skipped jump has event flags. The patch generalizes that existing guard: it skips this specific retargeting whenever the intermediate jump carries `RUBY_EVENT_LINE` or `RUBY_EVENT_COVERAGE_LINE`, regardless of whether coverage is active. Other jump-to-jump folding remains enabled. The change only retains the event-bearing jump that TracePoint must visit; it does not add trace-edge metadata or require new VM, YJIT, or ZJIT handling. This is the smaller alternative suggested in the discussion of [ruby/ruby#17825](https://github.com/ruby/ruby/pull/17825). ### Performance The retained jump costs one additional YARV dispatch when the interpreter traverses an affected edge. Performance-sensitive JIT execution can optimize away the extra control-flow overhead: YJIT emits no machine jump when it can place the target block next, while ZJIT cleans up the jump chain in its control-flow graph. The following results compare Ruby immediately before and after the patch on an Apple M4 Pro. Each result is the median of 9 or 11 samples after 3 warmups: | Targeted benchmark | Baseline | Patched | Change | |---|---:|---:|---:| | Hot affected edge, interpreter | 46.20 M iterations/s | 45.74 M iterations/s | -1.0% | | Minimal guarded reader, interpreter | 30.95 M calls/s | 30.13 M calls/s | -2.7% | | Minimal guarded reader, YJIT | 272.56 M calls/s | 272.56 M calls/s | 0.0% | | Minimal guarded reader, ZJIT | 435.34 M calls/s | 436.89 M calls/s | +0.4% | For a synthetic hot-edge benchmark, two long YJIT runs in opposite order varied between -0.5% and +0.4%, and ZJIT differed by +0.04%. There was therefore no repeatable JIT regression. The interpreter figures are deliberately concentrated worst cases where every iteration or call traverses the affected edge. ## Related work - Previous coverage issue: [Bug #15980](https://bugs.ruby-lang.org/issues/15980) - Earlier edge-metadata approach: [ruby/ruby#17825](https://github.com/ruby/ruby/pull/17825) - Selective peephole fix: [ruby/ruby#18122](https://github.com/ruby/ruby/pull/18122) -- https://bugs.ruby-lang.org/
1 0
0 0
[ruby-core:126716] [Ruby Bug#22268] Backport resolv security fixes (CVE-2026-80212, CVE-2026-80213)
by k0kubun (Takashi Kokubun) 14 Sep '26

14 Sep '26
Issue #22268 has been updated by k0kubun (Takashi Kokubun). Backport changed from 3.3: REQUIRED, 3.4: DONE, 4.0: REQUIRED to 3.3: REQUIRED, 3.4: DONE, 4.0: DONE ruby_4_0 commit:2da9a6ef3f423fb85acfd5c41150bb22cdeb14ef. ---------------------------------------- Bug #22268: Backport resolv security fixes (CVE-2026-80212, CVE-2026-80213) https://bugs.ruby-lang.org/issues/22268#change-119014 * Author: hsbt (Hiroshi SHIBATA) * Status: Closed * Backport: 3.3: REQUIRED, 3.4: DONE, 4.0: DONE ---------------------------------------- The resolv security releases of 2026-08-27 fix CVE-2026-80212 and CVE-2026-80213. https://www.ruby-lang.org/en/news/2026/08/27/multiple-vulnerabilities-in-re… `resolv` is a default gem. master already has the fixes through the automatic sync from `ruby/resolv`, but the stable branches do not. | Branch | Current | Target | PR | | --- | --- | --- | --- | | `ruby_4_0` | 0.7.0 | 0.7.2 | https://github.com/ruby/ruby/pull/18528 | | `ruby_3_4` | 0.7.1 | 0.7.2 | https://github.com/ruby/ruby/pull/18529 | | `ruby_3_3` | 0.3.1 | 0.3.2 | https://github.com/ruby/ruby/pull/18530 | -- https://bugs.ruby-lang.org/
1 0
0 0
[ruby-core:126570] [Ruby Bug#22292] YJIT/ZJIT: Struct accessor crashes after an instance variable is set on a Struct that exactly fills the largest GC slot
by shia (Sangyong Sim) 14 Sep '26

14 Sep '26
Issue #22292 has been reported by shia (Sangyong Sim). ---------------------------------------- Bug #22292: YJIT/ZJIT: Struct accessor crashes after an instance variable is set on a Struct that exactly fills the largest GC slot https://bugs.ruby-lang.org/issues/22292 * Author: shia (Sangyong Sim) * Status: Open * Backport: 3.3: UNKNOWN, 3.4: UNKNOWN, 4.0: UNKNOWN ---------------------------------------- ruby -v: ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +PRISM [x86_64-linux] Also reproduced on 4.0.1 and on master (3600d410ad, 2026-09-04). Not reproduced on 3.4.9. ## Summary With YJIT or ZJIT enabled, this sequence crashes the process: 1. Define a Struct whose embedded body exactly fills the largest GC slot. That is 78 members on 4.0.x and 125 on master. 2. Call an accessor enough times for the JIT to compile it. Every instance is embedded at this point. 3. Set an instance variable on any one instance of that class. 4. Create a new instance and call the same accessor. After step 3, every *new* instance of the class is allocated with the heap layout. The compiled accessor still assumes the embedded layout, so it reads the heap pointer as if it were a member value. The interpreter is not affected. Instances created before step 3 are not affected either. ## Reproduction ```ruby n = (ARGV[0] || 78).to_i # 78 on 4.0.x, 125 on master klass = Struct.new(*(1..n).map { |i| :"m#{i}" }, keyword_init: true) reader = ->(s) { s.m2 } make = -> { klass.new(m1: 1, m2: 2, :"m#{n}" => n) } before = make.call 300.times { reader.call(before); make.call } # JIT compiles the accessor; all instances embedded before.instance_variable_set(:@iv, 1) # class now has RCLASS_MAX_IV_COUNT > 0 after = make.call # this instance is heap-allocated raise "wrong value" unless reader.call(after) == 2 && after.send(:"m#{n}") == n puts "ok" ``` ``` $ ruby sN.rb 78 ok $ ruby --yjit sN.rb 78 sN.rb:9: [BUG] Segmentation fault at 0x0000000000000005 $ ruby --zjit sN.rb 78 sN.rb:9: [BUG] Segmentation fault at 0x0000000000000005 ``` On master, pass `125` instead of `78`. No JIT options other than `--yjit` / `--zjit` are needed; the 300 warm-up calls are enough for the default call thresholds. ## Cause `struct_alloc` (master struct.c:830) reserves one extra VALUE in the embedded size once any instance of the class has had an instance variable: ```c size_t embedded_size = offsetof(struct RStruct, as.ary) + (sizeof(VALUE) * n); if (RCLASS_MAX_IV_COUNT(klass) > 0) { /* struct.c:834 */ embedded_size += sizeof(VALUE); } ... if (n > 0 && n <= embed_len_max && rb_gc_size_allocatable_p(embedded_size)) { /* struct.c:842 */ ``` When the embedded size already fills the largest slot, the extra 8 bytes no longer fit. `rb_gc_size_allocatable_p` returns false and every later instance is heap-allocated. So the embedded/heap decision depends on `RCLASS_MAX_IV_COUNT`, which can change at any time. Both JITs make that decision once, when the accessor is compiled, and never check it again: * YJIT: `gen_struct_aref` reads `FL_TEST_RAW(comptime_recv, RSTRUCT_EMBED_LEN_MASK)` from the sample receiver (master yjit/src/codegen.rs:8993). * ZJIT: uses `is_struct_embedded()` from the profiled type (master zjit/src/hir.rs:4967). A comment there says the layout is fixed per class. History of the extra slot: * f3206cc79b (2025-08-06, in 4.0 but not 3.4) added it. In 4.0.x an embedded struct keeps its `fields_obj` reference in that slot. * 40f105bd55 (2026-07-16, master only) moved `fields_obj` into its own field of `struct RStruct`. Since then nothing reads the extra slot, but `struct_alloc` still reserves it. ## Only at the boundary This happens only for the one member count that exactly fills the largest slot. Below it, the extra 8 bytes still fit and the instance stays embedded. Above it, instances are heap-allocated from the start, and the JIT compiles the heap path. Measured on 4.0.6 with `--yjit`: | members | memsize_of before / after ivar | result | |---|---|---| | 70–77 | 640 / 640 | ok | | **78** | **640 / 664** | **SEGV** | | 79–82 | 672–696 / same (heap from the start) | ok | On master the boundary is 125 (1024 -> 1040). ## Possible fix (master) On master the extra slot is unused, so `struct_alloc` can stop reserving it. The embedded/heap decision then depends only on the member count, and the JIT assumption holds. ```diff diff --git a/struct.c b/struct.c index 84a0940ba7..21966e927e 100644 --- a/struct.c +++ b/struct.c @@ -831,9 +831,6 @@ struct_alloc(VALUE klass) { long n = num_members(klass); size_t embedded_size = offsetof(struct RStruct, as.ary) + (sizeof(VALUE) * n); - if (RCLASS_MAX_IV_COUNT(klass) > 0) { - embedded_size += sizeof(VALUE); - } VALUE flags = T_STRUCT; ``` With this change the repro passes under `--yjit` and `--zjit` on master, and `test_struct.rb`, `test_data.rb`, `test_objectspace.rb`, `test_gc_compact.rb` pass. `ObjectSpace.memsize_of` for such a struct stays 1024 after an instance variable is set on the class (previously 1040). ## Possible fix (4.0.x) On 4.0.x the extra slot is still used, so it cannot simply be removed. Instead, decide embedded-vs-heap first, from the member count alone, and add the extra slot only when it still fits: * Before: add 8 bytes whenever the class has had an ivar, then check whether the total fits. At the boundary it does not, so the instance goes to the heap. * After: check whether the members fit. If they do, the instance is embedded, no matter what. Then add the 8 bytes only if they also fit. If they do not, the instance stays embedded and its ivars go through the existing `RSTRUCT_GEN_FIELDS` path (generic fields table). That is the same path an instance allocated before the class had any ivar already uses. Tested on 4.0.6 with this patch applied: ```diff --- a/struct.c +++ b/struct.c @@ -822,29 +822,31 @@ { long n = num_members(klass); size_t embedded_size = offsetof(struct RStruct, as.ary) + (sizeof(VALUE) * n); - if (RCLASS_MAX_IV_COUNT(klass) > 0) { + // Whether an instance is embedded must depend only on the member count: + // the JITs decide the layout of a Struct accessor at compile time and + // assume it holds for every instance of the class. So the extra slot for + // the inline fields_obj reference is only added when it still fits; when + // it does not, the instance stays embedded and its ivars go through the + // RSTRUCT_GEN_FIELDS path, exactly like an instance allocated before the + // class had any ivar. + bool embedded = n > 0 && rb_gc_size_allocatable_p(embedded_size); + if (embedded && RCLASS_MAX_IV_COUNT(klass) > 0 + && rb_gc_size_allocatable_p(embedded_size + sizeof(VALUE))) { embedded_size += sizeof(VALUE); } VALUE flags = T_STRUCT | (RGENGC_WB_PROTECTED_STRUCT ? FL_WB_PROTECTED : 0); - if (n > 0 && rb_gc_size_allocatable_p(embedded_size)) { + if (embedded) { flags |= n << RSTRUCT_EMBED_LEN_SHIFT; - if (RCLASS_MAX_IV_COUNT(klass) == 0) { - // We set the flag before calling `NEWOBJ_OF` in case a NEWOBJ tracepoint does - // attempt to write fields. We'll remove it later if no fields was written to. - flags |= RSTRUCT_GEN_FIELDS; - } + // We set the flag before calling `NEWOBJ_OF` in case a NEWOBJ tracepoint does + // attempt to write fields. We'll remove it later if no fields was written to. + flags |= RSTRUCT_GEN_FIELDS; NEWOBJ_OF(st, struct RStruct, klass, flags, embedded_size, 0); - if (RCLASS_MAX_IV_COUNT(klass) == 0) { - if (!rb_shape_obj_has_fields((VALUE)st) - && embedded_size < rb_gc_obj_slot_size((VALUE)st)) { - FL_UNSET_RAW((VALUE)st, RSTRUCT_GEN_FIELDS); - RSTRUCT_SET_FIELDS_OBJ((VALUE)st, 0); - } - } - else { + if (!rb_shape_obj_has_fields((VALUE)st) + && embedded_size < rb_gc_obj_slot_size((VALUE)st)) { + FL_UNSET_RAW((VALUE)st, RSTRUCT_GEN_FIELDS); RSTRUCT_SET_FIELDS_OBJ((VALUE)st, 0); } ``` Results with the patched 4.0.6 build: * The repro passes under `--yjit` and `--zjit` for 78 members, and at the other slot boundaries (3, 8, 18, 38) as well. * Only the boundary case changes: a 78-member instance created after an ivar is set on the class is now 640 bytes (embedded, `GEN_FIELDS`) instead of 664 (heap). Classes that have room for the slot behave as before; for example, 3 members still go from 40 to 80 and use the inline slot. * `test_struct.rb`, `test_data.rb`, `test_objectspace.rb`, `test_gc_compact.rb`: 180 tests, 0 failures. -- https://bugs.ruby-lang.org/
3 4
0 0
[ruby-core:126714] [Ruby Bug#22257] Prepending a module to an already-included module leaves stale super caches
by k0kubun (Takashi Kokubun) 14 Sep '26

14 Sep '26
Issue #22257 has been updated by k0kubun (Takashi Kokubun). Backport changed from 3.3: WONTFIX, 3.4: REQUIRED, 4.0: REQUIRED to 3.3: WONTFIX, 3.4: REQUIRED, 4.0: DONE ruby_4_0 commit:06611602a958ebe1126be3a5cb151f3c2c8bffcc. ---------------------------------------- Bug #22257: Prepending a module to an already-included module leaves stale super caches https://bugs.ruby-lang.org/issues/22257#change-119012 * Author: eightbitraptor (Matt V-H) * Status: Closed * Target version: 4.1 * Backport: 3.3: WONTFIX, 3.4: REQUIRED, 4.0: DONE ---------------------------------------- When a module is prepended to a module that already has includers, super call sites inside the prepended module keep calling the old method entry after the method is redefined. [**Fix implemented in this PR**](https://github.com/ruby/ruby/pull/18421) <pre> module M; def foo; :m; end; end class D; include M; end M.prepend(Module.new { def foo; super; end }) D.new.foo # prime the super call-site cache M.send(:define_method, :foo) { :hooked } p D.new.foo </pre> I found this while working on an unrelated Ractor issue. But this problem manifests itself for Ractors as follows: <pre> Ractor.new {} require 'set' Kernel.send(:define_method, :require) { |f| $hook = f } require 'set' p $hook # => nil, the hook never runs </pre> This is because creating the first Ractor prepends an internal `RactorRequire` wrapper onto Kernel. After the first require primes the cache in the wrapper, redefining Kernel#require silently does nothing. The linked PR fixes this by registering the backfilled iclass in the module's subclasses list, after the includer walk finishes. -- https://bugs.ruby-lang.org/
1 0
0 0
[ruby-core:126379] [Ruby Feature#22243] Change fork behavior when Process._fork raises an exception during fork with a block argument
by rpeng (Richard Peng) 14 Sep '26

14 Sep '26
Issue #22243 has been reported by rpeng (Richard Peng). ---------------------------------------- Feature #22243: Change fork behavior when Process._fork raises an exception during fork with a block argument https://bugs.ruby-lang.org/issues/22243 * Author: rpeng (Richard Peng) * Status: Open ---------------------------------------- # Problem Ruby exposes Process.fork in two formats - one with block arguments and one without. When a block is passed in, the implicit contract is that a child process will terminate once the block ends. However, it is possible for the child to "escape" the enclosed block in certain circumstances when Process._fork raises an exception. Here's a demonstration of what may occur: ```ruby module BadForkTracker def _fork pid = super if pid == 0 raise "exception during fork in child" # end pid end end Process.singleton_class.prepend(BadForkTracker) loop do sleep(1) puts "I'm parent #{Process.pid}" fork do puts "I'm child #{Process.pid} and I'm exiting" end rescue StandardError => e puts "#{Process.pid} Received Error: #{e}" # <- child ends up in this block, then resumes the loop. most users will not be expecting this. end ``` In practice, there are many libraries that implement "fork tracking" (active_support, datadog, connection_pool, to name a few) by prepending to the `Process` module. Many of which are designed to run user specified callbacks which increases the surface area of errors occurring. This means that on _fork, there's a good chance that something will throw and cause a child to resume execution in the "parent" context. In addition, many workloads (sidekiq, puma) have global rescues that are similar to the example case I provided above, which would unintentionally cause the child to continue execution. # Proposal I am proposing a change in behavior: when Process.fork is called with a block, if Process._fork raises an exception in the child, immediately terminate the child with an error exit code (instead of relying on an enclosing rescue to handle it). This guarantees that a child cannot escape out of the block it was meant to execute. In my opinion, this is more in line with how libraries generally expect this to work (e.g. there are very few libraries that are being defensive and attempting not to throw in `def _fork` overrides). -- https://bugs.ruby-lang.org/
4 4
0 0
[ruby-core:126222] [Ruby Bug#22223] Socket.tcp with connect_timeout returns a phantom "connected" socket for a refused connection on macOS 27 (kernel answers EISCONN on connect retry; SO_ERROR never consulted)
by tdehnke (Tony Dehnke) 14 Sep '26

14 Sep '26
Issue #22223 has been reported by tdehnke (Tony Dehnke). ---------------------------------------- Bug #22223: Socket.tcp with connect_timeout returns a phantom "connected" socket for a refused connection on macOS 27 (kernel answers EISCONN on connect retry; SO_ERROR never consulted) https://bugs.ruby-lang.org/issues/22223 * Author: tdehnke (Tony Dehnke) * Status: Open * ruby -v: ruby 3.4.10 (2026-06-30 revision 2b0b7728dc) +PRISM [arm64-darwin27] * Backport: 3.3: UNKNOWN, 3.4: UNKNOWN, 4.0: UNKNOWN ---------------------------------------- ## Summary On macOS 27.0 (beta, build 26A5388g, arm64 — M5), `Socket.tcp(host, port, connect_timeout: X)` returns a socket object instead of raising `Errno::ECONNREFUSED` when nothing is listening on the port. The returned socket is not usable (first `write` raises `Errno::EPIPE`). The blocking paths (`connect_timeout: nil`, `TCPSocket.new`) raise `Errno::ECONNREFUSED` correctly. ⚠️ Note: this is a macOS **beta** build; the kernel behavior may change before release. Reporting anyway because Ruby's connect idiom is not robust against it, and the failure is silent (phantom success, not an error). ## Repro (no listener on port 3999) ```ruby require "socket" Socket.tcp("127.0.0.1", 3999, connect_timeout: 1) # => #<Socket ...> (expected: Errno::ECONNREFUSED) Socket.tcp("127.0.0.1", 3999, connect_timeout: 1, fast_fallback: false) # => #<Socket ...> (same — not Happy-Eyeballs-specific) Socket.tcp("127.0.0.1", 3999, connect_timeout: nil) # blocking path # => Errno::ECONNREFUSED (correct) TCPSocket.new("127.0.0.1", 3999) # => Errno::ECONNREFUSED (correct) s = Socket.tcp("127.0.0.1", 3999, connect_timeout: 1) s.write("x") # => Errno::EPIPE — the "connected" socket was never connected ``` Reproduced identically on ruby 3.4.5 (arm64-darwin27) and ruby 3.4.10 (arm64-darwin27). Other tools on the same machine (curl, a blocking C connect) report Connection refused correctly and instantly. ## Root cause observed The classic nonblocking idiom — `connect_nonblock` → wait writable → retry `connect_nonblock`, treating `Errno::EISCONN` as success (as recommended in Ruby's own `connect_nonblock` documentation, and as used by `Socket.tcp`'s connect_timeout path) — is broken by a Darwin 27 kernel behavior change. C-level demonstration on a refused nonblocking connect: ``` first connect: r=-1 errno=36 (Operation now in progress) poll: r=1 revents=0x10 # POLLHUP — yet the fd is reported "ready" retry connect: r=-1 errno=56 (Socket is already connected) # EISCONN, on a REFUSED socket SO_ERROR: 61 (Connection refused) # the truth is still available here ``` So the kernel answers the retry-`connect(2)` with `EISCONN` instead of `ECONNREFUSED`, and Ruby's idiom takes that as "connected". `getsockopt(SO_ERROR)` still reports `ECONNREFUSED` — checking it after writability (before/instead of trusting the connect-retry result) detects the failure correctly. Ruby-level equivalent: ```ruby sock = Socket.new(:INET, :STREAM) addr = Socket.sockaddr_in(3999, "127.0.0.1") begin sock.connect_nonblock(addr) rescue IO::WaitWritable sock.wait_writable(2) # returns "writable" begin sock.connect_nonblock(addr) # raises Errno::EISCONN (!) rescue Errno::EISCONN sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_ERROR).int # => 61 ECONNREFUSED end end ``` ## Impact Any code probing "is something listening?" via `Socket.tcp` with a `connect_timeout` silently gets a false positive on this platform. Found in the wild: shakapacker's `Shakapacker::DevServer#running?` (probe with `connect_timeout: 0.1`) reported a webpack dev server that wasn't there, so its Rails middleware proxied every asset request into a dead port — every page 502s with no error pointing anywhere near the cause. ## Suggested direction After waiting for writability in the connect_timeout / HEv2 paths (and possibly in the documented `connect_nonblock` idiom), consult `getsockopt(SO_ERROR)` rather than relying on the retry-`connect` returning `EISCONN` — SO_ERROR carries the correct `ECONNREFUSED` even when the kernel misanswers the retry. (Also plausibly worth an Apple Feedback report; the poll `revents=POLLHUP`-as-ready + `EISCONN`-on-refused combination looks like an OS regression.) ## Environment - macOS 27.0 (beta), build 26A5388g, Apple Silicon (M5), Darwin 27 - ruby 3.4.5 (2025-07-16 revision 20cda200d3) +PRISM [arm64-darwin27] — also ruby 3.4.10 [arm64-darwin27] - Possibly related (different symptom, same neighborhood): Bug #21104 (Net::HTTP failures on macOS ≥ 3.4, hardware-dependent, RUBY_TCP_NO_FAST_FALLBACK workaround). Unlike #21104, this repro is NOT affected by `fast_fallback: false`. -- https://bugs.ruby-lang.org/
4 4
0 0
[ruby-core:126201] [Ruby Bug#22220] Performance regression when requiring `aws-sdk-ec2` in Ruby 4.0.6
by emanuelevans (Emanuel Evans) 14 Sep '26

14 Sep '26
Issue #22220 has been reported by emanuelevans (Emanuel Evans). ---------------------------------------- Bug #22220: Performance regression when requiring `aws-sdk-ec2` in Ruby 4.0.6 https://bugs.ruby-lang.org/issues/22220 * Author: emanuelevans (Emanuel Evans) * Status: Open * ruby -v: 4.0.6 * Backport: 3.3: UNKNOWN, 3.4: UNKNOWN, 4.0: UNKNOWN ---------------------------------------- Ruby 4.0.6 introduced a large performance regression when loading `aws-sdk-ec2` (~30s on my machine, compared to <1s for ruby 4.0.5). Script to reproduce: ``` for v in 4.0.5 4.0.6; do docker run --rm ruby:$v-slim bash -c ' gem install --no-document aws-sdk-ec2 -v 1.402.0 >/dev/null 2>&1 ruby -e "t = Process.clock_gettime(Process::CLOCK_MONOTONIC) require \"aws-sdk-ec2\" printf(\"ruby %s: %.2fs\n\", RUBY_VERSION, Process.clock_gettime(Process::CLOCK_MONOTONIC) - t)"' done ``` It appears to be related to `Shape.new` and/or `Class.new` compile times; minimal reproduction: ``` for v in 4.0.5 4.0.6; do docker run --rm ruby:$v-slim ruby -e ' src = "class Shape\n def self.new(**) = nil\nend\nmodule M\n" + (1..16_000).map { |i| %( K_#{i} = Shape.new(name: "k#{i}")) }.join("\n") + "\nend\n" File.write("/tmp/bench.rb", src) t = Process.clock_gettime(Process::CLOCK_MONOTONIC) RubyVM::InstructionSequence.compile_file("/tmp/bench.rb") printf("ruby %s: %.2fs\n", RUBY_VERSION, Process.clock_gettime(Process::CLOCK_MONOTONIC) - t)' done ``` -- https://bugs.ruby-lang.org/
4 4
0 0
[ruby-core:126710] [Ruby Bug#22218] Line TracePoint misses executed loop condition after a guard
by k0kubun (Takashi Kokubun) 14 Sep '26

14 Sep '26
Issue #22218 has been updated by k0kubun (Takashi Kokubun). Backport changed from 3.3: REQUIRED, 3.4: DONE, 4.0: REQUIRED to 3.3: REQUIRED, 3.4: DONE, 4.0: DONE ruby_4_0 commit:be08f56e6093e3733ccda80b3f3bfcb29c09b66d. ---------------------------------------- Bug #22218: Line TracePoint misses executed loop condition after a guard https://bugs.ruby-lang.org/issues/22218#change-119008 * Author: ioquatix (Samuel Williams) * Status: Closed * Assignee: ioquatix (Samuel Williams) * Backport: 3.3: REQUIRED, 3.4: DONE, 4.0: DONE ---------------------------------------- `TracePoint.new(:line)` can miss the line event for an executed loop condition when the loop follows a conditional guard. This is related to [Bug #15980](https://bugs.ruby-lang.org/issues/15980), which fixed the same control-flow problem for line coverage. The coverage-specific workaround does not apply to ordinary line TracePoint consumers, so TracePoint and Coverage can still disagree. ## Affected versions This affects Ruby 3.3 and later. The behavior was reproduced on Ruby 3.4.4 and Ruby 4.0.5, both of which report `[1, 3]`. Ruby 3.3.0 through 3.3.11 retain the same coverage-only compiler guard; the source explicitly notes that the TracePoint line event does not occur. Ruby 4.1 development versions are also affected without the proposed patch. ## Reproduction ```ruby source = <<~RUBY raise if 1 == 2 while true break end RUBY lines = [] TracePoint.new(:line) do |trace| lines << trace.lineno if trace.path == "example.rb" end.enable do eval(source, binding, "example.rb") end p lines ``` ### Actual result ```ruby [1, 3] ``` ### Expected result ```ruby [1, 2, 3] ``` The same problem occurs with a non-constant predicate, for example: ```ruby def read return if @finished while chunk = super consume(chunk) end end ``` ## Cause Jump-to-jump peephole optimization retargets the conditional branch past the synthetic jump carrying the line event. As a result, execution reaches the loop body without visiting the event-bearing instruction. ## Proposed Fix The proposed fix is [ruby/ruby#18122](https://github.com/ruby/ruby/pull/18122). Ruby already stops this jump-to-jump optimization when line coverage is enabled and the skipped jump has event flags. The patch generalizes that existing guard: it skips this specific retargeting whenever the intermediate jump carries `RUBY_EVENT_LINE` or `RUBY_EVENT_COVERAGE_LINE`, regardless of whether coverage is active. Other jump-to-jump folding remains enabled. The change only retains the event-bearing jump that TracePoint must visit; it does not add trace-edge metadata or require new VM, YJIT, or ZJIT handling. This is the smaller alternative suggested in the discussion of [ruby/ruby#17825](https://github.com/ruby/ruby/pull/17825). ### Performance The retained jump costs one additional YARV dispatch when the interpreter traverses an affected edge. Performance-sensitive JIT execution can optimize away the extra control-flow overhead: YJIT emits no machine jump when it can place the target block next, while ZJIT cleans up the jump chain in its control-flow graph. The following results compare Ruby immediately before and after the patch on an Apple M4 Pro. Each result is the median of 9 or 11 samples after 3 warmups: | Targeted benchmark | Baseline | Patched | Change | |---|---:|---:|---:| | Hot affected edge, interpreter | 46.20 M iterations/s | 45.74 M iterations/s | -1.0% | | Minimal guarded reader, interpreter | 30.95 M calls/s | 30.13 M calls/s | -2.7% | | Minimal guarded reader, YJIT | 272.56 M calls/s | 272.56 M calls/s | 0.0% | | Minimal guarded reader, ZJIT | 435.34 M calls/s | 436.89 M calls/s | +0.4% | For a synthetic hot-edge benchmark, two long YJIT runs in opposite order varied between -0.5% and +0.4%, and ZJIT differed by +0.04%. There was therefore no repeatable JIT regression. The interpreter figures are deliberately concentrated worst cases where every iteration or call traverses the affected edge. ## Related work - Previous coverage issue: [Bug #15980](https://bugs.ruby-lang.org/issues/15980) - Earlier edge-metadata approach: [ruby/ruby#17825](https://github.com/ruby/ruby/pull/17825) - Selective peephole fix: [ruby/ruby#18122](https://github.com/ruby/ruby/pull/18122) -- https://bugs.ruby-lang.org/
1 0
0 0
[ruby-core:126098] [Ruby Bug#22198] win32: heap overflow in Kernel#system
by mame (Yusuke Endoh) 14 Sep '26

14 Sep '26
Issue #22198 has been reported by mame (Yusuke Endoh). ---------------------------------------- Bug #22198: win32: heap overflow in Kernel#system https://bugs.ruby-lang.org/issues/22198 * Author: mame (Yusuke Endoh) * Status: Assigned * Assignee: windows * ruby -v: ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +PRISM [x64-mingw-ucrt] * Backport: 3.3: REQUIRED, 3.4: REQUIRED, 4.0: REQUIRED ---------------------------------------- `system('foo.exe %A')` looks past `%A` for a closing `%`, reading past the end of the string. It does not reproduce normally, but with page heap enabled it faults: ``` PS C:\> gflags /p /enable ruby.exe /full PS C:\> ruby -e "system('x' * 1024 + '.exe %A')" -e:1: [BUG] Segmentation fault ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +PRISM [x64-mingw-ucrt] -- Control frame information ----------------------------------------------- c:0003 p:---- s:0011 e:000010 l:y b:0001 CFUNC :system c:0002 p:0013 s:0006 e:000005 l:n b:---- EVAL -e:1 [FINISH] c:0001 p:0000 s:0003 E:001620 l:y b:---- DUMMY [FINISH] -- Ruby level backtrace information ---------------------------------------- -e:1:in '<main>' -e:1:in 'system' -- Threading information --------------------------------------------------- Total ractor count: 1 Ruby thread count for this ractor: 1 -- C level backtrace information ------------------------------------------- C:\WINDOWS\SYSTEM32\ntdll.dll(NtWaitForSingleObject+0x14) [0x00007ffbd55000e4] C:\WINDOWS\System32\KERNELBASE.dll(WaitForSingleObjectEx+0xaf) [0x00007ffbd274c11f] C:\Users\YusukeEndoh\scoop\apps\ruby\current\bin\x64-ucrt-ruby400.dll(rb_vm_bugreport+0x237) [0x00007ffb37262227] C:\Users\YusukeEndoh\scoop\apps\ruby\current\bin\x64-ucrt-ruby400.dll(rb_bug_for_fatal_signal+0x9b) [0x00007ffb37000beb] C:\Users\YusukeEndoh\scoop\apps\ruby\current\bin\x64-ucrt-ruby400.dll(rb_shape_free_all+0xfb5) [0x00007ffb37191985] C:\WINDOWS\System32\ucrtbase.dll(seh_filter_exe+0x84) [0x00007ffbd2fff2e4] [0x00007ff765e81080] C:\WINDOWS\SYSTEM32\ntdll.dll(_chkstk+0x9f) [0x00007ffbd550479f] *snip* ``` PR: https://github.com/ruby/ruby/pull/17929 -- https://bugs.ruby-lang.org/
3 2
0 0
[ruby-core:126038] [Ruby Bug#22190] Class#subclasses does not include clones of classes that include modules
by shugo (Shugo Maeda) 14 Sep '26

14 Sep '26
Issue #22190 has been reported by shugo (Shugo Maeda). ---------------------------------------- Bug #22190: Class#subclasses does not include clones of classes that include modules https://bugs.ruby-lang.org/issues/22190 * Author: shugo (Shugo Maeda) * Status: Open * Target version: 4.0 * ruby -v: ruby 4.0.5 (2026-05-20 revision 64336ffd0e) +PRISM [x86_64-linux] * Backport: 3.3: UNKNOWN, 3.4: UNKNOWN, 4.0: UNKNOWN ---------------------------------------- `Class#subclasses` does not return clones of classes that include (or prepend) a module, while clones of classes without modules are returned. This is a regression first released in Ruby 4.0.4. ```ruby module M; end class X; end class C < X include M end class D < X; end clone_c = C.clone clone_d = D.clone p clone_c.superclass #=> X p X.subclasses.include?(C) #=> true p X.subclasses.include?(clone_d) #=> true (D includes no module) p X.subclasses.include?(clone_c) #=> false (expected: true) ``` The same happens with `prepend` instead of `include`. Ruby 4.0.3 and earlier return `true` for all of the above. ## Analysis Since a2531ba293 ("Simplify subclasses list, remove from Box"), `class_associate_super()` maintains subclass lists only for direct T_CLASS -> T_CLASS links: ```c // class.c (master) // Only maintain subclass lists for T_CLASS→T_CLASS relationships. // Include/prepend inserts ICLASSes into the super chain, but T_CLASS // subclass lists should track only the immutable T_CLASS→T_CLASS link. if (RB_TYPE_P(klass, T_CLASS) && RB_TYPE_P(super, T_CLASS)) { ``` For normally created classes this is fine because the T_CLASS -> T_CLASS link is registered at class creation time, before any module is included. However, `rb_mod_init_copy()` builds the clone's superclass chain differently: * When the original has no origin, the clone shares the original's ICLASS chain: `rb_class_set_super(clone, RCLASS_SUPER(orig))` (class.c:983), where `RCLASS_SUPER(orig)` is a T_ICLASS. The condition above is false, so the clone is never added to the real superclass's subclass list. * When the original has an origin (prepend), the tail is connected via `rb_class_set_super(clone_origin, RCLASS_SUPER(orig_origin))` (class.c:1031), where `clone_origin` is a T_ICLASS, so this link is not registered either. Before a2531ba293, subclass entries were reparented onto ICLASSes and the `Class#subclasses` walk descended through non-T_CLASS entries (see `class_descendants_recursive` in 4.0.3), so a clone registered under the shared ICLASS was still found from the superclass. Method lookup and method cache invalidation do not seem to be affected (redefining a method on the superclass after cloning is observed correctly through the clone); the visible breakage is in the reflection API. Other users of the subclass lists may want checking, though (for example the allocator propagation added in 111215de27 also iterates subclass lists). -- https://bugs.ruby-lang.org/
2 3
0 0
  • ← Newer
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • ...
  • 416
  • Older →

HyperKitty Powered by HyperKitty version 1.3.12.