cache_lib 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 49951bebe666d8d905c8cb74e5b1324f95f460da
4
- data.tar.gz: eb58345e35655ea65121a69d22f6d936c9eae506
3
+ metadata.gz: 04ef9e417311a3650f36194439a2a1a131a15079
4
+ data.tar.gz: dc0a81c4f79f2cd9da54ab117b304cf0a07fb549
5
5
  SHA512:
6
- metadata.gz: 51a1d55a39dbacf762ab5b1b58275de0018fb968d7b200e7dbddbed4f159e1f3691ebd96232999a484fcffc355f9703922f3bc3f43017dd66076a0ce5c48bd28
7
- data.tar.gz: f38f39b35db8a674c052184ba4ee32e8b7c640d06f56574ef5d528f9c4e1356ae38610949b4329d55cf48cc370b00e5007296aeab4826f3df09a1d4ba6a45607
6
+ metadata.gz: 44933670c7e8e9e1d56c99b3fe9b97268f6c2ce70f3bb463ad97d4fa8cf4e0e6d6858d33fa2c7bc04301a52a63e42a074255bb4be1dcb02605d869d40fe93002
7
+ data.tar.gz: 10b126320be7b1ff1f6da63c57a16d7c3c3b225810faec6ed59557a83c2d083dea87b94d0b82a84cb3bdf492c875d6c7a5965cf762a2ca590733d3d8989a8111
data/CHANGELOG.md ADDED
@@ -0,0 +1,8 @@
1
+ ### 0.1.0
2
+ This release does not have any breaking API changes.
3
+ NEW: TtlCache added, a simple extension of the LRU cache with a cache level ttl eviction strategy.
4
+ NEW: #delete and #expire method added to all caches.
5
+ FIX: Infinite loop bug LirsCache fixed.
6
+
7
+ ### 0.0.1
8
+ Initial release implementing Basic, FIFO, LRU and LIRS caches.
data/NOTICE.md ADDED
@@ -0,0 +1,31 @@
1
+ ## Credits
2
+ ### LruRedux
3
+ ::LirsCache and ::SafeLirsCache started as a feature fork of LruRedux 0.8.1.
4
+
5
+ ##### Homepage:
6
+ https://github.com/SamSaffron/lru_redux/
7
+ ##### License:
8
+ ```
9
+ Copyright (c) 2013 Sam Saffron
10
+
11
+ MIT License
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining
14
+ a copy of this software and associated documentation files (the
15
+ "Software"), to deal in the Software without restriction, including
16
+ without limitation the rights to use, copy, modify, merge, publish,
17
+ distribute, sublicense, and/or sell copies of the Software, and to
18
+ permit persons to whom the Software is furnished to do so, subject to
19
+ the following conditions:
20
+
21
+ The above copyright notice and this permission notice shall be
22
+ included in all copies or substantial portions of the Software.
23
+
24
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31
+ ```
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # CacheLib
2
- #### A Ruby caching library implementing Basic, FIFO, LRU and LIRS caches.
2
+ #### A Ruby caching library implementing Basic, FIFO, LRU, TTL and LIRS caches.
3
3
 
4
4
  ##### CacheLib is young library and breaking api changes are still possible. Please read the release notes before upgrading.
