cache_lib 0.0.1 → 0.1.0

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