diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb index 2075d79bd1..34961f22e1 100644 --- a/activesupport/lib/active_support/core_ext/date/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date/calculations.rb @@ -17,7 +17,7 @@ class Date # If Date.beginning_of_week has not been set for the current request, returns the week start specified in config.beginning_of_week. # If no config.beginning_of_week was specified, returns :monday. def beginning_of_week - Thread.current[:beginning_of_week] || beginning_of_week_default || :monday + ::ActiveSupport::IsolatedExecutionState[:beginning_of_week] || beginning_of_week_default || :monday end # Sets Date.beginning_of_week to a week start (e.g. :monday) for current request/thread. @@ -25,7 +25,7 @@ class Date # This method accepts any of the following day symbols: # :monday, :tuesday, :wednesday, :thursday, :friday, :saturday, :sunday def beginning_of_week=(week_start) - Thread.current[:beginning_of_week] = find_beginning_of_week!(week_start) + ::ActiveSupport::IsolatedExecutionState[:beginning_of_week] = find_beginning_of_week!(week_start) end # Returns week start day symbol (e.g. :monday), or raises an +ArgumentError+ for invalid day symbol. diff --git a/activesupport/lib/active_support/core_ext/time/zones.rb b/activesupport/lib/active_support/core_ext/time/zones.rb index 4443405a85..76d8639063 100644 --- a/activesupport/lib/active_support/core_ext/time/zones.rb +++ b/activesupport/lib/active_support/core_ext/time/zones.rb @@ -12,7 +12,7 @@ class Time # Returns the TimeZone for the current request, if this has been set (via Time.zone=). # If Time.zone has not been set for the current request, returns the TimeZone specified in config.time_zone. def zone - Thread.current[:time_zone] || zone_default + ::ActiveSupport::IsolatedExecutionState[:time_zone] || zone_default end # Sets Time.zone to a TimeZone object for the current request/thread. @@ -39,7 +39,7 @@ class Time # end # end def zone=(time_zone) - Thread.current[:time_zone] = find_zone!(time_zone) + ::ActiveSupport::IsolatedExecutionState[:time_zone] = find_zone!(time_zone) end # Allows override of Time.zone locally inside supplied block; diff --git a/activesupport/lib/active_support/execution_context.rb b/activesupport/lib/active_support/execution_context.rb index 01d7742e9a..1c95188bae 100644 --- a/activesupport/lib/active_support/execution_context.rb +++ b/activesupport/lib/active_support/execution_context.rb @@ -46,7 +46,7 @@ module ActiveSupport private def store - Thread.current[:active_support_execution_context] ||= {} + IsolatedExecutionState[:active_support_execution_context] ||= {} end end end diff --git a/activesupport/lib/active_support/execution_wrapper.rb b/activesupport/lib/active_support/execution_wrapper.rb index 36e80b23c7..0c11087d26 100644 --- a/activesupport/lib/active_support/execution_wrapper.rb +++ b/activesupport/lib/active_support/execution_wrapper.rb @@ -113,11 +113,11 @@ module ActiveSupport self.active = Concurrent::Hash.new def self.active? # :nodoc: - @active[Thread.current] + @active[IsolatedExecutionState.unique_id] end def run! # :nodoc: - self.class.active[Thread.current] = true + self.class.active[IsolatedExecutionState.unique_id] = true run end @@ -132,7 +132,7 @@ module ActiveSupport def complete! complete ensure - self.class.active.delete Thread.current + self.class.active.delete(IsolatedExecutionState.unique_id) end def complete # :nodoc: diff --git a/activesupport/lib/active_support/isolated_execution_state.rb b/activesupport/lib/active_support/isolated_execution_state.rb index 5d972a4acd..7e14b9b4f6 100644 --- a/activesupport/lib/active_support/isolated_execution_state.rb +++ b/activesupport/lib/active_support/isolated_execution_state.rb @@ -25,6 +25,10 @@ module ActiveSupport end end + def unique_id + self[:__id__] ||= Object.new + end + def [](key) current[key] end diff --git a/activesupport/lib/active_support/logger_thread_safe_level.rb b/activesupport/lib/active_support/logger_thread_safe_level.rb index 4f860939e7..042f484f82 100644 --- a/activesupport/lib/active_support/logger_thread_safe_level.rb +++ b/activesupport/lib/active_support/logger_thread_safe_level.rb @@ -18,8 +18,7 @@ module ActiveSupport end def local_level - # Note: Thread#[] is fiber-local - Thread.current[:logger_thread_safe_level] + IsolatedExecutionState[:logger_thread_safe_level] end def local_level=(level) @@ -31,7 +30,7 @@ module ActiveSupport else raise ArgumentError, "Invalid log level: #{level.inspect}" end - Thread.current[:logger_thread_safe_level] = level + IsolatedExecutionState[:logger_thread_safe_level] = level end def level diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb index 4935a8ffcf..0759d3a086 100644 --- a/activesupport/lib/active_support/notifications/fanout.rb +++ b/activesupport/lib/active_support/notifications/fanout.rb @@ -226,12 +226,12 @@ module ActiveSupport end def start(name, id, payload) - timestack = Thread.current[:_timestack] ||= [] + timestack = IsolatedExecutionState[:_timestack] ||= [] timestack.push Time.now end def finish(name, id, payload) - timestack = Thread.current[:_timestack] + timestack = IsolatedExecutionState[:_timestack] started = timestack.pop @delegate.call(name, started, Time.now, id, payload) end @@ -243,12 +243,12 @@ module ActiveSupport end def start(name, id, payload) - timestack = Thread.current[:_timestack_monotonic] ||= [] + timestack = IsolatedExecutionState[:_timestack_monotonic] ||= [] timestack.push Process.clock_gettime(Process::CLOCK_MONOTONIC) end def finish(name, id, payload) - timestack = Thread.current[:_timestack_monotonic] + timestack = IsolatedExecutionState[:_timestack_monotonic] started = timestack.pop @delegate.call(name, started, Process.clock_gettime(Process::CLOCK_MONOTONIC), id, payload) end @@ -256,14 +256,14 @@ module ActiveSupport class EventObject < Evented def start(name, id, payload) - stack = Thread.current[:_event_stack] ||= [] + stack = IsolatedExecutionState[:_event_stack] ||= [] event = build_event name, id, payload event.start! stack.push event end def finish(name, id, payload) - stack = Thread.current[:_event_stack] + stack = IsolatedExecutionState[:_event_stack] event = stack.pop event.payload = payload event.finish! diff --git a/activesupport/lib/active_support/tagged_logging.rb b/activesupport/lib/active_support/tagged_logging.rb index 0bc4a21401..26852e590a 100644 --- a/activesupport/lib/active_support/tagged_logging.rb +++ b/activesupport/lib/active_support/tagged_logging.rb @@ -57,7 +57,7 @@ module ActiveSupport def current_tags # We use our object ID here to avoid conflicting with other instances thread_key = @thread_key ||= "activesupport_tagged_logging_tags:#{object_id}" - Thread.current[thread_key] ||= [] + IsolatedExecutionState[thread_key] ||= [] end def tags_text diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb index 1930c417fd..e2b52daa65 100644 --- a/activesupport/lib/active_support/xml_mini.rb +++ b/activesupport/lib/active_support/xml_mini.rb @@ -181,11 +181,11 @@ module ActiveSupport end def current_thread_backend - Thread.current[:xml_mini_backend] + IsolatedExecutionState[:xml_mini_backend] end def current_thread_backend=(name) - Thread.current[:xml_mini_backend] = name && cast_backend_name_to_module(name) + IsolatedExecutionState[:xml_mini_backend] = name && cast_backend_name_to_module(name) end def cast_backend_name_to_module(name) diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb index cb28444bba..c328234a39 100644 --- a/activesupport/test/core_ext/time_with_zone_test.rb +++ b/activesupport/test/core_ext/time_with_zone_test.rb @@ -1205,13 +1205,11 @@ class TimeWithZoneMethodsForTimeAndDateTimeTest < ActiveSupport::TestCase def test_time_zone_setter_is_thread_safe Time.use_zone "Paris" do - t1 = Thread.new { Time.zone = "Alaska" }.join - t2 = Thread.new { Time.zone = "Hawaii" }.join - assert t1.stop?, "Thread 1 did not finish running" - assert t2.stop?, "Thread 2 did not finish running" + t1 = Thread.new { Time.zone = "Alaska"; Time.zone } + t2 = Thread.new { Time.zone = "Hawaii"; Time.zone } assert_equal ActiveSupport::TimeZone["Paris"], Time.zone - assert_equal ActiveSupport::TimeZone["Alaska"], t1[:time_zone] - assert_equal ActiveSupport::TimeZone["Hawaii"], t2[:time_zone] + assert_equal ActiveSupport::TimeZone["Alaska"], t1.value + assert_equal ActiveSupport::TimeZone["Hawaii"], t2.value end end diff --git a/activesupport/test/logger_test.rb b/activesupport/test/logger_test.rb index 23e77ad270..2461b48bc6 100644 --- a/activesupport/test/logger_test.rb +++ b/activesupport/test/logger_test.rb @@ -272,6 +272,9 @@ class LoggerTest < ActiveSupport::TestCase end def test_logger_level_main_fiber_safety + previous_isolation_level = ActiveSupport::IsolatedExecutionState.isolation_level + ActiveSupport::IsolatedExecutionState.isolation_level = :fiber + @logger.level = Logger::INFO assert_level(Logger::INFO) @@ -283,9 +286,14 @@ class LoggerTest < ActiveSupport::TestCase assert_level(Logger::ERROR) fiber.resume end + ensure + ActiveSupport::IsolatedExecutionState.isolation_level = previous_isolation_level end def test_logger_level_local_fiber_safety + previous_isolation_level = ActiveSupport::IsolatedExecutionState.isolation_level + ActiveSupport::IsolatedExecutionState.isolation_level = :fiber + @logger.level = Logger::INFO assert_level(Logger::INFO) @@ -313,6 +321,25 @@ class LoggerTest < ActiveSupport::TestCase end.resume assert_level(Logger::INFO) + ensure + ActiveSupport::IsolatedExecutionState.isolation_level = previous_isolation_level + end + + def test_logger_level_thread_safety + previous_isolation_level = ActiveSupport::IsolatedExecutionState.isolation_level + ActiveSupport::IsolatedExecutionState.isolation_level = :thread + + @logger.level = Logger::INFO + assert_level(Logger::INFO) + + enumerator = Enumerator.new do |yielder| + @logger.level = Logger::DEBUG + yielder.yield @logger.level + end + assert_equal Logger::DEBUG, enumerator.next + assert_level(Logger::DEBUG) + ensure + ActiveSupport::IsolatedExecutionState.isolation_level = previous_isolation_level end def test_temporarily_logging_at_a_noisier_level