5
- CacheLib currently implements basic, FIFO, LRU and LIRS caches along with offering thread safe implementations of each. Originally a LIRS cache feature fork of [LruRedux](https://github.com/SamSaffron/lru_redux), CacheLib was built to provide a clean base for implementing the LIRS cache in Ruby, along with offering user friendly api.
5
+ CacheLib currently implements basic, FIFO, LRU and LIRS caches along with offering thread safe implementations of each. Originally a LIRS cache feature fork of [LruRedux](https://github.com/SamSaffron/lru_redux), CacheLib was built to provide a clean base for implementing the LIRS cache in Ruby, along with offering a user friendly api.
6
6
 
7
7
  ##### Basic:
8
8
  The basic cache is the simplest cache available and forms the base for the others. It does NOT have an eviction strategy and will store however much it is given.
@@ -13,8 +13,11 @@ The FIFO cache adds to the basic cache with a simple First In First Our eviction
13
13
  ##### LRU:
14
14
  The LRU cache further adds to the previous caches with a Least Recently Used eviction strategy. The primary difference between FIFO and LRU is that LRU will refresh currently cached items if they are requested again.
15
15
 
16
+ ##### TTL:
17
+ The TTL cache is an extension of the LRU cache, adding a TTL eviction strategy that takes precedence over LRU eviction.
18
+
16
19
  ##### LIRS:
17
- The LIRS cache implements the Low Inter-Reference Recency Set Replacement Policy developed by Song Jiang and Xiaodong Zhang. Please reference the [original paper](http://citeseer.ist.psu.edu/viewdoc/summary?doi=10.1.1.116.2184) for more information.
20
+ The LIRS cache implements the Low Inter-Reference Recency Set eviction policy. LIRS improves performance over LRU by taking into account the recency and reuse of an item in eviction selection. Please reference the [original paper](http://citeseer.ist.psu.edu/viewdoc/summary?doi=10.1.1.116.2184) for more information.
18
21
 
19
22
  ##### CacheLib is Ruby >= 1.9.3 only.
20
23
  If you are looking for a LRU cache that supports an earlier version or just want an alternative LRU cache, please take a look at [LruRedux](https://github.com/SamSaffron/lru_redux).
@@ -33,8 +36,14 @@ cache = CacheLib.create :fifo, 100
33
36
  # LRU with a limit of 100
34
37
  cache = CacheLib.create :lru, 100
35
38
 
39
+ # TTL with a limit of 100 and a ttl of 5 minutes.
40
+ cache = CacheLib.create :ttl, 100, 5 * 60
41
+
36
42
  # LIRS with a cache limit of 100, made up of a S limit of 95 and a Q limit of 5.
37
43
  cache = CacheLib.create :lirs, 95, 5
44
+
45
+ # Threadsafe versions of every cache can be made by using CacheLib.safe_create
46
+ cache = CacheLib.safe_create :lirs, 95, 5
38
47
  ```
39
48
 
40
49
  ### Using the cache
@@ -42,7 +51,9 @@ cache = CacheLib.create :lirs, 95, 5
42
51
  # Cache methods remain the same across all variations
43
52
  cache = CacheLib.create :lru, 100
44
53
 
45
- # .get is the primary method for accessing the cache. It requires a key and block that will generate the value that is stored if the key is not cached.
54
+ # .get is the primary method for accessing the cache.
55
+ # It requires a key and block that will generate the value that is stored
56
+ # if the key is not cached.
46
57
  cache.get(:a) { 'Apple' }
47
58
  # => 'Apple'
48
59
  cache.get(:b) { 'Beethoven' }
@@ -50,7 +61,8 @@ cache.get(:b) { 'Beethoven' }
50
61
  cache.get(:a) { 'Apricot' }
51
62
  # => 'Apple'
52
63
 
53
- # .store takes a key and value and implements the functionality of Hash#store. Will refresh the key in LRU and LIRS caches.
64
+ # .store takes a key and value and implements the functionality of Hash#store.
65
+ # Will refresh the key in LRU and LIRS caches.
54
66
  cache.store(:c, 'Chopin')
55
67
  # => 'Chopin'
56
68
  cache.store(:a, 'Mozart')
@@ -70,18 +82,24 @@ cache.lookup(:t)
70
82
  cache[:a]
71
83
  # => 'Mozart'
72
84
 
73
- # .fetch is similar to .lookup, but takes an optional block that will execute if the key is not cached.
85
+ # .fetch is similar to .lookup,
86
+ # but takes an optional block that will execute if the key is not cached.
74
87
  cache.fetch(:c)
75
88
  # => 'Chopin'
76
89
  cache.fetch(:r) { 21 * 2 }
77
90
  # => 42
78
91
 
79
- # .evict takes a key and will remove the key from the cache and return the associated value. Returns nil is the key is not cached.
92
+ # .evict takes a key and will remove the key from the cache and return the associated value.
93
+ # Returns nil is the key is not cached.
80
94
  cache.evict(:d)
81
95
  # => 'Denmark'
82
96
  cache.evict(:r)
83
97
  # => nil
84
98
 
99
+ # .delete is equivalent to .evict
100
+ cache.delete(:x)
101
+ # => nil
102
+
85
103
  # .each takes each key value pair and applies the given block to them.
86
104
  cache.each { |_, value| puts value }
87
105
  # => 'Chopin'
@@ -94,7 +112,9 @@ cache.key?(:a)
94
112
  cache.key?(:g)
95
113
  # => false
96
114
 
97
- # .to_a returns an array of arrays of key value pairs that are cached. Basic, FIFO and LIRS are insertion ordered while LRU is recency ordered, starting with newest.
115
+ # .to_a returns an array of arrays of key value pairs that are cached.
116
+ # Basic, FIFO and LIRS are insertion ordered while LRU and TTL are recency ordered,
117
+ # starting with the newest.
98
118
  cache.to_a
99
119
  # => [[:c, 'Chopin'], [:a, 'Mozart'], [:b, 'Beethoven']]
100
120
 
@@ -110,13 +130,44 @@ cache.values
110
130
  cache.size
111
131
  # => 3
112
132
 
133
+ # .limit returns the current cache limit.
134
+ cache.limit
135
+ # => 100
136
+
137
+ # The limit can be changed with .limit=.
138
+ cache.limit = 125
139
+ # => 125
140
+
113
141
  # .clear removes all items from the cache.
114
142
  cache.clear
115
143
  # => nil
144
+
145
+ # The LIRS and TTL caches have additional options for .limit=.
146
+ lirs = CacheLib.create :lirs, 95, 5
147
+ ttl = CacheLib.create :ttl, 100, 5 * 60
148
+
149
+ # LIRS require both the Stack limit and Queue limit be given,
150
+ # and their sum is the cache limit.
151
+ lirs.limit = 120, 5
152
+ # => [120, 5]
153
+ lirs.limit
154
+ # => 125
155
+
156
+ # TTL can have a new limit and ttl set at the same time.
157
+ ttl.limit = 125, 10 * 60
158
+ # => [125, 600]
159
+
160
+ # Or just the limit
161
+ ttl.limit = 150
162
+
163
+ # Or just the ttl
164
+ ttl.limit = nil, 6 * 60
116
165
  ```
117
166
 
118
- ## Copyright
167
+ ## Credits and Notices
168
+ See [NOTICE.md](NOTICE.md) for details.
119
169
 
170
+ ## Copyright
120
171
  Copyright (c) 2015 Kaijah Hougham
121
172
 
122
173
  See [LICENSE.txt](LICENSE.txt) for details.
data/cache_lib.gemspec CHANGED
@@ -6,7 +6,7 @@ Gem::Specification.new do |gem|
6
6
  gem.name = 'cache_lib'
7
7
  gem.version = CacheLib::VERSION
8
8
  gem.summary = 'A Ruby Caching Library'
9
- gem.description = 'A Ruby caching library implementing Basic, FIFO, LRU and LIRS caching strategies.'
9
+ gem.description = 'A Ruby caching library implementing Basic, FIFO, LRU, TTL and LIRS caches.'
10
10
  gem.license = 'MIT'
11
11
  gem.authors = 'Kaijah Hougham'
12
12
  gem.email = 'github@seberius.com'
@@ -21,5 +21,6 @@ Gem::Specification.new do |gem|
21
21
 
22
22
  gem.add_development_dependency 'bundler', '~> 1.7'
23
23
  gem.add_development_dependency 'rake', '~> 10'
24
- gem.add_development_dependency 'minitest', '~> 5.0'
24
+ gem.add_development_dependency 'minitest', '~> 5.5'
25
+ gem.add_development_dependency 'timecop', '~> 0.7'
25
26
  end
data/lib/cache_lib.rb CHANGED
@@ -2,10 +2,12 @@ require_relative 'cache_lib/util_hash'
2
2
  require_relative 'cache_lib/basic_cache'
3
3
  require_relative 'cache_lib/fifo_cache'
4
4
  require_relative 'cache_lib/lru_cache'
5
+ require_relative 'cache_lib/ttl_cache'
5
6
  require_relative 'cache_lib/lirs_cache'
6
7
  require_relative 'cache_lib/safe_basic_cache'
7
8
  require_relative 'cache_lib/safe_fifo_cache'
8
9
  require_relative 'cache_lib/safe_lru_cache'
10
+ require_relative 'cache_lib/safe_ttl_cache'
9
11
  require_relative 'cache_lib/safe_lirs_cache'
10
12
  require_relative 'cache_lib/version'
11
13
 
@@ -15,6 +17,7 @@ module CacheLib
15
17
  when :basic then BasicCache.new(*args)
16
18
  when :fifo then FifoCache.new(*args)
17
19
  when :lru then LruCache.new(*args)
20
+ when :ttl then TtlCache.new(*args)
18
21
  when :lirs then LirsCache.new(*args)
19
22
  else fail ArgumentError "Cache type not recognized: #{type}"
20
23
  end
@@ -25,6 +28,7 @@ module CacheLib
25
28
  when :basic then SafeBasicCache.new(*args)
26
29
  when :fifo then SafeFifoCache.new(*args)
27
30
  when :lru then SafeLruCache.new(*args)
31
+ when :ttl then SafeTtlCache.new(*args)
28
32
  when :lirs then SafeLirsCache.new(*args)
29
33
  else fail ArgumentError "Cache type not recognized: #{type}"
30
34
  end
@@ -21,9 +21,10 @@ module CacheLib
21
21
  end
22
22
 
23
23
  def get(key)
24
- value = hit(key)
24
+ has_key = true
25
+ value = @cache.fetch(key) { has_key = false }
25
26
 
26
- if value
27
+ if has_key
27
28
  value
28
29
  else
29
30
  miss(key, yield)
@@ -58,6 +59,10 @@ module CacheLib
58
59
  nil
59
60
  end
60
61
 
62
+ def expire
63
+ nil
64
+ end
65
+
61
66
  def each
62
67
  @cache.each do |pair|
63
68
  yield pair
@@ -90,11 +95,14 @@ module CacheLib
90
95
  end
91
96
 
92
97
  def inspect
93
- "#{self.class} currently caching #{@cache.size} items."
98
+ "#{self.class}, "\
99
+ "Limit: #{@limit}, "\
100
+ "Size: #{@cache.size}"
94
101
  end
95
102
 
96
103
  alias_method :[], :lookup
97
104
  alias_method :[]=, :store
105
+ alias_method :delete, :evict
98
106
 
99
107
  protected
100
108
 
@@ -3,7 +3,7 @@ module CacheLib
3
3
  def initialize(*args)
4
4
  limit, _ = args
5
5
 
6
- fail ArgumentError "Cache Limit must be 1 or greater: #{limit}" if
6
+ fail ArgumentError, "Cache Limit must be 1 or greater: #{limit}" if
7
7
  limit.nil? || limit < 1
8
8
 
9
9
  @limit = limit
@@ -13,7 +13,10 @@ module CacheLib
13
13
 
14
14
  def limit=(args)
15
15
  limit, _ = args
16
- fail ArgumentError "Cache Limit must be 1 or greater: #{limit}" if
16
+
17
+ limit ||= @limit
18
+
19
+ fail ArgumentError, "Cache Limit must be 1 or greater: #{limit}" if
17
20
  limit.nil? || limit < 1
18
21
 
19
22
  @limit = limit
@@ -21,18 +24,14 @@ module CacheLib
21
24
  resize
22
25
  end
23
26
 
24
- def inspect
25
- "#{self.class} with a limit of #{@limit} "\
26
- "currently caching #{@cache.size} items."
27
- end
28
-
29
27
  alias_method :[], :lookup
30
28
  alias_method :[]=, :store
29
+ alias_method :delete, :evict
31
30
 
32
31
  protected
33
32
 
34
33
  def resize
35
- @cache.delete(@cache.tail) while @cache.size > @limit
34
+ @cache.pop_tail while @cache.size > @limit
36
35
  end
37
36
 
38
37
  def miss(key, value)
@@ -3,9 +3,9 @@ module CacheLib
3
3
  def initialize(*args)
4
4
  s_limit, q_limit = args
5
5
 
6
- fail ArgumentError 'S Limit must be 1 or greater.' if
6
+ fail ArgumentError, 'S Limit must be 1 or greater.' if
7
7
  s_limit.nil? || s_limit < 1
8
- fail ArgumentError 'Q Limit must be 1 or greater.' if
8
+ fail ArgumentError, 'Q Limit must be 1 or greater.' if
9
9
  q_limit.nil? || q_limit < 1
10
10
 
11
11
  @s_limit = s_limit
@@ -32,9 +32,9 @@ module CacheLib
32
32
  def limit=(args)
33
33
  s_limit, q_limit = args
34
34
 
35
- fail ArgumentError 'S Limit must be 1 or greater.' if
35
+ fail ArgumentError, 'S Limit must be 1 or greater.' if
36
36
  s_limit.nil? || s_limit < 1
37
- fail ArgumentError 'Q Limit must be 1 or greater.' if
37
+ fail ArgumentError, 'Q Limit must be 1 or greater.' if
38
38
  q_limit.nil? || q_limit < 1
39
39
 
40
40
  @s_limit = s_limit
@@ -106,47 +106,50 @@ module CacheLib
106
106
  end
107
107
 
108
108
  def inspect
109
- "#{self.class} with a limit of #{@limit}, "\
110
- "s_limit of #{@s_limit} and q_limit of #{@q_limit} "\
111
- "currently caching #{@cache.size} items."
109
+ "#{self.class}, "\
110
+ "Limit: #{@limit}, "\
111
+ "Stack Limit: #{@s_limit}, "\
112
+ "Queue Limit: #{@q_limit}, "\
113
+ "Size: #{@cache.size}"
112
114
  end
113
115
 
114
116
  alias_method :[], :lookup
115
117
  alias_method :[]=, :store
118
+ alias_method :delete, :evict
116
119
 
117
120
  protected
118
121
 
119
122
  def trim_stack
120
- s_tail = @stack.tail
121
- while @queue.key?(s_tail) || !@cache.key?(s_tail)
122
- @stack.delete(s_tail)
123
- s_tail = @stack.tail
123
+ key, _ = @stack.tail
124
+ while key && (@queue.key?(key) || !@cache.key?(key))
125
+ @stack.delete(key)
126
+ key, _ = @stack.tail
124
127
  end
125
128
  end
126
129
 
127
130
  def promote_hir
128
- key = @queue.head
131
+ key = @queue.head_key
129
132
 
130
133
  @stack.set_tail(key, nil) unless @stack.key?(key)
131
134
  @queue.delete(key)
132
135
  end
133
136
 
134
137
  def pop_tail
135
- key = @queue.tail
138
+ key, _ = @queue.tail
136
139
 
137
140
  @queue.delete(key)
138
141
  @cache.delete(key)
139
142
  end
140
143
 
141
144
  def pop_stack
142
- key = @stack.tail
145
+ key, _ = @stack.tail
143
146
 
144
147
  @cache.delete(key)
145
148
  trim_stack
146
149
  end
147
150
 
148
151
  def demote_lir
149
- key = @stack.tail
152
+ key, _ = @stack.tail
150
153
 
151
154
  @stack.delete(key)
152
155
  @queue.set_head(key, nil)
@@ -154,11 +157,7 @@ module CacheLib
154
157
  end
155
158
 
156
159
  def resize
157
- s_size = 0
158
-
159
- @stack.each do |key, _|
160
- s_size += 1 if @cache.key?(key) && !@queue.key?(key)
161
- end
160
+ s_size = @stack.count { |key, _| @cache.key?(key) && !@queue.key?(key) }
162
161
 
163
162
  promote_hir while (s_size < @s_limit && @queue.size > 0) && s_size += 1
164
163
  pop_tail while @queue.size > 0 && @cache.size > @limit
@@ -176,7 +175,7 @@ module CacheLib
176
175
 
177
176
  demote_lir
178
177
  else
179
- s_tail_key = @stack.tail
178
+ s_tail_key, _ = @stack.tail
180
179
  @stack.refresh(key)
181
180
 
182
181
  trim_stack if s_tail_key == key
@@ -3,7 +3,7 @@ module CacheLib
3
3
  def initialize(*args)
4
4
  limit, _ = args
5
5
 
6
- fail ArgumentError "Cache Limit must be 1 or greater: #{limit}" if
6
+ fail ArgumentError, "Cache Limit must be 1 or greater: #{limit}" if
7
7
  limit.nil? || limit < 1
8
8
 
9
9
  @limit = limit
@@ -14,7 +14,9 @@ module CacheLib
14
14
  def limit=(args)
15
15
  limit, _ = args
16
16
 
17
- fail ArgumentError "Cache Limit must be 1 or greater: #{limit}" if
17
+ limit ||= @limit
18
+
19
+ fail ArgumentError, "Cache Limit must be 1 or greater: #{limit}" if
18
20
  limit.nil? || limit < 1
19
21
 
20
22
  @limit = limit
@@ -53,13 +55,9 @@ module CacheLib
53
55
  end
54
56
  end
55
57
 
56
- def inspect
57
- "#{self.class} with a limit of #{@limit} "\
58
- "currently caching #{@cache.size} items."
59
- end
60
-
61
58
  alias_method :[], :lookup
62
59
  alias_method :[]=, :store
60
+ alias_method :delete, :evict
63
61
 
64
62
  protected
65
63
 
@@ -56,6 +56,12 @@ module CacheLib
56
56
  end
57
57
  end
58
58
 
59
+ def expire
60
+ synchronize do
61
+ super
62
+ end
63
+ end
64
+
59
65
  def each
60
66
  synchronize do
61
67
  super
@@ -94,5 +100,6 @@ module CacheLib
94
100
 
95
101
  alias_method :[], :lookup
96
102
  alias_method :[]=, :store
103
+ alias_method :delete, :evict
97
104
  end
98
105
  end
@@ -0,0 +1,7 @@
1
+ require_relative 'safe_sync'
2
+
3
+ module CacheLib
4
+ class SafeTtlCache < TtlCache
5
+ include SafeSync
6
+ end
7
+ end
@@ -0,0 +1,150 @@
1
+ module CacheLib
2
+ class TtlCache < BasicCache
3
+ def initialize(*args)
4
+ limit, ttl = args
5
+
6
+ fail ArgumentError, "Cache Limit must be 1 or greater: #{limit}" if
7
+ limit.nil? || limit < 1
8
+ fail ArgumentError, "TTL must be :none, 0 or greater: #{ttl}" unless
9
+ ttl == :none || ((ttl.is_a? Numeric) && ttl >= 0)
10
+
11
+ @limit = limit
12
+ @ttl = ttl
13
+
14
+ @cache = UtilHash.new
15
+ @queue = UtilHash.new
16
+ end
17
+
18
+ def initialize_copy(source)
19
+ source_raw = source.raw
20
+
21
+ @limit = source_raw[:limit]
22
+
23
+ @cache = source_raw[:cache]
24
+ @queue = source_raw[:queue]
25
+ end
26
+
27
+ def limit=(args)
28
+ limit, ttl = args
29
+
30
+ limit ||= @limit
31
+ ttl ||= @ttl
32
+
33
+ fail ArgumentError, "Cache Limit must be 1 or greater: #{limit}" if
34
+ limit.nil? || limit < 1
35
+ fail ArgumentError, "TTL must be :none, 0 or greater: #{ttl}" unless
36
+ ttl == :none || ((ttl.is_a? Numeric) && ttl >= 0)
37
+
38
+ @limit = limit
39
+ @ttl = ttl
40
+
41
+ resize
42
+ end
43
+
44
+ def get(key)
45
+ if hit?(key)
46
+ hit(key)
47
+ else
48
+ miss(key, yield)
49
+ end
50
+ end
51
+
52
+ def store(key, value)
53
+ ttl_evict
54
+
55
+ @cache.delete(key)
56
+ @queue.delete(key)
57
+
58
+ miss(key, value)
59
+ end
60
+
61
+ def lookup(key)
62
+ hit(key) if hit?(key)
63
+ end
64
+
65
+ def fetch(key)
66
+ if hit?(key)
67
+ hit(key)
68
+ else
69
+ yield if block_given?
70
+ end
71
+ end
72
+
73
+ def evict(key)
74
+ @queue.delete(key)
75
+ @cache.delete(key)
76
+ end
77
+
78
+ def expire
79
+ ttl_evict
80
+ end
81
+
82
+ def raw
83
+ { limit: @limit,
84
+ cache: @cache.clone,
85
+ queue: @queue.clone }
86
+ end
87
+
88
+ def inspect
89
+ "#{self.class}, "\
90
+ "Limit: #{@limit}, "\
91
+ "TTL: #{@ttl}, "\
92
+ "Size: #{@cache.size}"
93
+ end
94
+
95
+ alias_method :[], :lookup
96
+ alias_method :[]=, :store
97
+ alias_method :delete, :evict
98
+
99
+ protected
100
+
101
+ def ttl_evict
102
+ return if @ttl == :none
103
+
104
+ ttl_horizon = Time.now - @ttl
105
+ key, time = @queue.tail
106
+
107
+ until time.nil? || time > ttl_horizon
108
+ @queue.delete(key)
109
+ @cache.delete(key)
110
+
111
+ key, time = @queue.tail
112
+ end
113
+ end
114
+
115
+ def resize
116
+ ttl_evict
117
+
118
+ while @cache.size > @limit
119
+ key, _ = @cache.tail
120
+
121
+ @queue.delete(key)
122
+ @cache.delete(key)
123
+ end
124
+ end
125
+
126
+ def hit?(key)
127
+ ttl_evict
128
+
129
+ @cache.key?(key)
130
+ end
131
+
132
+ def hit(key)
133
+ @cache.refresh(key)
134
+ end
135
+
136
+ def miss(key, value)
137
+ @cache[key] = value
138
+ @queue[key] = Time.now
139
+
140
+ while @cache.size > @limit
141
+ key, _ = @cache.tail
142
+
143
+ @queue.delete(key)
144
+ @cache.delete(key)
145
+ end
146
+
147
+ value
148
+ end
149
+ end
150
+ end
@@ -1,11 +1,11 @@
1
1
  module CacheLib
2
2
  class UtilHash < Hash
3
- def head
3
+ def head_key
4
4
  keys.last
5
5
  end
6
6
 
7
7
  def tail
8
- first[0]
8
+ first
9
9
  end
10
10
 
11
11
  def set_head(key, value)
@@ -18,7 +18,7 @@ module CacheLib
18
18
  end
19
19
 
20
20
  def pop_tail
21
- delete(tail)
21
+ delete(first[0])
22
22
  end
23
23
 
24
24
  def refresh(key)
@@ -1,3 +1,3 @@
1
1
  module CacheLib
2
- VERSION = '0.0.1'
2
+ VERSION = '0.1.0'
3
3
  end
@@ -1,5 +1,5 @@
1
1
  require 'cache_lib'
2
- require 'minitest/autorun'
2
+ require 'minitest'
3
3
 
4
4
  class TestBasicCache < MiniTest::Test
5
5
  def setup
@@ -63,6 +63,11 @@ class TestBasicCache < MiniTest::Test
63
63
  assert_equal nil, @cache.evict(:z)
64
64
 
65
65
  assert_equal nil, @cache.lookup(:a)
66
+
67
+ assert_equal 2, @cache.delete(:b)
68
+ assert_equal nil, @cache.delete(:z)
69
+
70
+ assert_equal nil, @cache.lookup(:b)
66
71
  end
67
72
 
68
73
  def test_clear
@@ -125,7 +130,7 @@ class TestBasicCache < MiniTest::Test
125
130
  @cache.store(:a, 1)
126
131
  @cache.store(:b, 2)
127
132
 
128
- assert_equal "#{@cache.class} currently caching 2 items.",
133
+ assert_equal "#{@cache.class}, Limit: , Size: 2",
129
134
  @cache.inspect
130
135
  end
131
136
  end
@@ -1,6 +1,3 @@
1
- require 'cache_lib'
2
- require 'minitest/autorun'
3
-
4
1
  require_relative 'test_basic_cache'
5
2
 
6
3
  class TestFifoCache < TestBasicCache
@@ -22,8 +19,7 @@ class TestFifoCache < TestBasicCache
22
19
  @cache.store(:a, 1)
23
20
  @cache.store(:b, 2)
24
21
 
25
- assert_equal "#{@cache.class} with a limit of 5 "\
26
- "currently caching 2 items.",
22
+ assert_equal "#{@cache.class}, Limit: 5, Size: 2",
27
23
  @cache.inspect
28
24
  end
29
25
 
@@ -1,6 +1,3 @@
1
- require 'cache_lib'
2
- require 'minitest/autorun'
3
-
4
1
  require_relative 'test_basic_cache'
5
2
 
6
3
  class TestLirsCache < TestBasicCache
@@ -22,9 +19,8 @@ class TestLirsCache < TestBasicCache
22
19
  @cache.store(:a, 1)
23
20
  @cache.store(:b, 2)
24
21
 
25
- assert_equal "#{@cache.class} with a limit of 5, "\
26
- "s_limit of 3 and q_limit of 2 "\
27
- "currently caching 2 items.",
22
+ assert_equal "#{@cache.class}, Limit: 5, Stack Limit: 3, "\
23
+ 'Queue Limit: 2, Size: 2',
28
24
  @cache.inspect
29
25
  end
30
26
 
@@ -1,6 +1,3 @@
1
- require 'cache_lib'
2
- require 'minitest/autorun'
3
-
4
1
  require_relative 'test_basic_cache'
5
2
 
6
3
  class TestLruCache < TestBasicCache
@@ -22,8 +19,7 @@ class TestLruCache < TestBasicCache
22
19
  @cache.store(:a, 1)
23
20
  @cache.store(:b, 2)
24
21
 
25
- assert_equal "#{@cache.class} with a limit of 5 "\
26
- "currently caching 2 items.",
22
+ assert_equal "#{@cache.class}, Limit: 5, Size: 2",
27
23
  @cache.inspect
28
24
  end
29
25
 
@@ -0,0 +1,14 @@
1
+ require 'timecop'
2
+
3
+ require_relative 'test_ttl_cache'
4
+
5
+ class TestSafeTtlCache < TestTtlCache
6
+ def setup
7
+ Timecop.freeze(Time.now)
8
+ @cache = CacheLib.safe_create :ttl, 5, 5 * 60
9
+ end
10
+
11
+ def teardown
12
+ Timecop.return
13
+ end
14
+ end
@@ -0,0 +1,143 @@
1
+ require 'timecop'
2
+
3
+ require_relative 'test_basic_cache'
4
+
5
+ class TestTtlCache < TestBasicCache
6
+ def setup
7
+ Timecop.freeze(Time.now)
8
+ @cache = CacheLib.create :ttl, 5, 5 * 60
9
+ end
10
+
11
+ def teardown
12
+ Timecop.return
13
+ end
14
+
15
+ def test_limit
16
+ assert_equal 5, @cache.limit
17
+ end
18
+
19
+ def test_set_limit
20
+ @cache.limit = 90
21
+
22
+ assert_equal 90, @cache.limit
23
+ end
24
+
25
+ def test_inspect
26
+ @cache.store(:a, 1)
27
+ @cache.store(:b, 2)
28
+
29
+ assert_equal "#{@cache.class}, Limit: 5, TTL: 300, Size: 2",
30
+ @cache.inspect
31
+ end
32
+
33
+ def test_lru_promotion
34
+ (1..5).each { |i| @cache.store(i, i) }
35
+ @cache.lookup(3)
36
+ @cache.lookup(1)
37
+
38
+ assert_equal({ 2 => 2, 4 => 4, 5 => 5, 3 => 3, 1 => 1 },
39
+ @cache.raw[:cache])
40
+ end
41
+
42
+ def test_lru_eviction
43
+ (1..5).each { |i| @cache.store(i, i) }
44
+ @cache.lookup(3)
45
+ @cache.lookup(1)
46
+ @cache.store(6, 6)
47
+
48
+ assert_equal 5, @cache.size
49
+ assert_equal({ 4 => 4, 5 => 5, 3 => 3, 1 => 1, 6 => 6 },
50
+ @cache.raw[:cache])
51
+ end
52
+
53
+ def test_upsize
54
+ (1..5).each { |i| @cache.store(i, i) }
55
+ @cache.limit = 7
56
+ (6..8).each { |i| @cache.store(i, i) }
57
+
58
+ assert_equal 7, @cache.size
59
+ assert_equal({ 2 => 2, 3 => 3, 4 => 4, 5 => 5,
60
+ 6 => 6, 7 => 7, 8 => 8 },
61
+ @cache.raw[:cache])
62
+ end
63
+
64
+ def test_downsize
65
+ (1..5).each { |i| @cache.store(i, i) }
66
+ @cache.limit = 3
67
+
68
+ assert_equal 3, @cache.size
69
+ assert_equal({ 3 => 3, 4 => 4, 5 => 5 }, @cache.raw[:cache])
70
+ end
71
+
72
+ # TTL tests using Timecop
73
+ def test_ttl_eviction_on_access
74
+ @cache.store(:a, 1)
75
+ @cache.store(:b, 2)
76
+
77
+ Timecop.freeze(Time.now + 330)
78
+
79
+ @cache.store(:c, 3)
80
+
81
+ assert_equal({ :c => 3 }, @cache.raw[:cache])
82
+ end
83
+
84
+ def test_ttl_eviction_on_expire
85
+ @cache.store(:a, 1)
86
+ @cache.store(:b, 2)
87
+
88
+ Timecop.freeze(Time.now + 330)
89
+
90
+ @cache.expire
91
+
92
+ assert_equal({}, @cache.raw[:cache])
93
+ end
94
+
95
+ def test_ttl_eviction_on_new_limit
96
+ @cache.store(:a, 1)
97
+ @cache.store(:b, 2)
98
+
99
+ Timecop.freeze(Time.now + 330)
100
+
101
+ @cache.limit = 10
102
+
103
+ assert_equal({}, @cache.raw[:cache])
104
+ end
105
+
106
+ def test_ttl_eviction_on_new_ttl
107
+ @cache.store(:a, 1)
108
+ @cache.store(:b, 2)
109
+
110
+ Timecop.freeze(Time.now + 330)
111
+
112
+ @cache.limit = nil, 10 * 60
113
+
114
+ assert_equal({ :a => 1, :b => 2 }, @cache.raw[:cache])
115
+
116
+ @cache.limit = nil, 2 * 60
117
+
118
+ assert_equal({}, @cache.raw[:cache])
119
+ end
120
+
121
+ def test_ttl_precedence_over_lru
122
+ @cache.store(:a, 1)
123
+
124
+ Timecop.freeze(Time.now + 60)
125
+
126
+ @cache.store(:b, 2)
127
+ @cache.store(:c, 3)
128
+ @cache.store(:d, 4)
129
+ @cache.store(:e, 5)
130
+
131
+ @cache.lookup(:a)
132
+
133
+ assert_equal [[:a, 1], [:e, 5], [:d, 4], [:c, 3], [:b, 2]],
134
+ @cache.to_a
135
+
136
+ Timecop.freeze(Time.now + 270)
137
+
138
+ @cache.store(:f, 6)
139
+
140
+ assert_equal [[:f, 6], [:e, 5], [:d, 4], [:c, 3], [:b, 2]],
141
+ @cache.to_a
142
+ end
143
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cache_lib
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kaijah Hougham
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-02-18 00:00:00.000000000 Z
11
+ date: 2015-02-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -44,23 +44,38 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '5.0'
47
+ version: '5.5'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '5.0'
55
- description: A Ruby caching library implementing Basic, FIFO, LRU and LIRS caching
56
- strategies.
54
+ version: '5.5'
55
+ - !ruby/object:Gem::Dependency
56
+ name: timecop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.7'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.7'
69
+ description: A Ruby caching library implementing Basic, FIFO, LRU, TTL and LIRS caches.
57
70
  email: github@seberius.com
58
71
  executables: []
59
72
  extensions: []
60
73
  extra_rdoc_files: []
61
74
  files:
75
+ - CHANGELOG.md
62
76
  - Gemfile
63
77
  - LICENSE.txt
78
+ - NOTICE.md
64
79
  - README.md
65
80
  - Rakefile
66
81
  - cache_lib.gemspec
@@ -74,6 +89,8 @@ files:
74
89
  - lib/cache_lib/safe_lirs_cache.rb
75
90
  - lib/cache_lib/safe_lru_cache.rb
76
91
  - lib/cache_lib/safe_sync.rb
92
+ - lib/cache_lib/safe_ttl_cache.rb
93
+ - lib/cache_lib/ttl_cache.rb
77
94
  - lib/cache_lib/util_hash.rb
78
95
  - lib/cache_lib/version.rb
79
96
  - test/cache_lib/test_basic_cache.rb
@@ -85,6 +102,8 @@ files:
85
102
  - test/cache_lib/test_safe_fifo_cache.rb
86
103
  - test/cache_lib/test_safe_lirs_cache.rb
87
104
  - test/cache_lib/test_safe_lru_cache.rb
105
+ - test/cache_lib/test_safe_ttl_cache.rb
106
+ - test/cache_lib/test_ttl_cache.rb
88
107
  homepage: https://github.com/seberius/cache_lib
89
108
  licenses:
90
109
  - MIT
@@ -105,7 +124,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
105
124
  version: '0'
106
125
  requirements: []
107
126
  rubyforge_project:
108
- rubygems_version: 2.2.2
127
+ rubygems_version: 2.4.5
109
128
  signing_key:
110
129
  specification_version: 4
111
130
  summary: A Ruby Caching Library
@@ -119,3 +138,5 @@ test_files:
119
138
  - test/cache_lib/test_safe_fifo_cache.rb
120
139
  - test/cache_lib/test_safe_lirs_cache.rb
121
140
  - test/cache_lib/test_safe_lru_cache.rb
141
+ - test/cache_lib/test_safe_ttl_cache.rb
142
+ - test/cache_lib/test_ttl_cache.rb