Repository navigation
Conversation
The template's config.ru requires i18n/ractorize after ractorize!, but the runner only called ractorize!, so I18n's config stayed unshareable and every page that translates (the scaffold's forms) raised on @backend from I18n::Config in the request Ractor. Released i18n has no such file, so a missing one is skipped.
Seeding through Post on the main Ractor defined its attribute methods before ractorize!, which hid that a production boot only defines them from a schema cache dump (with check_schema_cache_dump_version = false, as the template sets). Without one, a model's first use in a request Ractor raises on ActiveRecord::AttributeMethods::GeneratedAttributeMethods::LOCK. Run db:prepare and db:schema:cache:dump before booting, and seed with SQL so no model is touched on the main Ractor.
| "Seed", "id" | ||
| ) | ||
| end | ||
| end |
There was a problem hiding this comment.
This make sense. But maybe we should just shell out to db:seed for this too
There was a problem hiding this comment.
Yeah, I think that makes sense! Will address tomorrow morning.
There was a problem hiding this comment.
Done in 4e8ed0b. db/seeds.rb now creates the two posts, and the runner adds db:seed to the bin/rails db:prepare db:schema:cache:dump call. Seeding goes through Post again, just in that separate process, so the runner itself still never touches the model before ractorize!. It reads the two ids back with a SELECT.
On Ruby 4.0.7 the committed app gives the same 1/16 as before, with the same failure for every request, both from a fresh database and with an existing one. The ids just come out higher, because SQLite keeps counting after Post.delete_all.
db/seeds.rb now creates the two posts the requests use, and the runner runs db:seed in the same bin/rails process as db:prepare and db:schema:cache:dump. Seeding can use Post again, and the runner's own process still never touches a model before the application is ractorized. The runner reads the two ids back with SQL.
I copied
bin/ractor-testinto an app generated fromractor_safe_rails_template.rband found two places where the runner boots differently from a server running that app. Both hide failures.Changes
i18n/ractorizeafterractorize!, as the template'sconfig.rudoes. The runner only calledractorize!, soI18n.configstayed unshareable and both scaffold form pages (/posts/new,/posts/:id/edit) raised@backend from I18n::Configin the request Ractor. The require is wrapped inrescue LoadErrorbecause the committed app is on released i18n 1.15.2, which has no such file.LoadErrorisn't aStandardError, so without the rescue it gets past phase 1'srescue => errorand crashesbin/ractor-test.Poston the main Ractor beforeractorize!, and that definedPost's attribute methods. A production boot defines them only from a schema cache dump: withcheck_schema_cache_dump_version = false, which the template sets, theactive_record.define_attribute_methodsinitializer defines them for every table found indb/schema_cache.yml. Without a dump, the first request to use a model has to define them inside its Ractor, and it raisescan not access non-shareable objects in constant ActiveRecord::AttributeMethods::GeneratedAttributeMethods::LOCK. The runner now runsbin/rails db:prepare db:schema:cache:dump db:seedbefore booting (this replaces theload db/schema.rbfallback).db/seeds.rbcreates the two posts the requests use, so seeding goes throughPostin that process and the runner reads the ids back with SQL..gitignoreignores the dumps it writes. The committed app writes three: primary, cache and queue.Verification
Setup: Ruby 4.0.7 (the repo pins 4.0.2) and rails/rails main
7ca3884f17. I generated the app withrails new -m ractor_safe_rails_template.rb --skip-solid, so it is onload_defaults 8.2, SQLite. Then I ranbin/rails g scaffold Post body:textandassets:precompile, and copied inbin/ractor-test,script/ractor_test.rbandscript/requests.yml.erb.With 8.2 defaults every HTML page fails on
@configuration from Herbbefore reaching either change. To see past that, the erubi column setsconfig.action_view.erb_implementation = :erubilocally. That setting is not part of this PR; Herb is covered in #23.@backend from I18n::ConfigThis branch's seeding without the
db:schema:cache:dumpstep gives 1/16 (erubi or herb). With erubi, 13 requests fail onGeneratedAttributeMethods::LOCK, including the POST/PATCH/DELETE that pass on main. That is what the old model-based seeding was hiding. A deploy built from the template runsdb:prepareand nothing writes the dump, so it would fail the same way.The 5 failures left at 11/16 are Ruby 4.0 class-variable reads from a non-main Ractor, outside the scope of this PR:
GET /up(@@silencerfromActiveSupport::BroadcastLogger, throughsilence_healthcheck_path) and the 4 JSON routes (@@key_formatterfromJbuilder).The committed app (
bundle exec bin/ractor-test): 1/16 on main and 1/16 on this branch, with the same errors (Rack::Mime::MIME_TYPESon GETs, a class ivar set on writes). The branch doesn't change that app's results. It now runsdb:prepareand writes the three dumps.RuboCop adds no offenses to
script/ractor_test.rb(the 5 reported byrubocop-rails-omakaseare already on main).Conformance case
This PR changes the runner itself (
script/ractor_test.rb), so it adds no unit test. The case is the runner:The numbers under Verification come from an app generated from the template, not from this repo's committed app. The repeatable check is running
bin/ractor-testin CI against an app regenerated from the template, so a runner that boots differently from a deploy shows up as a changed pass count (#23).