Skip to content

Replace Ractor.check_isolation with RUBY_RACTOR_CHECK_ISOLATION - #1055

Merged
yaroslav-shopify merged 6 commits into
Shopify:ractor-check-isolationfrom
yaroslav-shopify:ractor-check-isolation
Sep 18, 2026
Merged

yaroslav-shopify merged 6 commits into
Shopify:ractor-check-isolationfrom
yaroslav-shopify:ractor-check-isolation

Conversation

@yaroslav-shopify

Copy link
Copy Markdown

Replaces Ractor.check_isolation(*args) { } with a process-wide environment variable, RUBY_RACTOR_CHECK_ISOLATION, so an application can be swept for Ractor-isolation violations by booting under the variable — no call-site edits.

What changes

  • The variable is read at boot exactly like RUBY_RACTOR_EXCLUSIVE (atoi > 0, same two files). When set, every Ractor.new behaves as Ractor.check_isolation did: the Proc is not isolated, arguments and return value pass by reference, and isolation violations are downgraded to :ractor_isolation warnings.
  • Ractor.check_isolation is removed; the env var is the only entry point.
  • The one-time advisory (other Ractors may run in parallel; suppressed when RUBY_RACTOR_EXCLUSIVE=1 engages) moves to boot and now survives -W0.
  • Fixes found while reviewing the fall-through paths this creates:
    • fork: a non-main Ractor's fork now warns and refuses (EPERM) instead of producing a child whose VM lost its other Ractors' threads while their objspaces stay mapped
    • the boot-advisory test no longer fails on builds without M:N threads (where exclusive mode cannot engage, the advisory correctly still prints)
    • ractor_shareable_proc's check-mode guard documented — it prevents a double warning, since the fall-through to rb_proc_ractor_make_shareable re-reports the same violation

Semantics

  • Main-Ractor violations still raise; only non-main Ractors downgrade.
  • Nested Ractor.new inside a check Ractor is also a check Ractor (the flag is process-wide).
  • Truthiness matches the sibling variable: 1 on, true/yes off.
  • The cross-objspace GC hazards flagged in review of the method form remain and now apply process-wide: a non-main Ractor mutating a main-owned container can still abort the VM. Accepted as the cost of a sweep tool; a warned process is not guaranteed to survive the operations it warns about.

Testing

  • test/ruby/test_ractor.rb migrated to the env var, including regression tests for the fork refusal and the boot advisory
  • make btest: 2066 PASS; make test-all: 36539 tests, 0 failures, 0 errors

Trigger the isolation check from a boolean environment variable read once at
boot instead of from a method call, so an application can be swept for
worker-Ractor incompatibilities without editing every Ractor.new call site.
Under the variable every non-main Ractor behaves the way check_isolation did:
the Proc is not isolated, arguments and the return value pass by reference,
and violations are downgraded to :ractor_isolation warnings.

Read the variable the way RUBY_RACTOR_EXCLUSIVE is read and relocate the
one-shot advisory to boot, keeping its suppression under exclusive mode.
Replace the per-Ractor isolation_check field with the process-wide flag. The
three sites in thread.c run in the parent's context while creating the child,
so they test the flag directly rather than the shared predicate, which
evaluates the current Ractor and would never fire from the main one.

Because the flag is process-wide it also covers nested Ractors, which
previously reverted to raising, so the test asserting that is inverted and a
companion pins that Ractor.new still raises when the variable is unset.
Downgrading the isolation violation to a warning left rb_fork_ruby falling
through into the fork it had just reported, because rb_raise is NORETURN and
rb_ractor_isolation_violation is not. A sweep of an application that calls
fork therefore forked, where the same application previously raised
Ractor::IsolationError.

fork keeps only the calling thread, so the child inherited a VM whose other
Ractors' threads were gone while their objspaces were still mapped. Refuse
the fork after warning and report it as a failed one: proc_fork_pid turns -1
into rb_sys_fail, rb_daemon returns -1, and the --help pager stops paging.
RUBY_RACTOR_EXCLUSIVE only engages when USE_MN_THREADS is set, so on a
non-MN build the advisory correctly still prints and the unconditional
zero-advisory assertion would fail. Branch on the +MN marker in
RUBY_DESCRIPTION, asserting suppression where exclusive engages and
advisory presence where it cannot.
The fall-through to rb_proc_ractor_make_shareable re-reports the same
violation, warned in check mode, so the upstream report is skipped there
to avoid warning twice. Flagged as suspicious by two review passes;
document it at the site.
The advisory announces a mode that changes process behavior, so it must
not be silenced by verbosity flags. Kernel.warn in the deleted method
form printed under -W0; the relocated rb_warn did not. Use fprintf.
Under RUBY_RACTOR_CHECK_ISOLATION a hot violation warns on every hit,
burying the sweep output. Key each warning on the violation's format
string and Ruby call site, print the first, and count the rest into a
one-line summary at process exit. The raising path is unchanged.
@yaroslav-shopify
yaroslav-shopify merged commit 3463786 into Shopify:ractor-check-isolation Sep 18, 2026
108 of 128 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant