unicache 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|