Ruby's thread local variables are badly designed compared to other languages, even Java or Python.
This gem fixes that.
In Ruby there's a global namespace of thread local names that are accessed like that:
Thread.current.thread_variable_get(:some_global_name)
This has two implications:
- If e.g. Rails uses the
:time_zone
thread variable you can't. Obviously. - If an object uses a thread variable it has to make sure it cleans up after itself when it gets garbage collected.
In Java or Python instead of a global namespace where you assign keys to values you just create a new instance of a
ThreadLocal
(or threading.local
) object, like this:
class MyThingy
def initialize
@some_setting = ThreadLocal.new(false)
end
def do_something
if @some_setting.get == true
# Do something only if it's enabled
end
end
def enable_setting
@some_setting.set(true)
end
end
a = MyThingy.new
b = MyThingy.new
a
and b
have different ivars which means their thread-local setting will never clash. And when one of them gets
garbage collected the thread local variable is cleaned up across all threads (not that important for a boolean but really
important in the general case.) Success!
Obviously this works just as well
class MyThingy
@settings = ThreadLocal.new({})
def self.settings
@settings.get
end
end
MyThingy.settings[:whatever] # this is per thread now
The threadlocal
gem works and is tested on Ruby 2.0 and 2.1, JRuby can use Java's built-in ThreadLocal
(making this
transparent is on the roadmap.)
There's also ThreadLocalDelegator
which is mostly useful when supplied with a default value / default proc.
class MyThingy
@settings = ThreadLocalDelegator.new({}) # will do {}.dup for every new thread since that's the default value
def self.enable_something
@settings[:something] = true
end
end
Thanks to
- Konstantin Haase for finally getting me to write this. Also see this gist.