Repository navigation
feat(ractorize): the ractorize job, proven by a report-only lane on Rails main - #26
Merged
Merged
Conversation
…ails main `conformance-kit check --ractorize` boots `ractorize.app` the way Rails 8.2 runs an app across Ractors: in production, with `ActiveSupport::Ractors.unshareable_proc_action = :raise`, then `Rails.application.ractorize!` and `require "i18n/ractorize"`. Each `ractorize.requests` path is served inside a non-main Ractor; a failure names the root cause and its first frame, also when the app's error page swallowed it (env["action_dispatch.exception"]). Then the audit runs on `ractorize.audit_namespaces` (default audit.namespace) in the same process. It skips with the reason below Ruby 4.0 or on a Rails without ractorize!. - New .conformance.yml keys: ractorize.app, .requests, .audit_namespaces, .setup (validated like the rest: unknown key exits 2). - config/ractorize.gemfile: the repo's Gemfile with Rails main, Rack main and Shopify/i18n#5 swapped in (it drops the repo's own lines for them, since Bundler refuses a gem declared twice). - conformance.yml: a Ractorize job, only when ractorize.app is set, continue-on-error until Rails 8.2 ships ractorize!. - main.yml: a report-only "Ractorize lane" (Ruby 4.0, the same gemfile) runs the fixture specs: a boot violation (a route block appended from an initializer) and a render violation (a shallow-frozen class ivar) must fail, naming the file and line; a conforming engine must pass. Refs #22 Claude-Session: https://claude.ai/code/session_01JCaQrTh5yGHrueUmq69PKs
From the fable-validator pass: - The runner checks Rails::Application#ractorize! before setting unshareable_proc_action = :raise, so a Rails that has ActiveSupport::Ractors but no ractorize! skips instead of failing its boot. Checked end to end on released Rails 8.1.4: skipped, exit 0. - conformance.yml: a repo whose bundle lacks conformance-kit gets an ::error naming the Gemfile line, not a KeyError backtrace. - README: the example frame is an absolute path, as the runner prints it. Refs #22 Claude-Session: https://claude.ai/code/session_01JCaQrTh5yGHrueUmq69PKs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #22.
conformance-kit check --ractorizeboots a gem's test app the way Rails 8.2 runs an app across Ractors. The reusable workflow gains a report-only Ractorize job. The kit proves the check against fixture apps in a report-only "Ractorize lane" on Rails main: option (a) from the issue discussion.The job. For
ractorize.appit:ractorize.setupin the app (withractor.env,RAILS_ENV=production);ActiveSupport::Ractors.unshareable_proc_action = :raiseand boots in production;Rails.application.ractorize!andrequire "i18n/ractorize";ractorize.requestspath insideRactor.new;ractorize.audit_namespaces(defaultaudit.namespace) in the same process.A failed request names the root cause and its first frame, also when the app's error page swallowed the exception (
env["action_dispatch.exception"]). Below Ruby 4.0, or on a Rails withoutractorize!, it skips with the reason and exits 0.What the lane proves (Ruby 4.0.7, Rails main
0d022e0, Rack maina9833c8, Shopify/i18n#51873db3):Changes
.conformance.yml: new keysractorize.app,ractorize.requests,ractorize.audit_namespacesandractorize.setup. They are validated like the rest: an unknown key exits 2, and so does--ractorizewithout the app or itsconfig/environment.rb.config/ractorize.gemfile: the repo's own Gemfile with Rails main, Rack main and Make I18n.fallbacks and INTERPOLATION_PATTERN readable from non-main Ractors Shopify/i18n#5 swapped in. It drops the repo's lines for those gems and Rails' component gems, since Bundler refuses a gem declared twice. Consumers copy it toGemfile.ractorize; the lane uses the same file.conformance.yml: aractorizejob, only whenractorize.appis set,continue-on-erroruntil Rails 8.2. It changes no existing job or required check name.main.yml: the report-only Ractorize lane, which runsrspec --tag ractorizeon Ruby 4.0 with that gemfile.main_onlyfor Rails::Engine state), the bundle, and that the job is report-only. CHANGELOG under the unreleased 0.5.0.Test plan
check --ractorize(exit 2 cases, setup stage, skip), the runner's skip gates, the request layer (serve inside a Ractor, root cause, swallowed exception), the gemfile override, and both workflows. Each was watched failing first. The gemfile spec also fails if the override is removed (Bundler: "You cannot specify the same gem twice").bundle exec rakeon Ruby 3.4.2: 243 examples, 0 failures; RuboCop clean.bundle exec rspecon 4.0.7 (243, 0 failures) and 3.2.8 (242, 0 failures). The lane's full suite: 243, 0 failures.audit.require.check --ractorizeskips ("ActiveSupport::Ractors is unavailable"), exit 0.ractorize.app(the issue's last criterion, a follow-up PR there).fable-validator: PASS WITH NOTES. I fixed three notes in the second commit: the skip ordering, the
KeyErrorin the consumer step, and the README frame.Accepted risks:
404and never exercises the render path; an auth redirect is a legitimate answer.refs/pull/5/headmoves with that PR and there is no committed lock, so both lanes re-resolve every run. Expected while report-only.ractor.ruby. A repo on 3.4 bundles Rails main and then skips."500 (0 bytes)"in the render fixture spec depends on Rails' default error body (lane only).Not verified:
db:schema:cache:dump,check_schema_cache_dump_version = false): no Active Record fixture here. It comes from the issue and Rails'ractors_test.rb, not from a run in this PR.Deviations & judgment calls
config/ractorize.gemfile(copied toGemfile.ractorize) instead of a generated file; it drops the repo's own lines for rails/rack/i18n and Rails' component gems (Bundler refuses a gem declared twice), then declares the branches. The kit's lane uses the same file, so the consumer path is the one exercised.ractorize.audit_namespacesdefaults toaudit.namespace.ractorize.setup(a command run in the app directory with RAILS_ENV=production before boot: schema load, schema cache dump, asset precompile);ractor.envapplies to the ractorize stages too, so one env map per repo.Proc's self is not shareable: #<Proc:... initializer.rb:3>), so the root cause alone names the culprit for boot failures.rack.input/rack.errorsStringIOs created inside the request Ractor, so the runner needs neitherRack::MockRequestnor rack#2523.ref: "refs/pull/5/head"on Shopify's repo), not theractor_supportbranch. Discovery behind it: ActiveSupport always requiresi18n/backend/fallbacks, soActionView::LookupContextreadsI18n.fallbacks(a class variable on the branch) on every render, and every request in a Ractor fails on it. On the branch the job would fail every consumer whatever their gem does. Switch back tobranch: "ractor_support"when conformance-kit 0.2: per-repo settings in .conformance.yml, one org-required conformance.yml workflow #5 merges.config.enable_reloadingdefaults to true outside a generated production.rb, and the i18n railtie then registers a reloader that can't be made shareable: boot fails under :raise. Fixture apps set it to false; the README lists it.show_exceptions, an exception inside the Ractor comes back as a 500 with an empty body;ShowExceptionskeeps it inenv["action_dispatch.exception"], so the runner reports it from there instead of changing the app's config..conformance.ymlallowlist Rails::Engine's own@called_from/@initializerswith anancestor:rule, as a real engine's would; the audit otherwise reports them on every engine.attr_readeris a C method, so a render violation's first frame is the line that calls the reader (the controller), not theattr_readerline.https://claude.ai/code/session_01JCaQrTh5yGHrueUmq69PKs
Summary by cubic
Adds
conformance-kit check --ractorize, which boots a gem's test app the way Rails 8.2 runs an app across Ractors: production boot withActiveSupport::Ractors.unshareable_proc_action = :raise, thenRails.application.ractorize!, eachractorize.requestspath served inside a non-main Ractor, and the audit run in the same process. The reusable workflow gains a report-only Ractorize job, and the kit is proven against fixture apps on Rails main by a report-only "Ractorize lane" in this repo's CI.New Features
check --ractorizerunsractorize.setup, then the boot/request/audit runner in its own process; it skips with the reason below Ruby 4.0 or on a Rails withoutractorize!, exiting 0..conformance.ymlkeysractorize.app,.requests,.audit_namespacesand.setup, validated like the rest: an unknown key exits 2, and so does--ractorizewithout an app or itsconfig/environment.rb.config/ractorize.gemfileswaps Rails main, Rack main and Make I18n.fallbacks and INTERPOLATION_PATTERN readable from non-main Ractors Shopify/i18n#5 into a repo's Gemfile, dropping the repo's own lines for them and for Rails' component gems because Bundler refuses a duplicate.Report-only rollout (until Rails 8.2)
conformance.ymlgains a Ractorize job only whenractorize.appis set,continue-on-errorand not a required check, so no existing job changes.main.ymlgains the Ractorize lane that runs the fixture specs on Ruby 4.0 with that gemfile: a boot violation and a render violation must fail naming file and line, and a conforming engine must pass.Written for commit 4ae888c. Summary will update on new commits.