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 +4 -4
- data/CHANGELOG.md +8 -0
- data/NOTICE.md +31 -0
- data/README.md +60 -9
- data/cache_lib.gemspec +3 -2
- data/lib/cache_lib.rb +4 -0
- data/lib/cache_lib/basic_cache.rb +11 -3
- data/lib/cache_lib/fifo_cache.rb +7 -8
- data/lib/cache_lib/lirs_cache.rb +20 -21
- data/lib/cache_lib/lru_cache.rb +5 -7
- data/lib/cache_lib/safe_sync.rb +7 -0
- data/lib/cache_lib/safe_ttl_cache.rb +7 -0
- data/lib/cache_lib/ttl_cache.rb +150 -0
- data/lib/cache_lib/util_hash.rb +3 -3
- data/lib/cache_lib/version.rb +1 -1
- data/test/cache_lib/test_basic_cache.rb +7 -2
- data/test/cache_lib/test_fifo_cache.rb +1 -5
- data/test/cache_lib/test_lirs_cache.rb +2 -6
- data/test/cache_lib/test_lru_cache.rb +1 -5
- data/test/cache_lib/test_safe_ttl_cache.rb +14 -0
- data/test/cache_lib/test_ttl_cache.rb +143 -0
- metadata +28 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 04ef9e417311a3650f36194439a2a1a131a15079
|
4
|
+
data.tar.gz: dc0a81c4f79f2cd9da54ab117b304cf0a07fb549
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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.
|
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.
|
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,
|
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.
|
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.
|
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
|
-
##
|
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
|
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.
|
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
|
-
|
24
|
+
has_key = true
|
25
|
+
value = @cache.fetch(key) { has_key = false }
|
25
26
|
|
26
|
-
if
|
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}
|
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
|
|
data/lib/cache_lib/fifo_cache.rb
CHANGED
@@ -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
|
-
|
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.
|
34
|
+
@cache.pop_tail while @cache.size > @limit
|
36
35
|
end
|
37
36
|
|
38
37
|
def miss(key, value)
|
data/lib/cache_lib/lirs_cache.rb
CHANGED
@@ -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}
|
110
|
-
"
|
111
|
-
"
|
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
|
-
|
121
|
-
while @queue.key?(
|
122
|
-
@stack.delete(
|
123
|
-
|
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.
|
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 =
|
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
|
data/lib/cache_lib/lru_cache.rb
CHANGED
@@ -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
|
-
|
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
|
|
data/lib/cache_lib/safe_sync.rb
CHANGED
@@ -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,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
|
data/lib/cache_lib/util_hash.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
module CacheLib
|
2
2
|
class UtilHash < Hash
|
3
|
-
def
|
3
|
+
def head_key
|
4
4
|
keys.last
|
5
5
|
end
|
6
6
|
|
7
7
|
def tail
|
8
|
-
first
|
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(
|
21
|
+
delete(first[0])
|
22
22
|
end
|
23
23
|
|
24
24
|
def refresh(key)
|
data/lib/cache_lib/version.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'cache_lib'
|
2
|
-
require 'minitest
|
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}
|
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}
|
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}
|
26
|
-
|
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}
|
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,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
|
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-
|
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.
|
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.
|
55
|
-
|
56
|
-
|
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.
|
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
|