Skip to content

Commit 1b186f7

Browse files
committed
inherit the isolation level from Rails by default
1 parent 5dd21fa commit 1b186f7

File tree

4 files changed

+116
-15
lines changed

4 files changed

+116
-15
lines changed

lib/config/locales/en.yml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -716,12 +716,13 @@ en:
716716
message: "The isolation level '%{level}' is not supported."
717717
summary: >
718718
You requested an isolation level of '%{level}', which is not
719-
supported. Only `:thread` and `:fiber` isolation levels are
720-
currently supported; note that the `:fiber` level is only
721-
supported on Ruby versions 3.2 and higher.
719+
supported. Only `:rails`, `:thread` and `:fiber` isolation
720+
levels are currently supported; note that the `:fiber` level is
721+
only supported on Ruby versions 3.2 and higher.
722722
resolution: >
723723
Use `:thread` as the isolation level. If you are using Ruby 3.2
724-
or higher, you may also use `:fiber`.
724+
or higher, you may also use `:fiber`. If using Rails 7+, you
725+
may also use `:rails` to inherit the isolation level from Rails.
725726
validations:
726727
message: "Validation of %{document} failed."
727728
summary: "The following errors were found: %{errors}"

lib/mongoid/config.rb

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -110,13 +110,16 @@ module Config
110110
# to `:global_thread_pool`.
111111
option :global_executor_concurrency, default: nil
112112

113+
VALID_ISOLATION_LEVELS = %i[ rails thread fiber ].freeze
114+
113115
# Defines the isolation level that Mongoid uses to store its internal
114116
# state.
115117
#
116-
# This option may be set to either `:thread` (the default) or `:fiber`.
117-
#
118-
# If set to `:thread`, Mongoid will use thread-local storage to manage
119-
# its internal state.
118+
# Valid values are:
119+
# - `:rails` - Uses the isolation level that Rails currently has
120+
# configured. (This is the default.)
121+
# - `:thread` - Uses thread-local storage.
122+
# - `:fiber` - Uses fiber-local storage (only supported in Ruby 3.2+).
120123
#
121124
# If set to `:fiber`, Mongoid will use fiber-local storage instead. This
122125
# may be necessary if you are using libraries like Falcon, which use
@@ -125,8 +128,46 @@ module Config
125128
# Note that the `:fiber` isolation level is only supported in Ruby 3.2
126129
# and later, due to semantic differences in how fiber storage is handled
127130
# in earlier Ruby versions.
128-
option :isolation_level, default: :thread, on_change: -> (level) do
129-
if %i[ thread fiber ].exclude?(level)
131+
option :isolation_level, default: :rails, on_change: -> (level) do
132+
validate_isolation_level!(level)
133+
end
134+
135+
# Returns the (potentially-dereferenced) isolation level that Mongoid
136+
# will use to store its internal state. If `isolation_level` is set to
137+
# `:rails`, this will return the isolation level that Rails is current
138+
# configured to use (`ActiveSupport::IsolatedExecutionState.isolation_level`).
139+
#
140+
# If using an older version of Rails that does not support
141+
# ActiveSupport::IsolatedExecutionState, this will return `:thread`
142+
# instead.
143+
#
144+
# @api private
145+
def real_isolation_level
146+
return isolation_level unless isolation_level == :rails
147+
148+
if defined?(ActiveSupport::IsolatedExecutionState)
149+
ActiveSupport::IsolatedExecutionState.isolation_level.tap do |level|
150+
# We can't guarantee that Rails will always support the same
151+
# isolation levels as Mongoid, so we check here to make sure
152+
# it's something we can work with.
153+
validate_isolation_level!(level)
154+
end
155+
else
156+
# The default, if Rails does not support IsolatedExecutionState,
157+
:thread
158+
end
159+
end
160+
161+
# Checks to see if the provided isolation level is something that Mongoid
162+
# supports. Raises Errors::UnsupportedIsolationLevel if it is not.
163+
#
164+
# This will also raise an error if the isolation level is set to `:fiber`
165+
# and the Ruby version is less than 3.2, since fiber-local storage
166+
# is not supported in earlier Ruby versions.
167+
#
168+
# @api private
169+
def validate_isolation_level!(level)
170+
unless VALID_ISOLATION_LEVELS.include?(level)
130171
raise Errors::UnsupportedIsolationLevel.new(level)
131172
end
132173

