1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
|
require 'delegate'
require 'weakref'
require 'thread'
module Tool
# Have thread local values without them actually being thread global.
#
# Advantages:
# * Values for all threads are garbage collected when ThreadLocal instance is.
# * Values for specific thread are garbage collected when thread is.
# * No hidden global state.
# * Supports other data types besides hashes.
#
# @example To replace Thread.current hash access
# local = Tool::ThreadLocal.new
# local[:key] = "value"
#
# Thread.new do
# local[:key] = "other value"
# puts local[:key] # other value
# end.join
#
# puts local[:key] # value
#
# @example Usage with Array
# local = Tool::ThreadLocal.new([:foo])
# local << :bar
#
# Thread.new { p local }.join # [:foo]
# p local # [:foo, :bar]
class ThreadLocal < Delegator
@mutex ||= Mutex.new
@locals ||= []
# Thread finalizer.
# @!visibility private
def self.cleanup(id)
@locals.keep_if do |local|
next false unless local.weakref_alive?
local.__cleanup__
true
end
end
# Generates weak reference to thread and sets up finalizer.
# @return [WeakRef]
# @!visibility private
def self.ref(thread)
thread[:weakref] ||= begin
ObjectSpace.define_finalizer(thread, method(:cleanup))
WeakRef.new(thread)
end
end
# @see #initialize
# @!visibility private
def self.new(*)
result = super
@mutex.synchronize { @locals << WeakRef.new(result) }
result
end
def initialize(default = {})
@default = default.dup
@map = {}
end
# @see Delegator
# @!visibility private
def __getobj__
ref = ::Tool::ThreadLocal.ref(Thread.current)
@map[ref] ||= @default.dup
end
# @return [Integer] number of threads with specific locals
# @!visibility private
def __size__
@map.size
end
# Remove locals for dead or GC'ed threads
# @!visibility private
def __cleanup__
@map.keep_if { |key, value| key.weakref_alive? and key.alive? }
end
end
end
|