Skip to content

[pull] master from ruby:master#1212

Merged
pull[bot] merged 13 commits into
turkdevops:masterfrom
ruby:master
Jul 14, 2026
Merged

[pull] master from ruby:master#1212
pull[bot] merged 13 commits into
turkdevops:masterfrom
ruby:master

Conversation

@pull

@pull pull Bot commented Jul 14, 2026

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

ko1 and others added 13 commits July 14, 2026 17:32
coroutine_thread_terminated designated the successor first
(thread_sched_to_dead_common) and only then removed the dying thread
from the living set.  The removal's VM-lock work (ractor_check_blocking
and a possible barrier join) then ran detached: the thread had already
left the running set while sched.running pointed to the successor.
Such a joiner corrupts the successor's saved machine context
(RB_VM_SAVE_MACHINE_CONTEXT captures the executing thread's state but
the join parked cr->threads.sched.running), can sleep on an
already-completed barrier until the NEXT one completes (a hang at
process exit), is mis-counted in barrier_waiting_cnt, and races
ractor_check_blocking's stale will-block decision.

Do the removal first, while the thread still owns the Ractor's
scheduler slot: the VM-lock work runs as an ordinary counted running
thread and the whole class of detached races disappears.  After the
removal th may be unreachable, but no GC can complete while th still
owns the slot (a barrier waits for it to join), so the short handoff
tail may keep touching th/sched.

The Ractor's last thread keeps the original reverse order: its removal
unlinks the Ractor itself, so r/sched must not be touched afterwards.
That order is safe only for it: with no successor, sched->running stays
NULL and vm_need_barrier requires a running thread, so its removal's VM
lock never joins a barrier in the first place.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The is_running check added by 3fa1c87 guarded against a terminating
thread joining the barrier after leaving the running set.  Since the
previous commit such a thread leaves the living set before handing over
its scheduler slot, so every joiner is a running-set member again.
Restore the unconditional count and assert the invariant instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Exercises a short-lived thread terminating while another Ractor drives
GC.compact barriers, the scenario fixed by the previous commits.
Skipped when the GC does not implement compaction (e.g. MMTk).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A fork taken while the parent holds the VM barrier (e.g. rb_gc_before_fork)
leaves the child with barrier_waiting set and a barrier owner that no longer
exists, so the next barrier in the child could never complete. Reset the
barrier fields in thread_sched_atfork like the rest of the scheduler state.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Encode the safety argument of the epilogue reorder as VM_ASSERTs: a
non-last dying thread still owns its scheduler slot when it leaves the
living set (its VM-lock work stays a counted running thread), and the
last thread's reverse order is only safe because it has no successor
(running == NULL, so the removal cannot join a barrier). Also check
winding_cnt against underflow at the reclaim decrement.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ractor_port_receive/send derive a raw 'struct ractor_port *rp' from self's
embedded (RUBY_TYPED_EMBEDDABLE) data and keep using it across a GC
safepoint: receive parks, and send allocates the basket. self is only kept
alive movably (rb_gc_mark_movable via the VM stack), so a concurrent
GC.compact can relocate the port and leave rp dangling -- the receiver then
polls a stale, reused slot's port id forever (a hang of Ractor#value under
IO + GC.compact). RB_GC_GUARD(self) keeps self on the machine stack, whose
conservative scan pins the port so it cannot move while rp is live.
Forking with live Ractors takes a VM barrier in the parent and the child
inherits scheduler/barrier state that must be reset. General regression
coverage for the parent-side fork barrier and the child teardown, checking
the surviving Ractors still work afterwards. (Uses GC.start, not GC.compact,
so it also runs on GCs without compaction such as MMTk.)
Instead of spilling into a raw buffer, `RObject` now spills
into an `IMEMO/fields` object like other types.

From an instance variable layout standpoint, an extended `RObject`
is now identical to a `RTypedData`, as in they both store the
reference to their `IMEMO/fields` at the same offset (`VALUE * 2`).

One positive consequence of this is that the only case where a `T_OBJECT`
needs sweeping is if a finalizer was registered.

YJIT now side exit when it need to write a new ivar into a `RObject`
that is out of space. This is unlikely to cause a performance regression
as this codepath isn't supposed to happen after warmup given `max_iv_count`
is recorded on classes, so future objects should be large enough.
Hence it's best not to waste executable memory for such codepath.

ZJIT lost support for writting into extended `RObject`.
Most of the code to support it is there it's just missing an
implementation of `RBASIC_SET_SHAPE_ID`, which recently changed
to strip some bits out of the shape (see comments).
I will leave it to the ZJIT team to implement it (sorry).

There are a few future cleanups planned that I keep for followups:

  - We can now get rid of the `ROBJECT_HEAP` flag, it's redundant with
    the shape layout bits.
  - `imemo_fields` can now embed the `st_table` when complex.

Co-Authored-By: John Hawthorn <john@hawthorn.email>
Co-authored-by: Randy Stauner <randy@r4s6.net>
…efines it

RubyGems older than 3.6 has no rubygems/vendored_tsort, and its own
Gem::TSort is often already loaded through rubygems/request_set by the
time Bundler runs, e.g. from Gem.activate_bin_path in binstubs. The
unconditional reassignment printed an already-initialized-constant
warning to stderr, breaking specs that assert empty stderr against
system RubyGems. The old vendored Gem::TSort is API-compatible, so keep
it when present.

ruby/rubygems@a42ded1d99

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…RGV=system

Since RGV started defaulting to ".", the system-rubygems-bundler job
has been silently testing the repo's RubyGems instead of the one
shipped with each Ruby, because switch_rubygems is injected into every
spec-spawned process. Add an explicit RGV=system mode and set it on the
job's test step.

The harness itself keeps running on the checked out RubyGems even in
this mode. The flattened lib/ contains both Bundler and RubyGems, so it
is unavoidably on the harness load path and would get mixed into an
older RubyGems otherwise. Only processes spawned by specs boot system
RubyGems untouched, which is the configuration under test.

ruby/rubygems@40bb5bcd38

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… tsort default gem

RubyGems 3.4 and 3.5 ship vendored tsort under its pre-3.6 name
rubygems/tsort, so prefer that when rubygems/vendored_tsort is missing.
Requiring the real tsort activated the tsort default gem, breaking the
guarantee that bundler/setup activates no gems. This mirrors the
fallback chain already used by vendored_net_http.

ruby/rubygems@7300743bfe

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… older than 4.1

Gem::Installer#remove_stale_plugins only exists since RubyGems 4.1, so
installing with no_install_plugin set on an older system RubyGems raised
NameError. Reimplement it in Bundler's installer, like generate_plugins
already is.

ruby/rubygems@d3037ba378

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…on under test

Under RGV=system the harness runs on the checked out RubyGems while
spec-spawned processes run system RubyGems, so rubygems-tagged filters
and expectations interpolating Gem::VERSION were checking the wrong
version. Capture the system RubyGems version before switching and use it
for the rubygems: exclusion filter, version-dependent expectations, and
the global gem cache location. The cached git gemspec comparison now
serializes with the RubyGems that wrote the file, since Specification#to_ruby
output differs across versions.

ruby/rubygems@62fa95fe35

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@pull pull Bot locked and limited conversation to collaborators Jul 14, 2026
@pull pull Bot added the ⤵️ pull label Jul 14, 2026
@pull pull Bot merged commit 605b28e into turkdevops:master Jul 14, 2026
1 of 3 checks passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants