lib/concurrent-ruby/concurrent/atomic/thread_local_var.rb
require 'concurrent/constants'
require_relative 'locals'
module Concurrent
# A `ThreadLocalVar` is a variable where the value is different for each thread.
# Each variable may have a default value, but when you modify the variable only
# the current thread will ever see that change.
#
# This is similar to Ruby's built-in thread-local variables (`Thread#thread_variable_get`),
# but with these major advantages:
# * `ThreadLocalVar` has its own identity, it doesn't need a Symbol.
# * Each Ruby's built-in thread-local variable leaks some memory forever (it's a Symbol held forever on the thread),
# so it's only OK to create a small amount of them.
# `ThreadLocalVar` has no such issue and it is fine to create many of them.
# * Ruby's built-in thread-local variables leak forever the value set on each thread (unless set to nil explicitly).
# `ThreadLocalVar` automatically removes the mapping for each thread once the `ThreadLocalVar` instance is GC'd.
#
# @!macro thread_safe_variable_comparison
#
# @example
# v = ThreadLocalVar.new(14)
# v.value #=> 14
# v.value = 2
# v.value #=> 2
#
# @example
# v = ThreadLocalVar.new(14)
#
# t1 = Thread.new do
# v.value #=> 14
# v.value = 1
# v.value #=> 1
# end
#
# t2 = Thread.new do
# v.value #=> 14
# v.value = 2
# v.value #=> 2
# end
#
# v.value #=> 14
class ThreadLocalVar
LOCALS = ThreadLocals.new
# Creates a thread local variable.
#
# @param [Object] default the default value when otherwise unset
# @param [Proc] default_block Optional block that gets called to obtain the
# default value for each thread
def initialize(default = nil, &default_block)
if default && block_given?
raise ArgumentError, "Cannot use both value and block as default value"
end
if block_given?
@default_block = default_block
@default = nil
else
@default_block = nil
@default = default
end
@index = LOCALS.next_index(self)
end
# Returns the value in the current thread's copy of this thread-local variable.
#
# @return [Object] the current value
def value
LOCALS.fetch(@index) { default }
end
# Sets the current thread's copy of this thread-local variable to the specified value.
#
# @param [Object] value the value to set
# @return [Object] the new value
def value=(value)
LOCALS.set(@index, value)
end
# Bind the given value to thread local storage during
# execution of the given block.
#
# @param [Object] value the value to bind
# @yield the operation to be performed with the bound variable
# @return [Object] the value
def bind(value)
if block_given?
old_value = self.value
self.value = value
begin
yield
ensure
self.value = old_value
end
end
end
protected
# @!visibility private
def default
if @default_block
self.value = @default_block.call
else
@default
end
end
end
end