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/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