Ractor Safe Rails
We would like to add experimental support for serving Rails requests from non-main Ractors.
Ractors are Ruby’s actor-based concurrency primitive for parallel execution without shared mutable state. Today, Rails applications and framework internals rely on mutable global state in ways that prevent a Rails application from being made shareable or called safely from a non-main Ractor.
The goal of this work is to establish a minimal, opt-in foundation for Ractor-compatible request handling in Rails.
Conceptually, we would like this shape to become possible:
Ractor.make_shareable(Rails.application)
Ractor.new(env) do |env|
Rails.application.call(env)
end
Strategy
The big picture end-goal is to run requests inside of a non-main Ractor. There are multiple ways to achieve that goal, but we want to take an “outside in” approach. That means we want to serve requests within non-main Ractors and fallback to the main Ractor for incompatible code, rather than going “inside out” by making all code Ractor-safe first and then serving requests fully from non-main Ractors.
We are choosing this approach because we believe it allows us to make progress more quickly. Even if only 10% of the request is run outside of the main Ractor, that is 10% more than we have today.
Additionally, this approach doesn’t require application authors to change their applications. For example, nobody is required to wrap code in Ractor.new { … } blocks.
The outside-in approach is still compatible with other approaches (that could be worked on in parallel). We just believe it offers a practical path to realizing benefits in production more quickly. Over time, we can move more of the request path off the main Ractor and reduce the amount of work that must be delegated back to it.
Initial scope
Our initial goal for experimental support is for a newly generated Rails application to serve a set of scaffolded routes from a non-main Ractor.
This gives us a small enough target to make progress while exercising a meaningful portion of the framework including:
-
Boot process & configuration
-
Database access
-
View rendering
-
Routing & middleware
The initial experiment will not support all Rails features. Explicitly out of scope:
-
Development mode (eg lazy loading)
-
Action Cable
-
Active Job
-
Non-Rails dependencies like Solid Cache
Once a baseline of support is established we expect to expand support to the above and other areas, with the support of the community.
Discovery process
The process of discovering all the changes needed to make the framework Ractor safe is labour intensive. The process involves booting the application, trying to make it shareable and fixing the first error encountered. This must be repeated until the application is shareable, and then again until a request can be served.
To help speed up this process we have completed a number of AI driven prototypes. Here is one example AI prototype that can successfully serve scaffolded routes from a non-Main Ractor. The code and solutions in this prototype are not what we intend to propose upstream. The prototypes provide a map of the problem space that needs to be covered in order to make the framework Ractor-safe.
You can see the scaffolded application that we are using to test Ractor support here.
Pragmatic tradeoffs for experimental support
The goal of this initial phase is experimental support only, with no guarantee of performance or production readiness. To that end, we expect to make pragmatic tradeoffs to meet our goal of serving requests from non-main Ractors.
Some work will initially be dispatched back to the main Ractor when it depends on behavior that is difficult or impossible to perform from a non-main Ractor, such as code loading or lazily evaluated global state. These fallbacks will be explicit and treated as areas for future improvement. Explicitly stated, our goal is that 0% of the request should be dispatched to the main Ractor, but initially it will be greater than 0%.
Similarly, some internal framework caches may initially become Ractor-local rather than globally shared. Common concurrent data structures like Concurrent::Map are not Ractor-safe, so Ractor-local caches may be the most practical first step. Over time, we can evaluate which caches should be shareable, which should remain Ractor-local, and which should be accessed through explicit Ractor-safe APIs.
Next steps
We welcome your questions and feedback on this proposal.
We have also begun opening PRs with some initial patterns for Ractor-safety that we would like to establish. You can find those tagged with the Ractor support label.