Skip to content

Commit 2fbe5d7

Browse files
authored
Merge pull request rails#43711 from Shopify/thread-accessor-isolated-state
Use IsolatedExecutionState in `thread_mattr_accessor`
2 parents b9098f6 + 2bf5974 commit 2fbe5d7

File tree

2 files changed

+31
-5
lines changed

2 files changed

+31
-5
lines changed

activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
#
77
# So the values are scoped within the Thread.current space under the class name
88
# of the module.
9+
#
10+
# Note that it can also be scoped per-fiber if Rails.application.config.active_support.isolation_level
11+
# is set to `:fiber`
912
class Module
1013
# Defines a per-thread class attribute and creates class and instance reader methods.
1114
# The underlying per-thread class variable is set to +nil+, if it is not previously defined.
@@ -14,9 +17,9 @@ class Module
1417
# thread_mattr_reader :user
1518
# end
1619
#
17-
# Current.user # => nil
18-
# Thread.current[:attr_Current_user] = "DHH"
20+
# Current.user = "DHH"
1921
# Current.user # => "DHH"
22+
# Thread.new { Current.user }.values # => nil
2023
#
2124
# The attribute name must be a valid method name in Ruby.
2225
#
@@ -41,7 +44,8 @@ def thread_mattr_reader(*syms, instance_reader: true, instance_accessor: true, d
4144
# to work with inheritance via polymorphism.
4245
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
4346
def self.#{sym}
44-
Thread.current["attr_" + name + "_#{sym}"]
47+
@__thread_mattr_#{sym} ||= "attr_\#{name}_#{sym}"
48+
::ActiveSupport::IsolatedExecutionState[@__thread_mattr_#{sym}]
4549
end
4650
EOS
4751

@@ -53,7 +57,7 @@ def #{sym}
5357
EOS
5458
end
5559

56-
Thread.current["attr_" + name + "_#{sym}"] = default unless default.nil?
60+
::ActiveSupport::IsolatedExecutionState["attr_#{name}_#{sym}"] = default unless default.nil?
5761
end
5862
end
5963
alias :thread_cattr_reader :thread_mattr_reader
@@ -84,7 +88,8 @@ def thread_mattr_writer(*syms, instance_writer: true, instance_accessor: true, d
8488
# to work with inheritance via polymorphism.
8589
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
8690
def self.#{sym}=(obj)
87-
Thread.current["attr_" + name + "_#{sym}"] = obj
91+
@__thread_mattr_#{sym} ||= "attr_\#{name}_#{sym}"
92+
::ActiveSupport::IsolatedExecutionState[@__thread_mattr_#{sym}] = obj
8893
end
8994
EOS
9095

activesupport/test/core_ext/module/attribute_accessor_per_thread_test.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,27 @@ class SubMyClass < MyClass
2020
@object = @class.new
2121
end
2222

23+
def test_is_shared_between_fibers
24+
@class.foo = 42
25+
enumerator = Enumerator.new do |yielder|
26+
yielder.yield @class.foo
27+
end
28+
assert_equal 42, enumerator.next
29+
end
30+
31+
def test_is_not_shared_between_fibers_if_isolation_level_is_fiber
32+
previous_level = ActiveSupport::IsolatedExecutionState.isolation_level
33+
ActiveSupport::IsolatedExecutionState.isolation_level = :fiber
34+
35+
@class.foo = 42
36+
enumerator = Enumerator.new do |yielder|
37+
yielder.yield @class.foo
38+
end
39+
assert_nil enumerator.next
40+
ensure
41+
ActiveSupport::IsolatedExecutionState.isolation_level = previous_level
42+
end
43+
2344
def test_can_initialize_with_default_value
2445
Thread.new do
2546
@class.thread_mattr_accessor :baz, default: "default_value"

0 commit comments

Comments
 (0)