aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--activerecord/test/cases/base_test.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/thread.rb70
-rw-r--r--activesupport/test/core_ext/thread_test.rb77
3 files changed, 148 insertions, 1 deletions
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index c5c500279e..d326ed7863 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -1444,7 +1444,7 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal "developers/#{dev.id}-#{dev.updated_at.utc.to_s(:nsec)}", dev.cache_key
end
- def test_cache_key_format_for_existing_record_with_updated_at_and_cache_timestamp_format_set
+ def test_cache_key_format_for_existing_record_with_updated_at_and_custom_cache_timestamp_format
dev = CachedDeveloper.first
assert_equal "cached_developers/#{dev.id}-#{dev.updated_at.utc.to_s(:number)}", dev.cache_key
end
diff --git a/activesupport/lib/active_support/core_ext/thread.rb b/activesupport/lib/active_support/core_ext/thread.rb
new file mode 100644
index 0000000000..6ad0b2d69c
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/thread.rb
@@ -0,0 +1,70 @@
+class Thread
+ LOCK = Mutex.new # :nodoc:
+
+ # Returns the value of a thread local variable that has been set. Note that
+ # these are different than fiber local values.
+ #
+ # Thread local values are carried along with threads, and do not respect
+ # fibers. For example:
+ #
+ # Thread.new {
+ # Thread.current.thread_variable_set("foo", "bar") # set a thread local
+ # Thread.current["foo"] = "bar" # set a fiber local
+ #
+ # Fiber.new {
+ # Fiber.yield [
+ # Thread.current.thread_variable_get("foo"), # get the thread local
+ # Thread.current["foo"], # get the fiber local
+ # ]
+ # }.resume
+ # }.join.value # => ['bar', nil]
+ #
+ # The value <tt>"bar"</tt> is returned for the thread local, where +nil+ is returned
+ # for the fiber local. The fiber is executed in the same thread, so the
+ # thread local values are available.
+ def thread_variable_get(key)
+ locals[key.to_sym]
+ end
+
+ # Sets a thread local with +key+ to +value+. Note that these are local to
+ # threads, and not to fibers. Please see Thread#thread_variable_get for
+ # more information.
+ def thread_variable_set(key, value)
+ locals[key.to_sym] = value
+ end
+
+ # Returns an an array of the names of the thread-local variables (as Symbols).
+ #
+ # thr = Thread.new do
+ # Thread.current.thread_variable_set(:cat, 'meow')
+ # Thread.current.thread_variable_set("dog", 'woof')
+ # end
+ # thr.join #=> #<Thread:0x401b3f10 dead>
+ # thr.thread_variables #=> [:dog, :cat]
+ #
+ # Note that these are not fiber local variables. Please see Thread#thread_variable_get
+ # for more details.
+ def thread_variables
+ locals.keys
+ end
+
+ # Returns <tt>true</tt> if the given string (or symbol) exists as a
+ # thread-local variable.
+ #
+ # me = Thread.current
+ # me.thread_variable_set(:oliver, "a")
+ # me.thread_variable?(:oliver) #=> true
+ # me.thread_variable?(:stanley) #=> false
+ #
+ # Note that these are not fiber local variables. Please see Thread#thread_variable_get
+ # for more details.
+ def thread_variable?(key)
+ locals.has_key?(key.to_sym)
+ end
+
+ private
+
+ def locals
+ @locals || LOCK.synchronize { @locals ||= {} }
+ end
+end unless Thread.instance_methods.include?(:thread_variable_set)
diff --git a/activesupport/test/core_ext/thread_test.rb b/activesupport/test/core_ext/thread_test.rb
new file mode 100644
index 0000000000..b58f59a8d4
--- /dev/null
+++ b/activesupport/test/core_ext/thread_test.rb
@@ -0,0 +1,77 @@
+require 'abstract_unit'
+require 'active_support/core_ext/thread'
+
+class ThreadExt < ActiveSupport::TestCase
+ def test_main_thread_variable_in_enumerator
+ assert_equal Thread.main, Thread.current
+
+ Thread.current.thread_variable_set :foo, "bar"
+
+ thread, value = Fiber.new {
+ Fiber.yield [Thread.current, Thread.current.thread_variable_get(:foo)]
+ }.resume
+
+ assert_equal Thread.current, thread
+ assert_equal Thread.current.thread_variable_get(:foo), value
+ end
+
+ def test_thread_variable_in_enumerator
+ Thread.new {
+ Thread.current.thread_variable_set :foo, "bar"
+
+ thread, value = Fiber.new {
+ Fiber.yield [Thread.current, Thread.current.thread_variable_get(:foo)]
+ }.resume
+
+ assert_equal Thread.current, thread
+ assert_equal Thread.current.thread_variable_get(:foo), value
+ }.join
+ end
+
+ def test_thread_variables
+ assert_equal [], Thread.new { Thread.current.thread_variables }.join.value
+
+ t = Thread.new {
+ Thread.current.thread_variable_set(:foo, "bar")
+ Thread.current.thread_variables
+ }
+ assert_equal [:foo], t.join.value
+ end
+
+ def test_thread_variable?
+ refute Thread.new { Thread.current.thread_variable?("foo") }.join.value
+ t = Thread.new {
+ Thread.current.thread_variable_set("foo", "bar")
+ }.join
+
+ assert t.thread_variable?("foo")
+ assert t.thread_variable?(:foo)
+ refute t.thread_variable?(:bar)
+ end
+
+ def test_thread_variable_strings_and_symbols_are_the_same_key
+ t = Thread.new {}.join
+ t.thread_variable_set("foo", "bar")
+ assert_equal "bar", t.thread_variable_get(:foo)
+ end
+
+ def test_thread_variable_frozen
+ t = Thread.new { }.join
+ t.freeze
+ assert_raises(RuntimeError) do
+ t.thread_variable_set(:foo, "bar")
+ end
+ end
+
+ def test_thread_variable_security
+ t = Thread.new { sleep }
+
+ assert_raises(SecurityError) do
+ Thread.new { $SAFE = 4; t.thread_variable_get(:foo) }.join
+ end
+
+ assert_raises(SecurityError) do
+ Thread.new { $SAFE = 4; t.thread_variable_set(:foo, :baz) }.join
+ end
+ end
+end