unicache 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.rdoc +3 -0
- data/LICENSE +20 -0
- data/README.rdoc +15 -0
- data/Rakefile +28 -0
- data/doc/UniCache.html +1905 -0
- data/doc/UniCache/CallbackError.html +142 -0
- data/doc/UniCache/Error.html +138 -0
- data/doc/UniCache/FetchError.html +142 -0
- data/doc/UniCache/LruEviction.html +583 -0
- data/doc/UniCache/RemoveError.html +142 -0
- data/doc/UniCache/SizeError.html +142 -0
- data/doc/_index.html +203 -0
- data/doc/class_list.html +53 -0
- data/doc/css/common.css +1 -0
- data/doc/css/full_list.css +57 -0
- data/doc/css/style.css +338 -0
- data/doc/file.CHANGELOG.html +79 -0
- data/doc/file.README.html +88 -0
- data/doc/file_list.html +58 -0
- data/doc/frames.html +28 -0
- data/doc/index.html +88 -0
- data/doc/js/app.js +214 -0
- data/doc/js/full_list.js +178 -0
- data/doc/js/jquery.js +4 -0
- data/doc/method_list.html +196 -0
- data/doc/top-level-namespace.html +112 -0
- data/lib/unicache.rb +498 -0
- data/test/test_all.rb +248 -0
- metadata +79 -0
data/lib/unicache.rb
ADDED
@@ -0,0 +1,498 @@
|
|
1
|
+
# UniCache is Universal purpose Cache with Least Recently Used
|
2
|
+
# replacement policy by default. Cache can be configured with another
|
3
|
+
# policy.
|
4
|
+
#
|
5
|
+
# UniCache is intended to be Thread safe.
|
6
|
+
#
|
7
|
+
# User can register callbacks that are run at various UniCache events.
|
8
|
+
# ":getdata" callback is used to retreive cache data if not in
|
9
|
+
# cache. Others do not effect the UniCache state.
|
10
|
+
#
|
11
|
+
# Usage example:
|
12
|
+
# require 'unicache'
|
13
|
+
#
|
14
|
+
# # Create cache with size 2.
|
15
|
+
# c = UniCache.new( 2 )
|
16
|
+
#
|
17
|
+
# # Register callbacks for some events (two for hit).
|
18
|
+
# c.registerCallback( :hit, Proc.new{ |k,v| puts "Hit: #{k}:#{v}" } )
|
19
|
+
# c.registerCallback( :hit, Proc.new{ |k,v| puts "Found: #{k}:#{v}" } )
|
20
|
+
# c.registerCallback( :miss, Proc.new{ |k,v| puts "Miss: #{k}:<NA>" } )
|
21
|
+
#
|
22
|
+
# # Set some values.
|
23
|
+
# c[0] = 'foo' # set 0 as 'foo'
|
24
|
+
# c.put( 'bar', 'foo' )
|
25
|
+
#
|
26
|
+
# # Get (or try to get) some values.
|
27
|
+
# a = c[ 'bar' ] # hit, a == 'foo'
|
28
|
+
# b = c.get( 0 ) # hit, b == 'foo'
|
29
|
+
# c.get( 'foo' ) # miss
|
30
|
+
# c.get( 2 ) # miss
|
31
|
+
#
|
32
|
+
# Produces:
|
33
|
+
# Hit: bar:foo
|
34
|
+
# Found: bar:foo
|
35
|
+
# Hit: 0:foo
|
36
|
+
# Found: 0:foo
|
37
|
+
# Miss: foo:<NA>
|
38
|
+
# Miss: 2:<NA>
|
39
|
+
|
40
|
+
class UniCache < Hash
|
41
|
+
|
42
|
+
# Generic UniCache exception.
|
43
|
+
class Error < StandardError; end
|
44
|
+
|
45
|
+
# ":getdata" callback data fetch exception.
|
46
|
+
class FetchError < Error; end
|
47
|
+
|
48
|
+
# Entry remove error.
|
49
|
+
class RemoveError < Error; end
|
50
|
+
|
51
|
+
# Callback error.
|
52
|
+
class CallbackError < Error; end
|
53
|
+
|
54
|
+
# Sizing error.
|
55
|
+
class SizeError < Error; end
|
56
|
+
|
57
|
+
|
58
|
+
# Cache size.
|
59
|
+
attr_reader :size
|
60
|
+
|
61
|
+
# Access lock.
|
62
|
+
attr_reader :lock
|
63
|
+
|
64
|
+
# Cache initialization.
|
65
|
+
#
|
66
|
+
# @param size [Integer] Cache size.
|
67
|
+
# @param evict [Object] Eviction policy.
|
68
|
+
def initialize( size, evict = LruEviction.new )
|
69
|
+
super()
|
70
|
+
|
71
|
+
@lock = Mutex.new
|
72
|
+
|
73
|
+
resize( size )
|
74
|
+
|
75
|
+
setEviction( evict )
|
76
|
+
|
77
|
+
# See: registerCallback.
|
78
|
+
@callbacks = {}
|
79
|
+
|
80
|
+
@callbackType = [ :getdata,
|
81
|
+
:add, :replace, :put, :overwrite,
|
82
|
+
:remove, :valueremove, :hit, :miss,
|
83
|
+
:peek ]
|
84
|
+
|
85
|
+
@callbackType.each do |type|
|
86
|
+
@callbacks[ type ] = []
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
# Set the eviction policy.
|
93
|
+
#
|
94
|
+
# @param evict [Object] Cache eviction policy.
|
95
|
+
def setEviction( evict )
|
96
|
+
@evict = evict
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
alias :hash_set_op :[]=
|
101
|
+
alias :hash_get_op :[]
|
102
|
+
|
103
|
+
alias :hash_store :store
|
104
|
+
alias :hash_fetch :fetch
|
105
|
+
|
106
|
+
alias :hash_delete :delete
|
107
|
+
|
108
|
+
|
109
|
+
# Disable Hash manipulate methods:
|
110
|
+
[ :merge, :merge!, :delete_if, :replace, :keep_if,
|
111
|
+
:reject, :update, :shift ].each do |method|
|
112
|
+
define_method( method ) {}
|
113
|
+
undef_method method
|
114
|
+
end
|
115
|
+
|
116
|
+
# Dummy definition for "store" just for removal.
|
117
|
+
def store(); end
|
118
|
+
|
119
|
+
# Remove "store" from UniCache.
|
120
|
+
remove_method :store
|
121
|
+
|
122
|
+
|
123
|
+
# Put new entry to the Cache. Make space for the new entry if
|
124
|
+
# needed.
|
125
|
+
#
|
126
|
+
# @param key [Object] Key (or tag) of the Cache entry.
|
127
|
+
# @param value [Object] Value of the Cache entry.
|
128
|
+
def put( key, value )
|
129
|
+
@lock.lock
|
130
|
+
ret = _put( key, value )
|
131
|
+
@lock.unlock
|
132
|
+
ret
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
# Put operator.
|
137
|
+
def []=( key, value )
|
138
|
+
self.put( key, value )
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
# Get Cache entry by key or return nil if not in Cache.
|
143
|
+
#
|
144
|
+
# If block is provided, it is run under UniCache lock and will
|
145
|
+
# protect the used data from being removed (concurrently).
|
146
|
+
#
|
147
|
+
# If ":getdata" callback is defined, it is used to get data. Data
|
148
|
+
# is always used, also when nil. UniCache lock is released before
|
149
|
+
# callback is called.
|
150
|
+
#
|
151
|
+
# @param key [Object] Key (or tag) of the Cache entry.
|
152
|
+
# @yield Procedure to fetch the data.
|
153
|
+
def get( key, &blk )
|
154
|
+
@lock.lock
|
155
|
+
ret = _get( key, &blk )
|
156
|
+
@lock.unlock
|
157
|
+
ret
|
158
|
+
end
|
159
|
+
|
160
|
+
|
161
|
+
# Get operator.
|
162
|
+
def []( key )
|
163
|
+
get( key )
|
164
|
+
end
|
165
|
+
|
166
|
+
|
167
|
+
# Get Cache entry by key (no effect to eviction).
|
168
|
+
#
|
169
|
+
# @param key [Object] Entry key or tag.
|
170
|
+
def peek( key )
|
171
|
+
runCallbacks( :peek, key )
|
172
|
+
hash_fetch( key, nil )
|
173
|
+
end
|
174
|
+
|
175
|
+
|
176
|
+
# Get Cache entry existance by key (no effect to eviction). Uses
|
177
|
+
# "peek", i.e. peek CB is run.
|
178
|
+
#
|
179
|
+
# @param key [Object] Entry key or tag.
|
180
|
+
def exist?( key )
|
181
|
+
if peek( key )
|
182
|
+
true
|
183
|
+
else
|
184
|
+
false
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
|
189
|
+
# Remove oldest Cache entry (Least Recently Used) or given.
|
190
|
+
#
|
191
|
+
# @param key [Object] Key to remove.
|
192
|
+
# @return [Object, Object] Key/value pair removed or nil if no entries.
|
193
|
+
def remove( key = nil )
|
194
|
+
@lock.lock
|
195
|
+
ret = _remove( key )
|
196
|
+
@lock.unlock
|
197
|
+
ret
|
198
|
+
end
|
199
|
+
|
200
|
+
|
201
|
+
# Resize the Cache.
|
202
|
+
#
|
203
|
+
# @param size [Integer] New Cache size.
|
204
|
+
def resize( size )
|
205
|
+
|
206
|
+
raise SizeError, "UniCache: Size must be bigger than 0" unless size > 0
|
207
|
+
|
208
|
+
@lock.lock
|
209
|
+
|
210
|
+
# Remove entries that does not fit anymore.
|
211
|
+
if @size && size < @size
|
212
|
+
( @size - size ).times do
|
213
|
+
_remove
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
ret = @size = size
|
218
|
+
|
219
|
+
@lock.unlock
|
220
|
+
|
221
|
+
ret
|
222
|
+
end
|
223
|
+
|
224
|
+
|
225
|
+
# Register a callback to be executed on Cache event.
|
226
|
+
#
|
227
|
+
# Possible events:
|
228
|
+
#
|
229
|
+
# [getdata] Data not found in cache, callback is used to fetch
|
230
|
+
# it. Data is always added to cache unless an exception
|
231
|
+
# is raised. Also all retrys should be captured into
|
232
|
+
# ":getdata". <key,value> from request.
|
233
|
+
#
|
234
|
+
# [add] Item is added to Cache without anything being
|
235
|
+
# replaced. <key,value> from request.
|
236
|
+
#
|
237
|
+
# [replace] Item is added to Cache and another item is
|
238
|
+
# removed. <key,value> is evicted entry.
|
239
|
+
#
|
240
|
+
# [put] Item is added to Cache. Cache conditions has no effect
|
241
|
+
# i.e. always run for "put". <key,value> from request.
|
242
|
+
#
|
243
|
+
# [overwrite] Existing entry value is replaced by new item
|
244
|
+
# value. <key,value> from request.
|
245
|
+
#
|
246
|
+
# [remove] Entry is deleted from Cache.
|
247
|
+
#
|
248
|
+
# [valueremove] Entry value is removed (remove or overwrite).
|
249
|
+
# <key,value> from old entry.
|
250
|
+
#
|
251
|
+
# [hit] Entry is found in Cache. <key,value> from cache.
|
252
|
+
#
|
253
|
+
# [miss] Entry is not found in Cache. <key> from request.
|
254
|
+
#
|
255
|
+
# [peek] Cache peek. <key,value> from request.
|
256
|
+
#
|
257
|
+
# Example:
|
258
|
+
# registerCallback( :add,
|
259
|
+
# Proc.new do |k,v,o| puts "Key: #{k} with value #{v} added" end
|
260
|
+
#
|
261
|
+
# @param type [Symbol] Callback event.
|
262
|
+
# @param proc [Proc] Callback called with args: <key>, <value>, <self>.
|
263
|
+
def registerCallback( type, proc )
|
264
|
+
if proc.kind_of?( Proc ) && @callbackType.index( type )
|
265
|
+
@callbacks[ type ].push proc
|
266
|
+
else
|
267
|
+
raise CallbackError, "UniCache: Trying to register invalid callback..."
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
|
272
|
+
# Remove all callbacks for type or all.
|
273
|
+
#
|
274
|
+
# @param type [Symbol] Remove callback by type.
|
275
|
+
def removeCallback( type = nil )
|
276
|
+
if type
|
277
|
+
@callbacks[ type ] = []
|
278
|
+
else
|
279
|
+
@callbackType.each do |type|
|
280
|
+
@callbacks[ type ] = []
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
|
286
|
+
# Clear all Cache entries.
|
287
|
+
def clear
|
288
|
+
@lock.lock
|
289
|
+
self.each do |k,v|
|
290
|
+
removeEntry( k )
|
291
|
+
end
|
292
|
+
@evict.clear
|
293
|
+
@lock.unlock
|
294
|
+
end
|
295
|
+
|
296
|
+
|
297
|
+
|
298
|
+
|
299
|
+
# LeastRecentlyUsed eviction policy. Mutex should be used to
|
300
|
+
# ensure atomicity of operations.
|
301
|
+
#
|
302
|
+
# Eviction policy class should include methods:
|
303
|
+
# [update] Called when key is accessed (get/put).
|
304
|
+
# [remove] Called when key is removed.
|
305
|
+
# [nextEvict] Peek to what is going to be removed next.
|
306
|
+
# [clear] Reset eviction state.
|
307
|
+
class LruEviction
|
308
|
+
|
309
|
+
|
310
|
+
# Instantiate.
|
311
|
+
def initialize
|
312
|
+
@lock = Mutex.new
|
313
|
+
clear
|
314
|
+
end
|
315
|
+
|
316
|
+
|
317
|
+
# Clear eviction list.
|
318
|
+
def clear
|
319
|
+
@lock.lock
|
320
|
+
@list = []
|
321
|
+
@lock.unlock
|
322
|
+
end
|
323
|
+
|
324
|
+
|
325
|
+
# Keep track of the least recently used keys. Place oldest at
|
326
|
+
# the beginning of the list.
|
327
|
+
#
|
328
|
+
# @param key [Object]
|
329
|
+
def update( key )
|
330
|
+
|
331
|
+
@lock.lock
|
332
|
+
|
333
|
+
# Delete old entries of this key.
|
334
|
+
@list.delete_if do |i| i == key end
|
335
|
+
|
336
|
+
# Add to end of LRU.
|
337
|
+
@list.push key
|
338
|
+
|
339
|
+
@lock.unlock
|
340
|
+
end
|
341
|
+
|
342
|
+
|
343
|
+
# Remove oldest entry.
|
344
|
+
#
|
345
|
+
# @param key [Object] If given, remove this entry instead of oldest.
|
346
|
+
def remove( key = nil )
|
347
|
+
@lock.lock
|
348
|
+
|
349
|
+
res = nil
|
350
|
+
|
351
|
+
if key
|
352
|
+
@list.delete_if do |i| i == key end
|
353
|
+
res = key
|
354
|
+
else
|
355
|
+
res = @list.shift
|
356
|
+
end
|
357
|
+
|
358
|
+
@lock.unlock
|
359
|
+
|
360
|
+
res
|
361
|
+
end
|
362
|
+
|
363
|
+
|
364
|
+
# Return the oldest i.e. next to be evicted.
|
365
|
+
def nextEvict
|
366
|
+
@list[0]
|
367
|
+
end
|
368
|
+
|
369
|
+
end
|
370
|
+
|
371
|
+
|
372
|
+
|
373
|
+
private
|
374
|
+
|
375
|
+
|
376
|
+
def _put( key, value )
|
377
|
+
runAdd = false
|
378
|
+
|
379
|
+
if hash_fetch( key, nil )
|
380
|
+
|
381
|
+
# Entry exists already.
|
382
|
+
runCallbacks( :overwrite, key, value )
|
383
|
+
runCallbacks( :valueremove, key )
|
384
|
+
|
385
|
+
elsif self.length >= @size
|
386
|
+
|
387
|
+
# Overflow.
|
388
|
+
runCallbacks( :replace, @evict.nextEvict )
|
389
|
+
_remove
|
390
|
+
|
391
|
+
else
|
392
|
+
|
393
|
+
# Delay add callback to have the entry value set.
|
394
|
+
runAdd = true
|
395
|
+
|
396
|
+
end
|
397
|
+
|
398
|
+
@evict.update( key )
|
399
|
+
|
400
|
+
hash_store( key, value )
|
401
|
+
|
402
|
+
runCallbacks( :add, key ) if runAdd
|
403
|
+
runCallbacks( :put, key )
|
404
|
+
|
405
|
+
self
|
406
|
+
end
|
407
|
+
|
408
|
+
|
409
|
+
def _get( key, &blk )
|
410
|
+
|
411
|
+
ret = hash_fetch( key, nil )
|
412
|
+
|
413
|
+
if ret
|
414
|
+
|
415
|
+
@evict.update( key )
|
416
|
+
runCallbacks( :hit, key )
|
417
|
+
|
418
|
+
else
|
419
|
+
|
420
|
+
runCallbacks( :miss, key, nil )
|
421
|
+
|
422
|
+
unless @callbacks[ :getdata ].empty?
|
423
|
+
|
424
|
+
# Get value using callback.
|
425
|
+
|
426
|
+
@lock.unlock if @lock.locked?
|
427
|
+
ret = runCallbacks( :getdata, key, nil )
|
428
|
+
@lock.lock
|
429
|
+
_put( key, ret )
|
430
|
+
|
431
|
+
end
|
432
|
+
|
433
|
+
end
|
434
|
+
|
435
|
+
if block_given?
|
436
|
+
|
437
|
+
# Get value using block.
|
438
|
+
yield( ret )
|
439
|
+
|
440
|
+
else
|
441
|
+
|
442
|
+
ret
|
443
|
+
|
444
|
+
end
|
445
|
+
|
446
|
+
|
447
|
+
end
|
448
|
+
|
449
|
+
|
450
|
+
def _remove( key = nil )
|
451
|
+
|
452
|
+
if key
|
453
|
+
unless keys.index( key )
|
454
|
+
@lock.unlock if @lock.locked?
|
455
|
+
raise RemoveError, "Key \"#{key}\" does not exist..."
|
456
|
+
end
|
457
|
+
else
|
458
|
+
key = @evict.remove
|
459
|
+
end
|
460
|
+
|
461
|
+
value = removeEntry( key )
|
462
|
+
|
463
|
+
[ key, value ]
|
464
|
+
end
|
465
|
+
|
466
|
+
|
467
|
+
# Remove entry from Cache.
|
468
|
+
def removeEntry( key )
|
469
|
+
if hash_fetch( key, nil )
|
470
|
+
runCallbacks( :remove, key )
|
471
|
+
runCallbacks( :valueremove, key )
|
472
|
+
value = hash_delete( key )
|
473
|
+
else
|
474
|
+
nil
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
|
479
|
+
# Run all callbacks per type.
|
480
|
+
def runCallbacks( type, key, value = nil )
|
481
|
+
ret = nil
|
482
|
+
@callbacks[ type ].each do |i|
|
483
|
+
ret = callback( i, key, value )
|
484
|
+
end
|
485
|
+
ret
|
486
|
+
end
|
487
|
+
|
488
|
+
|
489
|
+
# Run given callback.
|
490
|
+
def callback( cb, key, value = nil )
|
491
|
+
unless value
|
492
|
+
value = hash_fetch( key, nil )
|
493
|
+
end
|
494
|
+
cb.call( key, value, self ) if cb
|
495
|
+
end
|
496
|
+
|
497
|
+
|
498
|
+
end
|