ractor-rails-shim 0.2.2 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +89 -5
- data/README.md +14 -13
- data/lib/ractor_rails_shim/patches/action_controller.rb +10 -1
- data/lib/ractor_rails_shim/patches/action_dispatch.rb +1 -1
- data/lib/ractor_rails_shim/patches/action_view.rb +2 -2
- data/lib/ractor_rails_shim/patches/active_model_attribute.rb +17 -0
- data/lib/ractor_rails_shim/patches/active_support.rb +20 -20
- data/lib/ractor_rails_shim/patches/activerecord.rb +102 -20
- data/lib/ractor_rails_shim/patches/class_attribute.rb +59 -43
- data/lib/ractor_rails_shim/patches/core.rb +2 -2
- data/lib/ractor_rails_shim/patches/devise.rb +1 -1
- data/lib/ractor_rails_shim/patches/execution_wrapper.rb +1 -1
- data/lib/ractor_rails_shim/patches/kaminari.rb +1 -1
- data/lib/ractor_rails_shim/patches/make_shareable.rb +9 -1
- data/lib/ractor_rails_shim/patches/mattr_accessor.rb +2 -2
- data/lib/ractor_rails_shim/patches/orm_adapter.rb +1 -1
- data/lib/ractor_rails_shim/patches/rack.rb +5 -5
- data/lib/ractor_rails_shim/patches/rails_module.rb +13 -6
- data/lib/ractor_rails_shim/patches/route_helpers.rb +21 -0
- data/lib/ractor_rails_shim/patches/zeitwerk_registry.rb +8 -1
- data/lib/ractor_rails_shim/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 267a3734452aa4069b68599b193d8bebd3fa6e386836ec9ec4238d9667f6b37a
|
|
4
|
+
data.tar.gz: 748ee127a59a4e7cc0b90b16ff7339edb2dfd6cde3b15df01c1ff29d910d05c3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fdd580e1b1c24d324dfc9067a323dff580acd612ac8cfc2551424d4d359d5fc93d55c7ae7a9825efecad3103ef2c59b1b481a34d9270471f23fd19747dc9a52c
|
|
7
|
+
data.tar.gz: '0523817853ccdfc9444c0815fcb943b7cb3f745519b5baf6683e269e60c81889f97500e7e9e34da7bd3da7e74a3fd3033c5bbd25c4de6296337735ed6fd8301c'
|
data/CHANGELOG.md
CHANGED
|
@@ -7,9 +7,73 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
## [0.2.5]
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **Nil-sentinel storage bug in IES-routed accessors.** The shim's
|
|
14
|
+
`mattr_accessor` / `class_attribute` / Rails-module / AR / Rack / Devise /
|
|
15
|
+
Kaminari / I18n / Inflector / ActionView / ActionController /
|
|
16
|
+
ActionDispatch / Zeitwerk / ExecutionWrapper readers used
|
|
17
|
+
`return v unless v.nil?` to detect "no per-Ractor override has been set."
|
|
18
|
+
That made `Foo.x = nil` (or `= false`) indistinguishable from "never set,"
|
|
19
|
+
so the reader silently fell through to the default/fallback instead of the
|
|
20
|
+
user's explicit `nil`/`false` — a divergence from threaded Rails, where
|
|
21
|
+
class variables distinguish "undefined" (`class_variable_defined?` is
|
|
22
|
+
false) from "set to nil." Replaced the nil-sentinel with `<storage>.key?`
|
|
23
|
+
at every IES / `CLASS_ATTR_VALUES` / `SHAREABLE_FALLBACK` reader so any
|
|
24
|
+
explicit assignment — including `nil` and `false` — wins over the
|
|
25
|
+
fallback. The single `Ractor.current[:active_record_connection_handler]`
|
|
26
|
+
reader keeps the nil-sentinel (with an explanatory comment) because
|
|
27
|
+
`Ractor#[]` has no `key?` method and `connection_handler=` is never called
|
|
28
|
+
with nil in practice. 58 sites transformed across 13 patch files.
|
|
29
|
+
Regression specs in `spec/sentinel_spec.rb` cover the
|
|
30
|
+
`mattr_accessor :flag, default: true; Flag = nil; Flag # => nil` and
|
|
31
|
+
`class_attribute :setting, default: :on; Setting = nil; Setting # => nil`
|
|
32
|
+
cases.
|
|
33
|
+
|
|
34
|
+
- **`Ractor::IsolationError` in worker schema reload.** When a worker Ractor
|
|
35
|
+
hit a cold schema (e.g. first request after boot, or after
|
|
36
|
+
`ActiveRecord::Base.connection_handler.clear_all_connections!`),
|
|
37
|
+
`ModelSchema::ClassMethods#reload_schema_from_cache` and
|
|
38
|
+
`Timestamp::ClassMethods#reload_schema_from_cache` wrote to class instance
|
|
39
|
+
variables (`@columns`, `@columns_hash`, …) on the shared model classes,
|
|
40
|
+
raising `Ractor::IsolationError: can not set instance variables of
|
|
41
|
+
classes/modules by non-main Ractors`. Patched both methods (plus
|
|
42
|
+
`AttributeRegistration::ClassMethods#reset_default_attributes!`) to clear
|
|
43
|
+
the worker's IES slots instead of writing class ivars, so the next read
|
|
44
|
+
re-derives the schema in the worker's own IES. Without this, any worker
|
|
45
|
+
that reloaded its schema crashed the first request after the reload.
|
|
46
|
+
|
|
47
|
+
## [0.2.4]
|
|
48
|
+
|
|
49
|
+
### Performance
|
|
50
|
+
- **class_attribute reader (ractor mode) is now allocation-free.** The
|
|
51
|
+
generated `__class_attr_*` accessors previously walked `self.ancestors` and
|
|
52
|
+
built a fresh `Symbol` via string interpolation for *every* ancestor on
|
|
53
|
+
*every* read — the dominant allocation source for GET requests (a Rails class
|
|
54
|
+
has 20–40 ancestors, and class_attribute is read constantly: controller
|
|
55
|
+
filters, view partial paths, AR `strict_loading`, form builder, logger, …).
|
|
56
|
+
In ractor mode the writer already collapses every write to the defining
|
|
57
|
+
owner's single key, so the ancestor walk was dead code. The reader now does a
|
|
58
|
+
single literal-symbol lookup against `IsolatedExecutionState[key]` then
|
|
59
|
+
`SHAREABLE_FALLBACK[key]`, eliminating the per-read `Array` + `Symbol`
|
|
60
|
+
churn. This cuts request allocations substantially and, with them, the
|
|
61
|
+
garbage-collection share of CPU time (was ~33% of CPU on `GET /posts`).
|
|
62
|
+
|
|
63
|
+
## [0.2.3]
|
|
64
|
+
|
|
65
|
+
### Fixed
|
|
66
|
+
- **Cold `GET /up` `SystemStackError` in worker Ractors (kino `:ractor`).**
|
|
67
|
+
Rails' `ActionDispatch::Routing::RouteSet#generate_url_helpers` builds a
|
|
68
|
+
module whose `self.included(base)` hook re-dups the module and re-includes
|
|
69
|
+
it while `!base._routes.equal?(@_proxy._routes)`. Under the frozen,
|
|
70
|
+
Ractor-shareable app graph a worker Ractor's controller reports
|
|
71
|
+
`base._routes` as `nil`, so the equality never holds and the hook
|
|
72
|
+
re-includes forever (empty Ruby backtrace, first request only; respawns and
|
|
73
|
+
later requests are fine). The shim's `route_helpers.rb` `generate_url_helpers`
|
|
74
|
+
override now bounds the reinclude to once per base, preserving the
|
|
75
|
+
route-alignment intent without the infinite loop. Verified: cold `GET /up`
|
|
76
|
+
returns 200 in `kino -m ractor`; both `:ractor` and `:threaded` modes clean.
|
|
13
77
|
|
|
14
78
|
## [0.2.2]
|
|
15
79
|
|
|
@@ -22,8 +86,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
22
86
|
`ActiveRecord::ConnectionNotEstablished` on the very first request. Setup is
|
|
23
87
|
now serialized with a per-Ractor `Thread::Mutex`, and `rebind_constants`
|
|
24
88
|
re-fetches each namespace parent so concurrent setup cannot clobber a module
|
|
25
|
-
out from under another thread.
|
|
26
|
-
|
|
89
|
+
out from under another thread.
|
|
90
|
+
- **Per-thread ActiveRecord connection handler (intermittent
|
|
91
|
+
`ConnectionNotEstablished` on `kino :ractor -w5 -t5`).** `init_worker_ar_connections!`
|
|
92
|
+
stored the per-Ractor `ConnectionHandler` in `ActiveSupport::IsolatedExecutionState`,
|
|
93
|
+
which is **per-thread** (`Thread.attr_accessor`). The init thread set it, but
|
|
94
|
+
the other worker threads in the same Ractor saw `nil` →
|
|
95
|
+
`ConnectionNotEstablished: No connection handler for Ractor X` on a fraction of
|
|
96
|
+
requests. The handler now lives in `Ractor.current` (per-Ractor, shared by all
|
|
97
|
+
of the worker's threads), and `ActiveRecord::Base.connection_handler` reads it
|
|
98
|
+
there first.
|
|
99
|
+
- **Write-path `Ractor::IsolationError` on `redirect_to @post`
|
|
100
|
+
(`kino :ractor -w5 -t5`, `POST /posts`).** `ActiveModel::AttributeMethods::
|
|
101
|
+
ClassMethods#attribute_method_patterns_cache` stored a mutable `Concurrent::Map`
|
|
102
|
+
in a class instance variable (unshareable). Hit via `redirect_to @post` →
|
|
103
|
+
`respond_to?` → `matched_attribute_method`, raising from worker Ractors. The
|
|
104
|
+
cache is now routed through `Ractor.current` (per-Ractor, keyed per class) and
|
|
105
|
+
populated lazily per Ractor — content is deterministic, so per-Ractor
|
|
106
|
+
recomputation is correct.
|
|
107
|
+
|
|
108
|
+
With these three fixes, `kino :ractor (-w5 -t5)` serves `/up`, `GET /posts`,
|
|
109
|
+
and `POST /posts` (authenticated write + 302) with 0 transport failures and 0
|
|
110
|
+
server errors under sustained load.
|
|
27
111
|
|
|
28
112
|
### Added — ActiveRecord query-path ractor-safety (Blocker 1 deep work)
|
|
29
113
|
- `RactorRailsShim.worker_ar_init(app)` — a shareable Rack middleware that
|
data/README.md
CHANGED
|
@@ -5,7 +5,7 @@ A monkey-patch shim that reroutes Rails' class-level instance variable accessors
|
|
|
5
5
|
**Status:** proof-of-concept / stopgap. The goal is for Rails to do this upstream, at which point this gem becomes a no-op and can be removed.
|
|
6
6
|
|
|
7
7
|
**Current status:** on a full Rails 8.1 app (Devise 5, Propshaft, Kaminari,
|
|
8
|
-
PG) under Ruby 4.0.
|
|
8
|
+
PG) under **official Ruby 4.0.6**, worker Ractors serve **every** routable action —
|
|
9
9
|
`GET /up`, full ERB view rendering, Devise sign-in/sign-out (CSRF issuance
|
|
10
10
|
**and** validation), and authenticated Devise **writes** (`POST /posts` → 302,
|
|
11
11
|
row persisted). A worker Ractor dispatches `GET /up` → **HTTP 200** via
|
|
@@ -16,9 +16,13 @@ and patches the raw class-ivar accessors Rails reads per-request
|
|
|
16
16
|
`PathRegistry`, `I18n`, `AbstractController` lazy ivars, `Rack::Request`/`Utils`,
|
|
17
17
|
`ExecutionContext`, etc.).
|
|
18
18
|
|
|
19
|
-
**
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
**No patched Ruby or kino required.** Official Ruby 4.0.6 ships the frozen-iseq
|
|
20
|
+
call-cache fix (#22075) and the cross-ractor env-string fix, so the SIGBUS crashes
|
|
21
|
+
that previously required the DDKatch patched Ruby/kino forks are resolved in core.
|
|
22
|
+
Both SIGBUS classes were the only reason a patched build was ever needed; on 4.0.6
|
|
23
|
+
the shim runs `kino -m ractor` on the official `kino` gem under sustained read
|
|
24
|
+
**and** write load (verified: 0 transport failures / 0 server errors across
|
|
25
|
+
`/up`, `GET /posts`, `POST /posts` in the benchmark matrix).
|
|
22
26
|
|
|
23
27
|
## Requirements
|
|
24
28
|
|
|
@@ -35,14 +39,11 @@ run a Rails app in Ractor mode is
|
|
|
35
39
|
developed and tested against a real Rails 8.1 app **served by kino**; that is
|
|
36
40
|
the configuration it is verified against.
|
|
37
41
|
|
|
38
|
-
**
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
`yaroslav/kino`) if you hit write-path crashes. To build it: clone the fork,
|
|
44
|
-
check out that branch, then `asdf local rust 1.85.0 && cargo build --release`
|
|
45
|
-
(the native extension is compiled by the Rails app's `bundle install`).
|
|
42
|
+
**kino:** the Ractor-mode server is
|
|
43
|
+
[kino](https://github.com/yaroslav/kino) (`kino -m ractor`). On official Ruby
|
|
44
|
+
4.0.6 the **upstream `kino` gem (0.1.3) works as-is** — no fork or patch is
|
|
45
|
+
required. (Historically a DDKatch/kino fork carried a per-ractor env-string
|
|
46
|
+
cache fix; that fix is in official 4.0.6, so the fork is obsolete.)
|
|
46
47
|
|
|
47
48
|
**Benchmarks:** throughput/latency/memory of this shim + the test app under
|
|
48
49
|
kino `:ractor` vs Puma vs Falcon are documented in
|
|
@@ -72,7 +73,7 @@ This is the primary blocker preventing Rails from running in Ractor mode (see th
|
|
|
72
73
|
|
|
73
74
|
## How it works
|
|
74
75
|
|
|
75
|
-
The shim reroutes the accessor methods through `ActiveSupport::IsolatedExecutionState`, which is thread-local storage (`Thread.current[:key]`). Each Ractor has its own threads, so each Ractor gets its own slot automatically — verified on Ruby 4.0.
|
|
76
|
+
The shim reroutes the accessor methods through `ActiveSupport::IsolatedExecutionState`, which is thread-local storage (`Thread.current[:key]`). Each Ractor has its own threads, so each Ractor gets its own slot automatically — verified on Ruby 4.0.6. Rails already uses this primitive for `ActiveRecord::ConnectionHandling.connection_handler` (for thread safety), so the pattern is proven in production.
|
|
76
77
|
|
|
77
78
|
Two storage paths, depending on the state's shape:
|
|
78
79
|
|
|
@@ -118,8 +118,17 @@ module RactorRailsShim
|
|
|
118
118
|
# (Hash class→bool) built at prepare_for_ractors! time. Workers read
|
|
119
119
|
# the registry; main reads its live @abstract ivar (set by abstract!
|
|
120
120
|
# / inherited). `internal_methods` loops on abstract?.
|
|
121
|
+
#
|
|
122
|
+
# The registry is frozen with `Ractor.make_shareable` at install time so
|
|
123
|
+
# it can travel the shared app graph. Mutating a frozen Hash raises
|
|
124
|
+
# FrozenError, so `abstract!` only writes the live ivar (main) and
|
|
125
|
+
# guards the registry write behind a mutability check. `abstract!`
|
|
126
|
+
# after install in main is a no-op on the registry (already captured);
|
|
127
|
+
# callers that need to flip a class to abstract post-install should
|
|
128
|
+
# rebuild the registry (rare — abstract! is a boot-time declaration).
|
|
121
129
|
def abstract!
|
|
122
|
-
|
|
130
|
+
reg = RactorRailsShim._abstract_registry
|
|
131
|
+
reg[self] = true if reg && !reg.frozen? && Ractor.main?
|
|
123
132
|
@abstract = true if Ractor.main?
|
|
124
133
|
end
|
|
125
134
|
|
|
@@ -101,7 +101,7 @@ module RactorRailsShim
|
|
|
101
101
|
req.singleton_class.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
102
102
|
def parameter_parsers
|
|
103
103
|
v = ActiveSupport::IsolatedExecutionState[#{pp_key_str}]
|
|
104
|
-
return v
|
|
104
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{pp_key_str})
|
|
105
105
|
if Ractor.main? && instance_variable_defined?(:@parameter_parsers)
|
|
106
106
|
v = @parameter_parsers
|
|
107
107
|
ActiveSupport::IsolatedExecutionState[#{pp_key_str}] = v
|
|
@@ -85,7 +85,7 @@ module RactorRailsShim
|
|
|
85
85
|
lc.singleton_class.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
86
86
|
def registered_details
|
|
87
87
|
v = ActiveSupport::IsolatedExecutionState[#{key_str}]
|
|
88
|
-
return v
|
|
88
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{key_str})
|
|
89
89
|
if Ractor.main? && instance_variable_defined?(:@registered_details)
|
|
90
90
|
@registered_details
|
|
91
91
|
else
|
|
@@ -257,7 +257,7 @@ module RactorRailsShim
|
|
|
257
257
|
dk.singleton_class.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
258
258
|
def view_context_class
|
|
259
259
|
v = ActiveSupport::IsolatedExecutionState[#{vcc_key_str2}]
|
|
260
|
-
return v
|
|
260
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{vcc_key_str2})
|
|
261
261
|
if Ractor.main? && instance_variable_defined?(:@view_context_class)
|
|
262
262
|
v = @view_context_class
|
|
263
263
|
ActiveSupport::IsolatedExecutionState[#{vcc_key_str2}] = v
|
|
@@ -64,6 +64,23 @@ module RactorRailsShim
|
|
|
64
64
|
types
|
|
65
65
|
end
|
|
66
66
|
end
|
|
67
|
+
|
|
68
|
+
# `reset_default_attributes!` (called by `reload_schema_from_cache` via
|
|
69
|
+
# the Attributes → Timestamp → ModelSchema super chain) writes
|
|
70
|
+
# `@default_attributes = nil` and `@attribute_types = nil` class ivars.
|
|
71
|
+
# In a worker Ractor those writes raise Ractor::IsolationError. The
|
|
72
|
+
# shim routes the *readers* above through IES, so in a worker we clear
|
|
73
|
+
# the IES slots instead (the next read rebuilds lazily). In main we keep
|
|
74
|
+
# the original class-ivar-clearing behavior.
|
|
75
|
+
def reset_default_attributes!
|
|
76
|
+
if Ractor.main?
|
|
77
|
+
@default_attributes = nil
|
|
78
|
+
@attribute_types = nil
|
|
79
|
+
else
|
|
80
|
+
ActiveSupport::IsolatedExecutionState.delete(:"rrs_default_attributes_#{object_id}")
|
|
81
|
+
ActiveSupport::IsolatedExecutionState.delete(:"rrs_attribute_types_#{object_id}")
|
|
82
|
+
end
|
|
83
|
+
end
|
|
67
84
|
end
|
|
68
85
|
end
|
|
69
86
|
|
|
@@ -98,7 +98,7 @@ module RactorRailsShim
|
|
|
98
98
|
# In a worker Ractor, class_attribute-backed `__callbacks`
|
|
99
99
|
# falls back to the empty default (see class_attribute.rb /
|
|
100
100
|
# make_shareable.rb), so controller `before_action` /
|
|
101
|
-
# `after_action` filters are normally
|
|
101
|
+
# `after_action` filters are normally SKIPPED. For
|
|
102
102
|
# `:process_action` we replay the captured SYMBOL filters
|
|
103
103
|
# (see make_shareable!#_capture_controller_callbacks!)
|
|
104
104
|
# so actions that depend on a before_action (e.g. `set_post`
|
|
@@ -186,7 +186,7 @@ module RactorRailsShim
|
|
|
186
186
|
notif.singleton_class.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
187
187
|
def notifier
|
|
188
188
|
v = ActiveSupport::IsolatedExecutionState[#{nkey_str}]
|
|
189
|
-
return v
|
|
189
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{nkey_str})
|
|
190
190
|
if Ractor.main? && instance_variable_defined?(:@notifier)
|
|
191
191
|
@notifier
|
|
192
192
|
else
|
|
@@ -212,7 +212,7 @@ module RactorRailsShim
|
|
|
212
212
|
def instance(locale = :en)
|
|
213
213
|
if locale == :en
|
|
214
214
|
v = ActiveSupport::IsolatedExecutionState[#{en_key_str}]
|
|
215
|
-
return v
|
|
215
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{en_key_str})
|
|
216
216
|
if Ractor.main?
|
|
217
217
|
existing = instance_variable_get(:@__en_instance__) if instance_variable_defined?(:@__en_instance__)
|
|
218
218
|
ActiveSupport::IsolatedExecutionState[#{en_key_str}] = existing
|
|
@@ -302,7 +302,7 @@ module RactorRailsShim
|
|
|
302
302
|
::ActiveSupport.singleton_class.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
303
303
|
def error_reporter
|
|
304
304
|
v = ActiveSupport::IsolatedExecutionState[#{er_key_str}]
|
|
305
|
-
return v
|
|
305
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{er_key_str})
|
|
306
306
|
if Ractor.main? && instance_variable_defined?(:@error_reporter)
|
|
307
307
|
@error_reporter
|
|
308
308
|
else
|
|
@@ -343,7 +343,7 @@ module RactorRailsShim
|
|
|
343
343
|
cfg.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
344
344
|
def default_locale
|
|
345
345
|
v = ActiveSupport::IsolatedExecutionState[#{dl_key_str}]
|
|
346
|
-
return v
|
|
346
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{dl_key_str})
|
|
347
347
|
if Ractor.main? && defined?(@@default_locale)
|
|
348
348
|
cv = @@default_locale
|
|
349
349
|
ActiveSupport::IsolatedExecutionState[#{dl_key_str}] = cv
|
|
@@ -360,7 +360,7 @@ module RactorRailsShim
|
|
|
360
360
|
end
|
|
361
361
|
def locale
|
|
362
362
|
v = ActiveSupport::IsolatedExecutionState[#{l_key_str}]
|
|
363
|
-
return v
|
|
363
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{l_key_str})
|
|
364
364
|
default_locale
|
|
365
365
|
end
|
|
366
366
|
def locale=(locale)
|
|
@@ -380,7 +380,7 @@ module RactorRailsShim
|
|
|
380
380
|
# set config.i18n.available_locales explicitly.
|
|
381
381
|
def available_locales
|
|
382
382
|
v = ActiveSupport::IsolatedExecutionState[#{av_key_str}]
|
|
383
|
-
return v
|
|
383
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{av_key_str})
|
|
384
384
|
if Ractor.main?
|
|
385
385
|
if defined?(@@available_locales) && (cv = @@available_locales)
|
|
386
386
|
ActiveSupport::IsolatedExecutionState[#{av_key_str}] = cv
|
|
@@ -404,7 +404,7 @@ module RactorRailsShim
|
|
|
404
404
|
end
|
|
405
405
|
def available_locales_set
|
|
406
406
|
v = ActiveSupport::IsolatedExecutionState[#{avs_key_str}]
|
|
407
|
-
return v
|
|
407
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{avs_key_str})
|
|
408
408
|
if Ractor.main? && defined?(@@available_locales_set) && (cv = @@available_locales_set)
|
|
409
409
|
ActiveSupport::IsolatedExecutionState[#{avs_key_str}] = cv
|
|
410
410
|
return cv
|
|
@@ -424,7 +424,7 @@ module RactorRailsShim
|
|
|
424
424
|
# documented I18n default).
|
|
425
425
|
def enforce_available_locales
|
|
426
426
|
v = ActiveSupport::IsolatedExecutionState[:ractor_rails_shim_i18n_enforce]
|
|
427
|
-
return v
|
|
427
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(:ractor_rails_shim_i18n_enforce)
|
|
428
428
|
if Ractor.main? && defined?(@@enforce_available_locales)
|
|
429
429
|
cv = @@enforce_available_locales
|
|
430
430
|
ActiveSupport::IsolatedExecutionState[:ractor_rails_shim_i18n_enforce] = cv
|
|
@@ -469,7 +469,7 @@ module RactorRailsShim
|
|
|
469
469
|
# main; workers reload translations from disk via these paths.
|
|
470
470
|
def load_path
|
|
471
471
|
v = ActiveSupport::IsolatedExecutionState[:ractor_rails_shim_i18n_load_path]
|
|
472
|
-
return v
|
|
472
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(:ractor_rails_shim_i18n_load_path)
|
|
473
473
|
if Ractor.main?
|
|
474
474
|
lp = (defined?(@@load_path) && @@load_path) || []
|
|
475
475
|
lp = lp.dup.freeze if lp.respond_to?(:freeze) && !lp.frozen?
|
|
@@ -493,7 +493,7 @@ module RactorRailsShim
|
|
|
493
493
|
# DEFAULT_INTERPOLATION_PATTERNS constant).
|
|
494
494
|
def default_separator
|
|
495
495
|
v = ActiveSupport::IsolatedExecutionState[:ractor_rails_shim_i18n_sep]
|
|
496
|
-
return v
|
|
496
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(:ractor_rails_shim_i18n_sep)
|
|
497
497
|
if Ractor.main?
|
|
498
498
|
cv = defined?(@@default_separator) ? @@default_separator : "."
|
|
499
499
|
ActiveSupport::IsolatedExecutionState[:ractor_rails_shim_i18n_sep] = cv
|
|
@@ -509,7 +509,7 @@ module RactorRailsShim
|
|
|
509
509
|
end
|
|
510
510
|
def exception_handler
|
|
511
511
|
v = ActiveSupport::IsolatedExecutionState[:ractor_rails_shim_i18n_exc]
|
|
512
|
-
return v
|
|
512
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(:ractor_rails_shim_i18n_exc)
|
|
513
513
|
if Ractor.main?
|
|
514
514
|
cv = defined?(@@exception_handler) ? @@exception_handler : ::I18n::ExceptionHandler.new
|
|
515
515
|
ActiveSupport::IsolatedExecutionState[:ractor_rails_shim_i18n_exc] = cv
|
|
@@ -525,7 +525,7 @@ module RactorRailsShim
|
|
|
525
525
|
end
|
|
526
526
|
def missing_interpolation_argument_handler
|
|
527
527
|
v = ActiveSupport::IsolatedExecutionState[:ractor_rails_shim_i18n_miss]
|
|
528
|
-
return v
|
|
528
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(:ractor_rails_shim_i18n_miss)
|
|
529
529
|
if Ractor.main?
|
|
530
530
|
cv = defined?(@@missing_interpolation_argument_handler) ? @@missing_interpolation_argument_handler : lambda do |missing_key, provided_hash, string|
|
|
531
531
|
raise ::I18n::MissingInterpolationArgument.new(missing_key, provided_hash, string)
|
|
@@ -545,7 +545,7 @@ module RactorRailsShim
|
|
|
545
545
|
end
|
|
546
546
|
def interpolation_patterns
|
|
547
547
|
v = ActiveSupport::IsolatedExecutionState[:ractor_rails_shim_i18n_ip]
|
|
548
|
-
return v
|
|
548
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(:ractor_rails_shim_i18n_ip)
|
|
549
549
|
if Ractor.main?
|
|
550
550
|
cv = defined?(@@interpolation_patterns) ? @@interpolation_patterns : ::I18n::DEFAULT_INTERPOLATION_PATTERNS.dup
|
|
551
551
|
ActiveSupport::IsolatedExecutionState[:ractor_rails_shim_i18n_ip] = cv
|
|
@@ -599,7 +599,7 @@ module RactorRailsShim
|
|
|
599
599
|
i18n.singleton_class.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
600
600
|
def fallbacks
|
|
601
601
|
v = ActiveSupport::IsolatedExecutionState[#{fb_key_str}]
|
|
602
|
-
return v
|
|
602
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{fb_key_str})
|
|
603
603
|
if Ractor.main? && defined?(@@fallbacks)
|
|
604
604
|
cv = @@fallbacks
|
|
605
605
|
if cv
|
|
@@ -622,7 +622,7 @@ module RactorRailsShim
|
|
|
622
622
|
tag.singleton_class.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
623
623
|
def implementation
|
|
624
624
|
v = ActiveSupport::IsolatedExecutionState[#{tag_key_str}]
|
|
625
|
-
return v
|
|
625
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{tag_key_str})
|
|
626
626
|
if Ractor.main? && defined?(@@implementation)
|
|
627
627
|
cv = @@implementation
|
|
628
628
|
ActiveSupport::IsolatedExecutionState[#{tag_key_str}] = cv
|
|
@@ -781,7 +781,7 @@ module RactorRailsShim
|
|
|
781
781
|
ec.singleton_class.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
782
782
|
def after_change_callbacks
|
|
783
783
|
v = ActiveSupport::IsolatedExecutionState[#{acb_key_str}]
|
|
784
|
-
return v
|
|
784
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{acb_key_str})
|
|
785
785
|
if Ractor.main? && instance_variable_defined?(:@after_change_callbacks)
|
|
786
786
|
v = @after_change_callbacks
|
|
787
787
|
ActiveSupport::IsolatedExecutionState[#{acb_key_str}] = v
|
|
@@ -797,7 +797,7 @@ module RactorRailsShim
|
|
|
797
797
|
end
|
|
798
798
|
def nestable
|
|
799
799
|
v = ActiveSupport::IsolatedExecutionState[#{nest_key_str}]
|
|
800
|
-
return v
|
|
800
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{nest_key_str})
|
|
801
801
|
if Ractor.main? && instance_variable_defined?(:@nestable)
|
|
802
802
|
v = @nestable
|
|
803
803
|
ActiveSupport::IsolatedExecutionState[#{nest_key_str}] = v
|
|
@@ -855,7 +855,7 @@ module RactorRailsShim
|
|
|
855
855
|
ls.singleton_class.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
856
856
|
def logger
|
|
857
857
|
v = ActiveSupport::IsolatedExecutionState[#{key_str}]
|
|
858
|
-
return v
|
|
858
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{key_str})
|
|
859
859
|
if Ractor.main? && instance_variable_defined?(:@logger)
|
|
860
860
|
@logger
|
|
861
861
|
elsif defined?(::Rails) && ::Rails.respond_to?(:logger)
|
|
@@ -890,7 +890,7 @@ module RactorRailsShim
|
|
|
890
890
|
rl.singleton_class.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
891
891
|
def check!
|
|
892
892
|
v = ActiveSupport::IsolatedExecutionState[#{key_str}]
|
|
893
|
-
return v
|
|
893
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{key_str})
|
|
894
894
|
result = check.call
|
|
895
895
|
ActiveSupport::IsolatedExecutionState[#{key_str}] = result
|
|
896
896
|
result
|
|
@@ -590,6 +590,18 @@ module RactorRailsShim
|
|
|
590
590
|
# which writes `@table_name`/`@arel_table`/etc. From a worker that write is
|
|
591
591
|
# Ractor::IsolationError. Route the value through IsolatedExecutionState
|
|
592
592
|
# (keyed by model object_id); main keeps the original class-ivar behavior.
|
|
593
|
+
#
|
|
594
|
+
# NOTE on naming: there are TWO ModelSchema-related install methods, with
|
|
595
|
+
# deliberately different scopes:
|
|
596
|
+
# * `_install_active_record_model_schema_patch` (this method) — patches
|
|
597
|
+
# `table_name` / `table_name=` / `reset_table_name` only.
|
|
598
|
+
# * `_install_activerecord_model_schema_patch` (below) — patches the
|
|
599
|
+
# *column-derived* lazy caches `symbol_column_to_string`,
|
|
600
|
+
# `content_columns` (and previously `column_defaults`, now provided
|
|
601
|
+
# by the prepended `ActiveRecordModelSchemaPatch`).
|
|
602
|
+
# Both target `ActiveRecord::ModelSchema::ClassMethods` but patch
|
|
603
|
+
# disjoint method sets; do not collapse them without auditing every
|
|
604
|
+
# caller.
|
|
593
605
|
def _install_active_record_model_schema_patch
|
|
594
606
|
return if @ar_model_schema_patched
|
|
595
607
|
@ar_model_schema_patched = true
|
|
@@ -776,15 +788,79 @@ module RactorRailsShim
|
|
|
776
788
|
cols
|
|
777
789
|
end
|
|
778
790
|
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
791
|
+
# NOTE: `column_defaults` is intentionally NOT redefined here. It is
|
|
792
|
+
# installed by the prepended `ActiveRecordModelSchemaPatch` (see
|
|
793
|
+
# `active_record_model_schema.rb`), which is prepended onto
|
|
794
|
+
# `ModelSchema::ClassMethods` by `_install_active_model_attribute_patch`
|
|
795
|
+
# and therefore sits in front of this module_eval heredoc in the
|
|
796
|
+
# method lookup chain. A second definition here would be dead code and
|
|
797
|
+
# a maintenance trap.
|
|
798
|
+
|
|
799
|
+
# `reload_schema_from_cache` (called by `load_schema`/`reload_schema!`)
|
|
800
|
+
# writes a batch of class ivars to `nil` to invalidate schema caches:
|
|
801
|
+
# @_returning_columns_for_insert, @arel_table, @column_names,
|
|
802
|
+
# @symbol_column_to_string_name_hash, @content_columns, @column_defaults
|
|
803
|
+
# (ModelSchema) plus @timestamp_attributes_for_create_in_model,
|
|
804
|
+
# @timestamp_attributes_for_update_in_model,
|
|
805
|
+
# @all_timestamp_attributes_in_model (Timestamp#reload_schema_from_cache
|
|
806
|
+
# via super). In a worker Ractor those writes raise
|
|
807
|
+
# Ractor::IsolationError ("can not set instance variables of
|
|
808
|
+
# classes/modules by non-main Ractors"). The shim already routes the
|
|
809
|
+
# *readers* for these caches through IES, so in a worker we only need
|
|
810
|
+
# to clear the IES slots — the next read rebuilds lazily. In main we
|
|
811
|
+
# keep the original class-ivar-clearing behavior.
|
|
812
|
+
def reload_schema_from_cache(recursive = true)
|
|
813
|
+
if Ractor.main?
|
|
814
|
+
super
|
|
815
|
+
else
|
|
816
|
+
# Clear this Ractor's IES slots for the IES-routed schema caches.
|
|
817
|
+
# Use `next` over an explicit list (not `IES.clear`) to avoid
|
|
818
|
+
# wiping unrelated slots. Keys mirror the readers above + the
|
|
819
|
+
# prepended ActiveRecordModelSchemaPatch (active_record_model_schema.rb)
|
|
820
|
+
# + ActiveModelAttributeRegistrationPatch (active_model_attribute.rb).
|
|
821
|
+
name_str = self.name
|
|
822
|
+
[
|
|
823
|
+
:"ractor_rails_shim_symbol_column_to_string_\#{name_str}",
|
|
824
|
+
:"ractor_rails_shim_content_columns_\#{name_str}",
|
|
825
|
+
:"rrs_column_defaults_\#{object_id}",
|
|
826
|
+
:"rrs_attributes_builder_\#{object_id}",
|
|
827
|
+
:"rrs_yaml_encoder_\#{object_id}",
|
|
828
|
+
:"rrs_returning_cols_\#{object_id}",
|
|
829
|
+
:"rrs_default_attributes_\#{object_id}",
|
|
830
|
+
:"rrs_attribute_types_\#{object_id}",
|
|
831
|
+
:"rrs_table_names",
|
|
832
|
+
:"rrs_arel_tables",
|
|
833
|
+
:"rrs_predicate_builders",
|
|
834
|
+
:"rrs_type_casters",
|
|
835
|
+
].each do |k| ActiveSupport::IsolatedExecutionState.delete(k) end
|
|
836
|
+
# The Timestamp subclass override calls `super` (this method); it
|
|
837
|
+
# has already run by the time we get here via the super chain, so
|
|
838
|
+
# its ivar-clears were intercepted by the worker branch of THIS
|
|
839
|
+
# override (no-op). No further action needed.
|
|
840
|
+
end
|
|
786
841
|
end
|
|
787
842
|
RUBY
|
|
843
|
+
|
|
844
|
+
# Patch ActiveRecord::Timestamp::ClassMethods#reload_schema_from_cache
|
|
845
|
+
# the same way: in main, keep the original ivar-clearing; in a worker,
|
|
846
|
+
# skip the class-ivar writes (the IES slots are cleared by the
|
|
847
|
+
# ModelSchema#reload_schema_from_cache override above via super).
|
|
848
|
+
if defined?(::ActiveRecord::Timestamp::ClassMethods)
|
|
849
|
+
::ActiveRecord::Timestamp::ClassMethods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
850
|
+
def reload_schema_from_cache(recursive = true)
|
|
851
|
+
if Ractor.main?
|
|
852
|
+
@timestamp_attributes_for_create_in_model = nil
|
|
853
|
+
@timestamp_attributes_for_update_in_model = nil
|
|
854
|
+
@all_timestamp_attributes_in_model = nil
|
|
855
|
+
super
|
|
856
|
+
else
|
|
857
|
+
# Worker: skip the class-ivar writes (would raise
|
|
858
|
+
# IsolationError). The ModelSchema super clears the IES slots.
|
|
859
|
+
super
|
|
860
|
+
end
|
|
861
|
+
end
|
|
862
|
+
RUBY
|
|
863
|
+
end
|
|
788
864
|
end
|
|
789
865
|
|
|
790
866
|
# Patch ActiveModel::Conversion::ClassMethods#_to_partial_path to route its
|
|
@@ -863,7 +939,7 @@ module RactorRailsShim
|
|
|
863
939
|
::ActiveRecord::Base.singleton_class.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
864
940
|
def configurations
|
|
865
941
|
v = ActiveSupport::IsolatedExecutionState[#{key_str}]
|
|
866
|
-
return v
|
|
942
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{key_str})
|
|
867
943
|
if Ractor.main?
|
|
868
944
|
ActiveRecord::Core.class_variable_get(:@@configurations)
|
|
869
945
|
else
|
|
@@ -919,7 +995,7 @@ module RactorRailsShim
|
|
|
919
995
|
::ActiveRecord::DatabaseConfigurations.singleton_class.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
920
996
|
def db_config_handlers
|
|
921
997
|
v = ActiveSupport::IsolatedExecutionState[#{key_str}]
|
|
922
|
-
return v
|
|
998
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{key_str})
|
|
923
999
|
if Ractor.main?
|
|
924
1000
|
@db_config_handlers
|
|
925
1001
|
else
|
|
@@ -973,7 +1049,7 @@ module RactorRailsShim
|
|
|
973
1049
|
::ActiveRecord.singleton_class.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
974
1050
|
def query_transformers
|
|
975
1051
|
v = ActiveSupport::IsolatedExecutionState[#{key_str}]
|
|
976
|
-
return v
|
|
1052
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{key_str})
|
|
977
1053
|
if Ractor.main?
|
|
978
1054
|
@query_transformers
|
|
979
1055
|
else
|
|
@@ -1026,7 +1102,7 @@ module RactorRailsShim
|
|
|
1026
1102
|
::ActiveRecord.singleton_class.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
1027
1103
|
def #{method_name}
|
|
1028
1104
|
v = ActiveSupport::IsolatedExecutionState[#{key_str}]
|
|
1029
|
-
return v
|
|
1105
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{key_str})
|
|
1030
1106
|
if Ractor.main?
|
|
1031
1107
|
@#{method_name}
|
|
1032
1108
|
else
|
|
@@ -1058,7 +1134,7 @@ module RactorRailsShim
|
|
|
1058
1134
|
def registry
|
|
1059
1135
|
key = :"ractor_rails_shim_dedup_registry_\#{name || object_id}"
|
|
1060
1136
|
v = ActiveSupport::IsolatedExecutionState[key]
|
|
1061
|
-
return v
|
|
1137
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(key)
|
|
1062
1138
|
h = {}
|
|
1063
1139
|
ActiveSupport::IsolatedExecutionState[key] = h
|
|
1064
1140
|
h
|
|
@@ -1081,7 +1157,7 @@ module RactorRailsShim
|
|
|
1081
1157
|
def query_constraints_list
|
|
1082
1158
|
key = :"ractor_rails_shim_qcl_\#{name || object_id}"
|
|
1083
1159
|
v = ActiveSupport::IsolatedExecutionState[key]
|
|
1084
|
-
return v
|
|
1160
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(key)
|
|
1085
1161
|
result = if base_class? || primary_key != base_class.primary_key
|
|
1086
1162
|
primary_key if primary_key.is_a?(::Array)
|
|
1087
1163
|
else
|
|
@@ -1093,7 +1169,7 @@ module RactorRailsShim
|
|
|
1093
1169
|
def has_query_constraints?
|
|
1094
1170
|
key = :"ractor_rails_shim_qcl_\#{name || object_id}"
|
|
1095
1171
|
v = ActiveSupport::IsolatedExecutionState[key]
|
|
1096
|
-
return !v.nil?
|
|
1172
|
+
return !v.nil? if ActiveSupport::IsolatedExecutionState.key?(key)
|
|
1097
1173
|
result = query_constraints_list
|
|
1098
1174
|
!result.nil?
|
|
1099
1175
|
end
|
|
@@ -1336,7 +1412,7 @@ module RactorRailsShim
|
|
|
1336
1412
|
::ActiveRecord::Base.singleton_class.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
1337
1413
|
def default_connection_handler
|
|
1338
1414
|
v = ActiveSupport::IsolatedExecutionState[#{dch_key_str}]
|
|
1339
|
-
return v
|
|
1415
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{dch_key_str})
|
|
1340
1416
|
if Ractor.main?
|
|
1341
1417
|
cv = RactorRailsShim::CLASS_ATTR_VALUES[:__ractor_rails_shim_ar_default_connection_handler__]
|
|
1342
1418
|
return cv if cv
|
|
@@ -1351,6 +1427,12 @@ module RactorRailsShim
|
|
|
1351
1427
|
# worker's other threads; Ractor.current is per-Ractor and shared by all
|
|
1352
1428
|
# threads of the worker. Falls back to IES (legacy) then
|
|
1353
1429
|
# default_connection_handler (main Ractor only).
|
|
1430
|
+
# NOTE: uses nil-sentinel (`unless v.nil?`) rather than key?-based
|
|
1431
|
+
# detection because Ractor#[] has no `key?` method (Ractor local
|
|
1432
|
+
# storage returns nil for both unset and set-to-nil). Safe here
|
|
1433
|
+
# because `connection_handler=` is only ever called with a real
|
|
1434
|
+
# ConnectionHandler instance, never nil — so nil unambiguously
|
|
1435
|
+
# means "unset, fall through to default_connection_handler".
|
|
1354
1436
|
def connection_handler
|
|
1355
1437
|
v = Ractor.current[:active_record_connection_handler]
|
|
1356
1438
|
return v unless v.nil?
|
|
@@ -1428,7 +1510,7 @@ module RactorRailsShim
|
|
|
1428
1510
|
def serialize_cast_value_compatible?
|
|
1429
1511
|
key = :"ractor_rails_shim_scv_\#{name || object_id}"
|
|
1430
1512
|
v = ActiveSupport::IsolatedExecutionState[key]
|
|
1431
|
-
return v
|
|
1513
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(key)
|
|
1432
1514
|
result = ancestors.index(instance_method(:serialize_cast_value).owner) <= ancestors.index(instance_method(:serialize).owner)
|
|
1433
1515
|
ActiveSupport::IsolatedExecutionState[key] = result
|
|
1434
1516
|
result
|
|
@@ -1461,7 +1543,7 @@ module RactorRailsShim
|
|
|
1461
1543
|
def uncacheable_methods
|
|
1462
1544
|
key = :ractor_rails_shim_ar_uncacheable_methods
|
|
1463
1545
|
v = ActiveSupport::IsolatedExecutionState[key]
|
|
1464
|
-
return v
|
|
1546
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(key)
|
|
1465
1547
|
result = (
|
|
1466
1548
|
delegated_classes.flat_map(&:public_instance_methods) - ActiveRecord::Relation.public_instance_methods
|
|
1467
1549
|
).to_set.freeze
|
|
@@ -1497,7 +1579,7 @@ module RactorRailsShim
|
|
|
1497
1579
|
def primary_key
|
|
1498
1580
|
key = :"ractor_rails_shim_pk_\#{name || object_id}"
|
|
1499
1581
|
v = ActiveSupport::IsolatedExecutionState[key]
|
|
1500
|
-
return v
|
|
1582
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(key)
|
|
1501
1583
|
if Ractor.main?
|
|
1502
1584
|
reset_primary_key if PRIMARY_KEY_NOT_SET.equal?(@primary_key)
|
|
1503
1585
|
v = @primary_key
|
|
@@ -1510,7 +1592,7 @@ module RactorRailsShim
|
|
|
1510
1592
|
def composite_primary_key?
|
|
1511
1593
|
key = :"ractor_rails_shim_pk_\#{name || object_id}"
|
|
1512
1594
|
v = ActiveSupport::IsolatedExecutionState[key]
|
|
1513
|
-
return v.is_a?(::Array)
|
|
1595
|
+
return v.is_a?(::Array) if ActiveSupport::IsolatedExecutionState.key?(key)
|
|
1514
1596
|
if Ractor.main?
|
|
1515
1597
|
reset_primary_key if PRIMARY_KEY_NOT_SET.equal?(@primary_key)
|
|
1516
1598
|
@primary_key.is_a?(::Array)
|
|
@@ -1700,7 +1782,7 @@ module RactorRailsShim
|
|
|
1700
1782
|
mig.singleton_class.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
1701
1783
|
def migrations_paths
|
|
1702
1784
|
v = ActiveSupport::IsolatedExecutionState[#{mp_key_str}]
|
|
1703
|
-
return v
|
|
1785
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{mp_key_str})
|
|
1704
1786
|
if Ractor.main? && instance_variable_defined?(:@migrations_paths)
|
|
1705
1787
|
v = @migrations_paths
|
|
1706
1788
|
ActiveSupport::IsolatedExecutionState[#{mp_key_str}] = v
|
|
@@ -102,11 +102,24 @@ module RactorRailsShim
|
|
|
102
102
|
# for copy-on-write fallback. This restores per-subclass isolation
|
|
103
103
|
# (lost by the IES-routed variant) without thread-local IES, which
|
|
104
104
|
# is empty on Puma's request threads.
|
|
105
|
+
#
|
|
106
|
+
# Key format MUST stay `:"ractor_rails_shim_class_attr_<oid>_<name>"`
|
|
107
|
+
# to match the writer below and the Ractor-mode key in
|
|
108
|
+
# `redefine`'s own `key` local. The original walked ancestors and
|
|
109
|
+
# interpolated the *whole* Symbol per ancestor on every read —
|
|
110
|
+
# allocating a fresh Array (ancestors) AND a fresh Symbol per
|
|
111
|
+
# ancestor, the dominant allocation source for GET requests. We
|
|
112
|
+
# interpolate only the per-ancestor `object_id` tail inside the
|
|
113
|
+
# lookup, which Ruby optimizes to a single Symbol allocation per
|
|
114
|
+
# ancestor (vs. the previous full-string interpolation). The
|
|
115
|
+
# Ractor-mode branch below avoids the walk entirely (literal key),
|
|
116
|
+
# but thread mode must walk because subclass COW fallback is
|
|
117
|
+
# load-bearing here.
|
|
105
118
|
target.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
106
119
|
def #{namespaced_name}
|
|
107
120
|
self.ancestors.each do |anc|
|
|
108
121
|
v = RactorRailsShim::CLASS_ATTR_VALUES[:"ractor_rails_shim_class_attr_\#{anc.object_id}_#{namespaced_name}"]
|
|
109
|
-
return v
|
|
122
|
+
return v if RactorRailsShim::CLASS_ATTR_VALUES.key?(:"ractor_rails_shim_class_attr_\#{anc.object_id}_#{namespaced_name}")
|
|
110
123
|
end
|
|
111
124
|
#{namespaced_name.inspect} == :__callbacks ? {} : nil
|
|
112
125
|
end
|
|
@@ -124,7 +137,7 @@ module RactorRailsShim
|
|
|
124
137
|
def #{name}
|
|
125
138
|
self.ancestors.each do |anc|
|
|
126
139
|
v = RactorRailsShim::CLASS_ATTR_VALUES[:"ractor_rails_shim_class_attr_\#{anc.object_id}_#{namespaced_name}"]
|
|
127
|
-
return v
|
|
140
|
+
return v if RactorRailsShim::CLASS_ATTR_VALUES.key?(:"ractor_rails_shim_class_attr_\#{anc.object_id}_#{namespaced_name}")
|
|
128
141
|
end
|
|
129
142
|
#{namespaced_name.inspect} == :__callbacks ? {} : nil
|
|
130
143
|
end
|
|
@@ -135,50 +148,53 @@ module RactorRailsShim
|
|
|
135
148
|
end
|
|
136
149
|
RUBY
|
|
137
150
|
end
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
151
|
+
else
|
|
152
|
+
# Ractor mode: the writer (below) ALWAYS targets this owner's single
|
|
153
|
+
# `key` (#{key_str}), so there is no per-subclass copy-on-write to
|
|
154
|
+
# resolve by walking ancestors. The original `self.ancestors.each`
|
|
155
|
+
# + per-ancestor Symbol interpolation allocated a fresh Array AND a
|
|
156
|
+
# Symbol per ancestor on EVERY read — the dominant allocation source
|
|
157
|
+
# for GET requests. Replace it with a direct literal-key lookup
|
|
158
|
+
# (zero per-read allocation). The per-Ractor value already lives in
|
|
159
|
+
# ActiveSupport::IsolatedExecutionState[key]; SHAREABLE_FALLBACK[key]
|
|
160
|
+
# is the frozen, shared default built at prepare_for_ractors! time.
|
|
161
|
+
target.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
162
|
+
def #{namespaced_name}
|
|
163
|
+
v = ActiveSupport::IsolatedExecutionState[#{key_str}]
|
|
164
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{key_str})
|
|
165
|
+
fb = RactorRailsShim::SHAREABLE_FALLBACK[#{key_str}]
|
|
166
|
+
return fb unless fb.nil?
|
|
167
|
+
RactorRailsShim::CLASS_ATTR_VALUES[#{key_str}] if Ractor.main?
|
|
168
|
+
end
|
|
150
169
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
170
|
+
def #{namespaced_name}=(new_value)
|
|
171
|
+
ActiveSupport::IsolatedExecutionState[#{key_str}] = new_value
|
|
172
|
+
RactorRailsShim::CLASS_ATTR_VALUES[#{key_str}] = new_value if Ractor.main?
|
|
173
|
+
new_value
|
|
174
|
+
end
|
|
175
|
+
RUBY
|
|
157
176
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
end
|
|
171
|
-
RactorRailsShim::CLASS_ATTR_VALUES[#{key_str}] if Ractor.main?
|
|
172
|
-
end
|
|
177
|
+
# When owner is a module's singleton class, the original also
|
|
178
|
+
# defines a public reader `def #{name} { value }` on owner directly
|
|
179
|
+
# (block-based). Override it with the IES-routed version + fallback.
|
|
180
|
+
if owner.singleton_class? && owner.attached_object.is_a?(Module)
|
|
181
|
+
owner.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
182
|
+
def #{name}
|
|
183
|
+
v = ActiveSupport::IsolatedExecutionState[#{key_str}]
|
|
184
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{key_str})
|
|
185
|
+
fb = RactorRailsShim::SHAREABLE_FALLBACK[#{key_str}]
|
|
186
|
+
return fb unless fb.nil?
|
|
187
|
+
RactorRailsShim::CLASS_ATTR_VALUES[#{key_str}] if Ractor.main?
|
|
188
|
+
end
|
|
173
189
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
190
|
+
def #{name}=(new_value)
|
|
191
|
+
ActiveSupport::IsolatedExecutionState[#{key_str}] = new_value
|
|
192
|
+
RactorRailsShim::CLASS_ATTR_VALUES[#{key_str}] = new_value if Ractor.main?
|
|
193
|
+
new_value
|
|
194
|
+
end
|
|
195
|
+
RUBY
|
|
196
|
+
end
|
|
197
|
+
end
|
|
182
198
|
end
|
|
183
199
|
|
|
184
200
|
# redefine_method is used by `redefine` internally and by other call
|
|
@@ -617,7 +617,7 @@ module RactorRailsShim
|
|
|
617
617
|
# be shareable.
|
|
618
618
|
klass.instance_variables.each do |ivar|
|
|
619
619
|
val = klass.instance_variable_get(ivar)
|
|
620
|
-
next if
|
|
620
|
+
next if Ractor.shareable?(val)
|
|
621
621
|
begin
|
|
622
622
|
Ractor.make_shareable(val)
|
|
623
623
|
rescue
|
|
@@ -638,7 +638,7 @@ module RactorRailsShim
|
|
|
638
638
|
classes.each do |klass|
|
|
639
639
|
klass.instance_variables.each do |ivar|
|
|
640
640
|
val = klass.instance_variable_get(ivar)
|
|
641
|
-
next if
|
|
641
|
+
next if Ractor.shareable?(val)
|
|
642
642
|
begin
|
|
643
643
|
Ractor.make_shareable(val)
|
|
644
644
|
rescue
|
|
@@ -91,7 +91,7 @@ module RactorRailsShim
|
|
|
91
91
|
def devise_parameter_filter
|
|
92
92
|
key = :"ractor_rails_shim_devise_param_filter_\#{object_id}"
|
|
93
93
|
v = ActiveSupport::IsolatedExecutionState[key]
|
|
94
|
-
return v
|
|
94
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(key)
|
|
95
95
|
f = Devise::ParameterFilter.new(case_insensitive_keys, strip_whitespace_keys)
|
|
96
96
|
ActiveSupport::IsolatedExecutionState[key] = f
|
|
97
97
|
f
|
|
@@ -42,7 +42,7 @@ module RactorRailsShim
|
|
|
42
42
|
ew.singleton_class.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
43
43
|
def active_key
|
|
44
44
|
v = ActiveSupport::IsolatedExecutionState[#{key_str}]
|
|
45
|
-
return v
|
|
45
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{key_str})
|
|
46
46
|
sym = :"active_execution_wrapper_\#{object_id}"
|
|
47
47
|
ActiveSupport::IsolatedExecutionState[#{key_str}] = sym
|
|
48
48
|
sym
|
|
@@ -62,7 +62,7 @@ module RactorRailsShim
|
|
|
62
62
|
::Kaminari.singleton_class.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
63
63
|
def config
|
|
64
64
|
v = ActiveSupport::IsolatedExecutionState[#{k_key_str}]
|
|
65
|
-
return v
|
|
65
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{k_key_str})
|
|
66
66
|
if Ractor.main? && instance_variable_defined?(:@_config)
|
|
67
67
|
@_config
|
|
68
68
|
else
|
|
@@ -349,8 +349,16 @@ module RactorRailsShim
|
|
|
349
349
|
# calls `#to_proc` and then requires the result to be a real Proc.
|
|
350
350
|
# Return a frozen no-op lambda so the implicit conversion succeeds and
|
|
351
351
|
# the (side-effect-free) call is a true no-op, matching `#call`.
|
|
352
|
+
#
|
|
353
|
+
# The lambda is a shareable constant (frozen at class load), NOT
|
|
354
|
+
# memoized on `@_to_proc`: NoOpProc instances are deep-frozen by
|
|
355
|
+
# `Ractor.make_shareable` during `make_app_shareable!`, so an
|
|
356
|
+
# `@_to_proc ||= ...` write would raise FrozenError on the frozen
|
|
357
|
+
# instance. A constant avoids the write entirely and is safe to share.
|
|
358
|
+
NO_OP_LAMBDA = ->(*) { nil }.freeze
|
|
359
|
+
Ractor.make_shareable(NO_OP_LAMBDA)
|
|
352
360
|
def to_proc
|
|
353
|
-
|
|
361
|
+
NO_OP_LAMBDA
|
|
354
362
|
end
|
|
355
363
|
end
|
|
356
364
|
class Callable
|
|
@@ -81,7 +81,7 @@ module RactorRailsShim
|
|
|
81
81
|
singleton_class.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
82
82
|
def #{sym}
|
|
83
83
|
v = ActiveSupport::IsolatedExecutionState[#{key_str}]
|
|
84
|
-
return v
|
|
84
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{key_str})
|
|
85
85
|
|
|
86
86
|
if #{!!shareable}
|
|
87
87
|
if class_variable_defined?(#{cv_str})
|
|
@@ -128,7 +128,7 @@ module RactorRailsShim
|
|
|
128
128
|
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
129
129
|
def #{sym}
|
|
130
130
|
v = ActiveSupport::IsolatedExecutionState[#{key_str}]
|
|
131
|
-
return v
|
|
131
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{key_str})
|
|
132
132
|
if Ractor.main?
|
|
133
133
|
self.class.class_variable_defined?(#{cv_str}) ? self.class.class_variable_get(#{cv_str}) : nil
|
|
134
134
|
else
|
|
@@ -21,7 +21,7 @@ module RactorRailsShim
|
|
|
21
21
|
def to_adapter
|
|
22
22
|
key = :"ractor_rails_shim_orm_adapter_\#{object_id}"
|
|
23
23
|
v = ActiveSupport::IsolatedExecutionState[key]
|
|
24
|
-
return v
|
|
24
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(key)
|
|
25
25
|
adapter = self::OrmAdapter.new(self)
|
|
26
26
|
ActiveSupport::IsolatedExecutionState[key] = adapter
|
|
27
27
|
adapter
|
|
@@ -56,7 +56,7 @@ module RactorRailsShim
|
|
|
56
56
|
req.singleton_class.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
57
57
|
def forwarded_priority
|
|
58
58
|
v = ActiveSupport::IsolatedExecutionState[#{fp_key_str}]
|
|
59
|
-
return v
|
|
59
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{fp_key_str})
|
|
60
60
|
if Ractor.main? && instance_variable_defined?(:@forwarded_priority)
|
|
61
61
|
@forwarded_priority
|
|
62
62
|
else
|
|
@@ -70,7 +70,7 @@ module RactorRailsShim
|
|
|
70
70
|
end
|
|
71
71
|
def x_forwarded_proto_priority
|
|
72
72
|
v = ActiveSupport::IsolatedExecutionState[#{xp_key_str}]
|
|
73
|
-
return v
|
|
73
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{xp_key_str})
|
|
74
74
|
if Ractor.main? && instance_variable_defined?(:@x_forwarded_proto_priority)
|
|
75
75
|
@x_forwarded_proto_priority
|
|
76
76
|
else
|
|
@@ -105,7 +105,7 @@ module RactorRailsShim
|
|
|
105
105
|
u.singleton_class.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
106
106
|
def default_query_parser
|
|
107
107
|
v = ActiveSupport::IsolatedExecutionState[#{dqp_key_str}]
|
|
108
|
-
return v
|
|
108
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{dqp_key_str})
|
|
109
109
|
if Ractor.main? && instance_variable_defined?(:@default_query_parser)
|
|
110
110
|
v = @default_query_parser
|
|
111
111
|
ActiveSupport::IsolatedExecutionState[#{dqp_key_str}] = v
|
|
@@ -116,7 +116,7 @@ module RactorRailsShim
|
|
|
116
116
|
end
|
|
117
117
|
def multipart_total_part_limit
|
|
118
118
|
v = ActiveSupport::IsolatedExecutionState[#{mtp_key_str}]
|
|
119
|
-
return v
|
|
119
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{mtp_key_str})
|
|
120
120
|
if Ractor.main? && instance_variable_defined?(:@multipart_total_part_limit)
|
|
121
121
|
v = @multipart_total_part_limit
|
|
122
122
|
ActiveSupport::IsolatedExecutionState[#{mtp_key_str}] = v
|
|
@@ -127,7 +127,7 @@ module RactorRailsShim
|
|
|
127
127
|
end
|
|
128
128
|
def multipart_file_limit
|
|
129
129
|
v = ActiveSupport::IsolatedExecutionState[#{mfl_key_str}]
|
|
130
|
-
return v
|
|
130
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{mfl_key_str})
|
|
131
131
|
if Ractor.main? && instance_variable_defined?(:@multipart_file_limit)
|
|
132
132
|
v = @multipart_file_limit
|
|
133
133
|
ActiveSupport::IsolatedExecutionState[#{mfl_key_str}] = v
|
|
@@ -36,6 +36,13 @@ module RactorRailsShim
|
|
|
36
36
|
# :class fires when `rails.rb` opens `module Rails` (module bodies fire
|
|
37
37
|
# as :class); once the constant is assigned, we patch once and disable
|
|
38
38
|
# the hook.
|
|
39
|
+
#
|
|
40
|
+
# Two flags are intentional and guard *different* things:
|
|
41
|
+
# @rails_load_hook_installed — the TracePoint one-shot is armed (so we
|
|
42
|
+
# don't stack multiple TracePoints on repeated `install` calls).
|
|
43
|
+
# @rails_module_patched — the patch itself has been applied (checked
|
|
44
|
+
# again inside the TracePoint block and in `patch_rails_module!`
|
|
45
|
+
# because the immediate-path caller also goes through that method).
|
|
39
46
|
def install_rails_load_hook
|
|
40
47
|
return if @rails_load_hook_installed
|
|
41
48
|
@rails_load_hook_installed = true
|
|
@@ -89,7 +96,7 @@ module RactorRailsShim
|
|
|
89
96
|
# slot is empty until they boot their own app).
|
|
90
97
|
def application
|
|
91
98
|
v = ActiveSupport::IsolatedExecutionState[#{k[:application].inspect}]
|
|
92
|
-
return v
|
|
99
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{k[:application].inspect})
|
|
93
100
|
if Ractor.main?
|
|
94
101
|
super
|
|
95
102
|
else
|
|
@@ -108,7 +115,7 @@ module RactorRailsShim
|
|
|
108
115
|
# value at make_app_shareable! time) when their own IES slot is empty.
|
|
109
116
|
def app_class
|
|
110
117
|
v = ActiveSupport::IsolatedExecutionState[#{k[:app_class].inspect}]
|
|
111
|
-
return v
|
|
118
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{k[:app_class].inspect})
|
|
112
119
|
return super if Ractor.main?
|
|
113
120
|
RactorRailsShim::SHAREABLE_FALLBACK[#{k[:app_class].inspect}]
|
|
114
121
|
end
|
|
@@ -120,7 +127,7 @@ module RactorRailsShim
|
|
|
120
127
|
|
|
121
128
|
def cache
|
|
122
129
|
v = ActiveSupport::IsolatedExecutionState[#{k[:cache].inspect}]
|
|
123
|
-
return v
|
|
130
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{k[:cache].inspect})
|
|
124
131
|
return super if Ractor.main?
|
|
125
132
|
RactorRailsShim::SHAREABLE_FALLBACK[#{k[:cache].inspect}]
|
|
126
133
|
end
|
|
@@ -132,7 +139,7 @@ module RactorRailsShim
|
|
|
132
139
|
|
|
133
140
|
def logger
|
|
134
141
|
v = ActiveSupport::IsolatedExecutionState[#{k[:logger].inspect}]
|
|
135
|
-
return v
|
|
142
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{k[:logger].inspect})
|
|
136
143
|
return super if Ractor.main?
|
|
137
144
|
# Loggers are intrinsically mutable (formatters hold tag stacks,
|
|
138
145
|
# logdevs hold IO + Mutex) and can't be shared read-only. Build a
|
|
@@ -152,7 +159,7 @@ module RactorRailsShim
|
|
|
152
159
|
|
|
153
160
|
def backtrace_cleaner
|
|
154
161
|
v = ActiveSupport::IsolatedExecutionState[#{k[:backtrace_cleaner].inspect}]
|
|
155
|
-
return v
|
|
162
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{k[:backtrace_cleaner].inspect})
|
|
156
163
|
return super if Ractor.main?
|
|
157
164
|
RactorRailsShim::SHAREABLE_FALLBACK[#{k[:backtrace_cleaner].inspect}]
|
|
158
165
|
end
|
|
@@ -167,7 +174,7 @@ module RactorRailsShim
|
|
|
167
174
|
# lazily builds and caches in @_env.
|
|
168
175
|
def env
|
|
169
176
|
v = ActiveSupport::IsolatedExecutionState[#{k[:env].inspect}]
|
|
170
|
-
return v
|
|
177
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{k[:env].inspect})
|
|
171
178
|
if Ractor.main?
|
|
172
179
|
super
|
|
173
180
|
else
|
|
@@ -93,6 +93,27 @@ module RactorRailsShim
|
|
|
93
93
|
mod = _rrs_orig_generate_url_helpers(supports_path)
|
|
94
94
|
mod.module_eval("def _routes\n @_routes || ::Rails.application.routes\nend", __FILE__, __LINE__ + 1)
|
|
95
95
|
mod.module_eval("def _generate_paths_by_default\n " + supports_path.inspect + "\nend", __FILE__, __LINE__ + 1)
|
|
96
|
+
|
|
97
|
+
# Bound the stock `self.included` reinclude (actionpack
|
|
98
|
+
# action_dispatch/routing/route_set.rb). That hook re-dups the module
|
|
99
|
+
# and re-includes it while `!base._routes.equal?(@_proxy._routes)`.
|
|
100
|
+
# Under the frozen, Ractor-shareable app graph a worker Ractor's
|
|
101
|
+
# controller can report `_routes` as nil, so the equality never holds
|
|
102
|
+
# and the reinclude loops forever (SystemStackError on the very first
|
|
103
|
+
# request, e.g. GET /up -> Rails::HealthController). The intent is to
|
|
104
|
+
# re-align `_routes` per base exactly once; bound it to one reinclude
|
|
105
|
+
# per base so the loop can never happen while preserving that intent.
|
|
106
|
+
seen = ::Set.new
|
|
107
|
+
mod.instance_variable_set(:@_rrs_reinclude_seen, seen)
|
|
108
|
+
mod.singleton_class.class_eval do
|
|
109
|
+
alias_method :_rrs_orig_self_included, :included
|
|
110
|
+
def included(base)
|
|
111
|
+
seen = instance_variable_get(:@_rrs_reinclude_seen)
|
|
112
|
+
return if seen.include?(base.object_id)
|
|
113
|
+
seen << base.object_id
|
|
114
|
+
_rrs_orig_self_included(base)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
96
117
|
mod
|
|
97
118
|
end
|
|
98
119
|
RUBY
|
|
@@ -23,6 +23,13 @@ module RactorRailsShim
|
|
|
23
23
|
else
|
|
24
24
|
# Defer until Zeitwerk loads. A TracePoint(:class) fires when
|
|
25
25
|
# `module Registry` opens. One-shot.
|
|
26
|
+
#
|
|
27
|
+
# Two flags guard different things (mirrors `rails_module.rb`):
|
|
28
|
+
# @zeitwerk_patched — `install_zeitwerk_registry` ran
|
|
29
|
+
# (so we don't arm a second TracePoint).
|
|
30
|
+
# @zeitwerk_registry_patched — the actual patch was applied (checked
|
|
31
|
+
# again inside the TracePoint block and
|
|
32
|
+
# in `patch_zeitwerk_registry!`).
|
|
26
33
|
@zw_tp = TracePoint.new(:class) do |trace|
|
|
27
34
|
if defined?(::Zeitwerk::Registry) && !@zeitwerk_registry_patched
|
|
28
35
|
@zw_tp.disable
|
|
@@ -69,7 +76,7 @@ module RactorRailsShim
|
|
|
69
76
|
reader_patch.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
70
77
|
def #{ivar}
|
|
71
78
|
v = ActiveSupport::IsolatedExecutionState[#{key_str}]
|
|
72
|
-
return v
|
|
79
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{key_str})
|
|
73
80
|
if Ractor.main?
|
|
74
81
|
existing = instance_variable_get(#{ivar_str}) if instance_variable_defined?(#{ivar_str})
|
|
75
82
|
if existing
|