unicache 0.0.1

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