class Concurrent::RubyThreadLocalVar

@!visibility private @!macro internal_implementation_note

Constants

ARRAYS
FREE

@!visibility private

LOCK

Public Class Methods

new(default = nil) click to toggle source

@!macro [attach] thread_local_var_method_initialize

Creates a thread local variable.

@param [Object] default the default value when otherwise unset
# File lib/concurrent/atomic/ruby_thread_local_var.rb, line 43
def initialize(default = nil)
  @default = default
  allocate_storage
end

Protected Class Methods

thread_finalizer(array) click to toggle source

@!visibility private

# File lib/concurrent/atomic/ruby_thread_local_var.rb, line 122
def self.thread_finalizer(array)
  proc do
    LOCK.synchronize do
      # The thread which used this thread-local array is now gone
      # So don't hold onto a reference to the array (thus blocking GC)
      ARRAYS.delete(array.object_id)
    end
  end
end
threadlocal_finalizer(index) click to toggle source

@!visibility private

# File lib/concurrent/atomic/ruby_thread_local_var.rb, line 107
def self.threadlocal_finalizer(index)
  proc do
    LOCK.synchronize do
      FREE.push(index)
      # The cost of GC'ing a TLV is linear in the number of threads using TLVs
      # But that is natural! More threads means more storage is used per TLV
      # So naturally more CPU time is required to free more storage
      ARRAYS.each_value do |array|
        array[index] = nil
      end
    end
  end
end

Public Instance Methods

bind(value) { || ... } click to toggle source

@!macro thread_local_var_method_bind

# File lib/concurrent/atomic/ruby_thread_local_var.rb, line 80
def bind(value, &block)
  if block_given?
    old_value = self.value
    begin
      self.value = value
      yield
    ensure
      self.value = old_value
    end
  end
end
value() click to toggle source

@!macro thread_local_var_method_get

# File lib/concurrent/atomic/ruby_thread_local_var.rb, line 49
def value
  if array = get_threadlocal_array
    value = array[@index]
    if value.nil?
      @default
    elsif value.equal?(NULL)
      nil
    else
      value
    end
  else
    @default
  end
end
value=(value) click to toggle source

@!macro thread_local_var_method_set

# File lib/concurrent/atomic/ruby_thread_local_var.rb, line 65
def value=(value)
  me = Thread.current
  # We could keep the thread-local arrays in a hash, keyed by Thread
  # But why? That would require locking
  # Using Ruby's built-in thread-local storage is faster
  unless array = get_threadlocal_array(me)
    array = set_threadlocal_array([], me)
    LOCK.synchronize { ARRAYS[array.object_id] = array }
    ObjectSpace.define_finalizer(me, self.class.thread_finalizer(array))
  end
  array[@index] = (value.nil? ? NULL : value)
  value
end

Protected Instance Methods

allocate_storage() click to toggle source

@!visibility private

# File lib/concurrent/atomic/ruby_thread_local_var.rb, line 95
def allocate_storage
  @index = LOCK.synchronize do
    FREE.pop || begin
    result = @@next
    @@next += 1
    result
    end
  end
  ObjectSpace.define_finalizer(self, self.class.threadlocal_finalizer(@index))
end

Private Instance Methods

get_threadlocal_array(thread = Thread.current) click to toggle source
# File lib/concurrent/atomic/ruby_thread_local_var.rb, line 136
def get_threadlocal_array(thread = Thread.current)
  thread.thread_variable_get(:__threadlocal_array__)
end
set_threadlocal_array(array, thread = Thread.current) click to toggle source
# File lib/concurrent/atomic/ruby_thread_local_var.rb, line 140
def set_threadlocal_array(array, thread = Thread.current)
  thread.thread_variable_set(:__threadlocal_array__, array)
end
value_for(thread) click to toggle source

This exists only for use in testing @!visibility private

# File lib/concurrent/atomic/ruby_thread_local_var.rb, line 157
def value_for(thread)
  if array = get_threadlocal_array(thread)
    value = array[@index]
    if value.nil?
      @default
    elsif value.equal?(NULL)
      nil
    else
      value
    end
  else
    @default
  end
end