File: weakstorage.rb

package info (click to toggle)
mikutter 5.1.0%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 9,780 kB
  • sloc: ruby: 22,912; sh: 186; makefile: 21
file content (268 lines) | stat: -rw-r--r-- 6,521 bytes parent folder | download | duplicates (3)
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