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 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268
|
# -*- coding: utf-8 -*-
#
# Weak Storage
#
require 'set'
require 'thread'
require 'weakref'
class WeakStore
def initialize
@_storage = gen_storage
@_mutex = Monitor.new
@_tls_name = "weakstore_lock_#{@_mutex.object_id}".to_sym
end
def atomic(&proc)
Thread.current[@_tls_name] ||= 0
Thread.current[@_tls_name] += 1
begin
@_mutex.synchronize(&proc)
ensure
Thread.current[@_tls_name] -= 1
end
end
protected
def storage
if(Thread.current[@_tls_name] and Thread.current[@_tls_name] >= 1)
@_storage
else
raise "WeakStore inner storage can control in atomic{} block only. #{Thread.current[@_tls_name]}"
nil end end
end
# 一定時間以上参照されなかったデータを自動的に忘れていく連想配列っぽいもの。
# デフォルトの最低有効期限(expire)は1800秒(30分)。expireより長くオブジェクトが保持される可能性がある。
class TimeLimitedStorage < WeakStore
def initialize(key_class=Object, val_class=Object, expire = 1800)
@key_class, @val_class, @expire, @repository, @last_modified = key_class, val_class, expire, gen_storage, Time.new.freeze
super()
end
def [](key)
atomic{
result = if storage.has_key?(key)
storage[key]
elsif @repository.has_key?(key)
@_storage[key] = @repository[key] end
repository
result } end
alias get []
def []=(key, val)
type_strict key => @key_class, val => @val_class
atomic{
storage[key] = val
@repository.delete(key) if(@repository.has_key?(key))
repository }
end
alias store []=
def has_key?(key)
atomic{
result = if storage.has_key?(key)
true
elsif @repository.has_key?(key)
@_storage[key] = @repository[key]
true end
repository
result } end
def inspect
atomic{ "#<TimeLimitedStorage(#{@key_class} => #{@val_class}): #{storage.size}>" }
end
private
def repository
now = Time.new
if (@last_modified + @expire) < now
@last_modified = now.freeze
@repository = @_storage
@_storage = gen_storage end end
def gen_storage
Hash.new end
end
# ストレージ内の要素が一定のサイズ以上になったら、古いものから消去される連想配列っぽいもの。
class SizeLimitedStorage < WeakStore
# 格納できる容量
attr_accessor :limit
# 使用している容量
attr_reader :using
# ==== Args
# [limit] 格納できる容量(Integer)
# [&proc] 要素の容量を返すブロック(デフォルト: sizeメソッド)
def initialize(key_class, val_class, limit, proc=nil, &block)
proc ||= block || :size.to_proc
@key_class, @val_class, @limit, @get_size = key_class, val_class, limit, proc
@using = 0
super()
end
def [](key)
atomic { storage[key] } end
alias get []
# 値 _val_ を追加する。
# これを入れることで容量制限を超えてしまう場合、入るようになるまで古い要素から順番に破棄される。
# _val_ のサイズが 容量制限以上だったら追加されない。
# ==== Args
# [key] キー
# [val] 値
# ==== Return
# val 又は nil(値が大きすぎて格納できない場合)
def []=(key, val)
type_strict key => @key_class, val => @val_class
val_size = get_size(val.freeze)
return nil if val_size >= @limit
atomic {
delete key
insert_value(key, val, val_size) }
val
end
alias store []=
def has_key?(key)
atomic{ storage.has_key? key }
end
# _key_ に対応する値を削除する
# ==== Args
# [key] キー
# ==== Return
# 削除した値。値が存在しない場合はnil。
def delete(key)
type_strict key => @key_class
atomic {
if has_key? key
result = storage.delete(key)
@using -= get_size(result)
result end } end
def inspect
"#<SizeLimitedStorage(#{@using}/#{@limit})>" end
private
def get_size(obj)
type_strict obj => @val_class
@get_size.call(obj) end
def insert_value(key, val, val_size = get_size(val))
type_strict key => @key_class, val => @val_class
if (@using + val_size) <= @limit
@using += val_size
storage[key] = val
else
delete storage.first[0]
insert_value(key, val, val_size) end end
def gen_storage
Hash.new end end
class WeakStorage < WeakStore
def initialize(key_class, val_class, name: Kernel.caller(1).first)
@key_class, @val_class = key_class, val_class
@name = -name
super()
end
def [](key)
result = atomic do
wr = storage[key]
if wr
if wr.weakref_alive?
wr.__getobj__
else
count_before = storage.size
storage.keep_if do |_, v|
v.weakref_alive?
end
count_after = storage.size
notice "#{@name}: #{count_before} -> #{count_after}, #{count_before - count_after} reference(s) was deleted."
nil
end
end
end
type_strict result => @val_class if result
result
end
alias add []
def []=(key, val)
type_strict key => @key_class, val => @val_class
atomic do
storage[key] = WeakRef.new(val)
result = storage[key].__getobj__
unless result.eql?(val)
error "#{@name}: object does not match!!!"
error "#{@name}: given value: #{val.inspect}"
error "#{@name}: stored value: #{result.inspect}"
abort
end
end
end
alias store []=
def has_key?(key)
atomic { storage[key]&.weakref_alive? }
end
def inspect
atomic { "#<WeakStorage(#{@name}: #{@key_class} => #{@val_class}): #{storage.size}>" }
end
private
def gen_storage
Hash.new
end
end
class WeakSet < WeakStore
include Enumerable
def initialize(val_class)
@val_class = val_class
super()
end
def each
atomic do
storage.each{ |n| yield(ObjectSpace._id2ref(n)) }
end
rescue RangeError => e
error e
nil
end
def add(val)
type_strict val => @val_class
ObjectSpace.define_finalizer(val, &gen_deleter)
atomic{
storage.add(val.object_id) } end
alias << add
def inspect
atomic{ "#<WeakSet(#{@val_class}): #{storage.size}>" }
end
private
def gen_storage
Set.new end
def gen_deleter
@gen_deleter ||= lambda{ |objid|
atomic{
storage.delete(objid) } } end
end
|