class Concurrent::RubyThreadLocalVar

@!visibility private @!macro internal_implementation_note

Constants

ARRAYS
FREE

@!visibility private

LOCK

Protected Class Methods

thread_finalizer(array) click to toggle source

@!visibility private

# File lib/concurrent/atomic/ruby_thread_local_var.rb, line 101
def self.thread_finalizer(array)
  proc do
    Thread.new do # avoid error: can't be called from trap context
      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
end
threadlocal_finalizer(index) click to toggle source

@!visibility private

# File lib/concurrent/atomic/ruby_thread_local_var.rb, line 84
def self.threadlocal_finalizer(index)
  proc do
    Thread.new do # avoid error: can't be called from trap context
      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
end

Public Instance Methods

value() click to toggle source

@!macro thread_local_var_method_get

# File lib/concurrent/atomic/ruby_thread_local_var.rb, line 39
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 55
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 72
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

default_for(thread) click to toggle source
# File lib/concurrent/atomic/ruby_thread_local_var.rb, line 153
def default_for(thread)
  if @default_block
    raise "Cannot use default_for with default block"
  else
    @default
  end
end
get_threadlocal_array(thread = Thread.current) click to toggle source
# File lib/concurrent/atomic/ruby_thread_local_var.rb, line 117
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 121
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 138
def value_for(thread)
  if array = get_threadlocal_array(thread)
    value = array[@index]
    if value.nil?
      default_for(thread)
    elsif value.equal?(NULL)
      nil
    else
      value
    end
  else
    default_for(thread)
  end
end