lib/mongoid/threaded.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,16 +42,16 @@ module Threaded
4242
# This is useful for making sure the state is clean when starting a new
4343
# thread or fiber.
4444
#
45-
# The value of Mongoid::Config.isolation_level is used to determine
45+
# The value of Mongoid::Config.real_isolation_level is used to determine
4646
# whether to reset the storage for the current thread or fiber.
4747
def reset!
48-
case Config.isolation_level
48+
case Config.real_isolation_level
4949
when :thread
5050
Thread.current.thread_variable_set(STORAGE_KEY, nil)
5151
when :fiber
5252
Fiber[STORAGE_KEY] = nil
5353
else
54-
raise "Unknown isolation level: #{Config.isolation_level.inspect}"
54+
raise "Unknown isolation level: #{Config.real_isolation_level.inspect}"
5555
end
5656
end
5757

@@ -517,7 +517,7 @@ def unset_current_scope(klass)
517517

518518
# Returns the current thread- or fiber-local storage as a Hash.
519519
def storage
520-
case Config.isolation_level
520+
case Config.real_isolation_level
521521
when :thread
522522
storage_hash = Thread.current.thread_variable_get(STORAGE_KEY)
523523

@@ -532,7 +532,7 @@ def storage
532532
Fiber[STORAGE_KEY] ||= {}
533533

534534
else
535-
raise "Unknown isolation level: #{Config.isolation_level.inspect}"
535+
raise "Unknown isolation level: #{Config.real_isolation_level.inspect}"
536536
end
537537
end
538538
end

spec/integration/isolation_state_spec.rb

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,65 @@ def fiber_operation(value)
5555
end
5656
end
5757

58+
context 'when set to :rails' do
59+
config_override :isolation_level, :rails
60+
61+
def self.with_rails_isolation_level(level)
62+
puts "Rails version: #{ActiveSupport.version}"
63+
around do |example|
64+
saved, ActiveSupport::IsolatedExecutionState.isolation_level =
65+
ActiveSupport::IsolatedExecutionState.isolation_level, level
66+
example.run
67+
ensure
68+
ActiveSupport::IsolatedExecutionState.isolation_level = saved
69+
end
70+
end
71+
72+
context 'when using Rails < 7' do
73+
max_rails_version '6.99'
74+
75+
it 'returns :thread' do
76+
expect(Mongoid::Config.isolation_level).to eq(:rails)
77+
expect(Mongoid::Config.real_isolation_level).to eq(:thread)
78+
end
79+
end
80+
81+
context 'when using Rails >= 7' do
82+
min_rails_version '7.0'
83+
84+
context 'when IsolatedExecutionState.isolation_level is set to :thread' do
85+
with_rails_isolation_level :thread
86+
87+
it 'returns :thread' do
88+
expect(Mongoid::Config.isolation_level).to eq(:rails)
89+
expect(Mongoid::Config.real_isolation_level).to eq(:thread)
90+
end
91+
end
92+
93+
context 'when IsolatedExecutionState.isolation_level is set to :fiber' do
94+
with_rails_isolation_level :fiber
95+
96+
context 'when Ruby version is >= 3.2' do
97+
ruby_version_gte '3.2'
98+
99+
it 'returns :fiber' do
100+
expect(Mongoid::Config.isolation_level).to eq(:rails)
101+
expect(Mongoid::Config.real_isolation_level).to eq(:fiber)
102+
end
103+
end
104+
105+
context 'when Ruby version is < 3.2' do
106+
ruby_version_lt '3.2'
107+
108+
it 'raises an error' do
109+
expect(Mongoid::Config.isolation_level).to eq(:rails)
110+
expect { Mongoid::Config.real_isolation_level }.to raise_error(Mongoid::Errors::UnsupportedIsolationLevel)
111+
end
112+
end
113+
end
114+
end
115+
end
116+
58117
context 'when set to :thread' do
59118
config_override :isolation_level, :thread
60119

0 commit comments

Comments
 (0)