sin_lru_redux 2.0.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -8,12 +8,13 @@ module LruRedux
8
8
  def initialize(*args)
9
9
  max_size, ttl, ignore_nil = args
10
10
 
11
+ max_size ||= 1000
11
12
  ttl ||= :none
12
13
  ignore_nil ||= false
13
14
 
14
- raise ArgumentError.new(:max_size) unless valid_max_size?(max_size)
15
- raise ArgumentError.new(:ttl) unless valid_ttl?(ttl)
16
- raise ArgumentError.new(:ignore_nil) unless valid_ignore_nil?(ignore_nil)
15
+ validate_max_size!(max_size)
16
+ validate_ttl!(ttl)
17
+ validate_ignore_nil!(ignore_nil)
17
18
 
18
19
  @max_size = max_size
19
20
  @ttl = ttl
@@ -22,64 +23,59 @@ module LruRedux
22
23
  @data_ttl = {}
23
24
  end
24
25
 
25
- def max_size=(max_size)
26
- max_size ||= @max_size
26
+ def max_size=(new_max_size)
27
+ new_max_size ||= @max_size
27
28
 
28
- raise ArgumentError.new(:max_size) unless valid_max_size?(max_size)
29
-
30
- @max_size = max_size
29
+ validate_max_size!(new_max_size)
31
30
 
31
+ @max_size = new_max_size
32
32
  resize
33
33
  end
34
34
 
35
- def ttl=(ttl)
36
- ttl ||= @ttl
37
- raise ArgumentError.new(:ttl) unless valid_ttl?(ttl)
35
+ def ttl=(new_ttl)
36
+ new_ttl ||= @ttl
38
37
 
39
- @ttl = ttl
38
+ validate_ttl!(new_ttl)
40
39
 
41
- ttl_evict
40
+ @ttl = new_ttl
41
+ evict_expired
42
42
  end
43
43
 
44
- def ignore_nil=(ignore_nil)
45
- ignore_nil ||= @ignore_nil
46
- raise ArgumentError.new(:ignore_nil) unless valid_ignore_nil?(ignore_nil)
44
+ def ignore_nil=(new_ignore_nil)
45
+ new_ignore_nil ||= @ignore_nil
47
46
 
48
- @ignore_nil = ignore_nil
47
+ validate_ignore_nil!(new_ignore_nil)
48
+
49
+ @ignore_nil = new_ignore_nil
50
+ evict_nil
49
51
  end
50
52
 
51
53
  def getset(key)
52
- ttl_evict
54
+ evict_expired
55
+
56
+ key_found = true
57
+ value = @data_lru.delete(key) { key_found = false }
53
58
 
54
- found = true
55
- value = @data_lru.delete(key) { found = false }
56
- if found
59
+ if key_found
60
+ @data_ttl.delete(key)
61
+ @data_ttl[key] = Time.now.to_f
57
62
  @data_lru[key] = value
58
63
  else
59
64
  result = yield
60
-
61
- if !result.nil? || !@ignore_nil
62
- @data_lru[key] = result
63
- @data_ttl[key] = Time.now.to_f
64
-
65
- if @data_lru.size > @max_size
66
- key, _ = @data_lru.first
67
-
68
- @data_ttl.delete(key)
69
- @data_lru.delete(key)
70
- end
71
- end
72
-
65
+ store_item(key, result)
73
66
  result
74
67
  end
75
68
  end
76
69
 
77
70
  def fetch(key)
78
- ttl_evict
71
+ evict_expired
79
72
 
80
- found = true
81
- value = @data_lru.delete(key) { found = false }
82
- if found
73
+ key_found = true
74
+ value = @data_lru.delete(key) { key_found = false }
75
+
76
+ if key_found
77
+ @data_ttl.delete(key)
78
+ @data_ttl[key] = Time.now.to_f
83
79
  @data_lru[key] = value
84
80
  else
85
81
  yield if block_given? # rubocop:disable Style/IfInsideElse
@@ -87,71 +83,55 @@ module LruRedux
87
83
  end
88
84
 
89
85
  def [](key)
90
- ttl_evict
86
+ evict_expired
91
87
 
92
- found = true
93
- value = @data_lru.delete(key) { found = false }
94
- @data_lru[key] = value if found
95
- end
88
+ key_found = true
89
+ value = @data_lru.delete(key) { key_found = false }
90
+ return unless key_found
96
91
 
97
- def []=(key, val)
98
- ttl_evict
99
-
100
- @data_lru.delete(key)
101
92
  @data_ttl.delete(key)
102
-
103
- @data_lru[key] = val
104
93
  @data_ttl[key] = Time.now.to_f
94
+ @data_lru[key] = value
95
+ end
105
96
 
106
- if @data_lru.size > @max_size
107
- key, _ = @data_lru.first
108
-
109
- @data_ttl.delete(key)
110
- @data_lru.delete(key)
111
- end
97
+ def []=(key, val)
98
+ evict_expired
112
99
 
113
- val # rubocop:disable Lint/Void
100
+ store_item(key, val)
114
101
  end
115
102
 
116
103
  def each(&block)
117
- ttl_evict
104
+ evict_expired
118
105
 
119
- array = @data_lru.to_a
120
- array.reverse!.each(&block)
106
+ @data_lru.to_a.reverse_each(&block)
121
107
  end
122
-
123
- # used further up the chain, non thread safe each
124
108
  alias_method :each_unsafe, :each
125
109
 
126
110
  def to_a
127
- ttl_evict
111
+ evict_expired
128
112
 
129
- array = @data_lru.to_a
130
- array.reverse!
113
+ @data_lru.to_a.reverse
131
114
  end
132
115
 
133
116
  def values
134
- ttl_evict
117
+ evict_expired
135
118
 
136
- vals = @data_lru.values
137
- vals.reverse!
119
+ @data_lru.values.reverse
138
120
  end
139
121
 
140
122
  def delete(key)
141
- ttl_evict
123
+ evict_expired
142
124
 
143
125
  @data_ttl.delete(key)
144
126
  @data_lru.delete(key)
145
127
  end
146
-
147
128
  alias_method :evict, :delete
148
129
 
149
130
  def key?(key)
150
- ttl_evict
131
+ evict_expired
151
132
 
152
133
  @data_lru.key?(key)
153
134
  end
154
-
155
135
  alias_method :has_key?, :key?
156
136
 
157
137
  def clear
@@ -159,63 +139,110 @@ module LruRedux
159
139
  @data_lru.clear
160
140
  end
161
141
 
162
- def expire
163
- ttl_evict
164
- end
165
-
166
142
  def count
167
143
  @data_lru.size
168
144
  end
169
145
 
170
- protected
146
+ def expire
147
+ evict_expired
148
+ end
171
149
 
172
- def valid_max_size?(max_size)
173
- return true if max_size.is_a?(Integer) && max_size >= 1
150
+ private
174
151
 
175
- false
152
+ # For cache validation only, ensure all is valid
153
+ def valid?
154
+ @data_lru.size == @data_ttl.size
176
155
  end
177
156
 
178
- def valid_ttl?(ttl)
179
- return true if ttl == :none
180
- return true if ttl.is_a?(Numeric) && ttl >= 0
157
+ def validate_max_size!(max_size)
158
+ unless max_size.is_a?(Numeric)
159
+ raise ArgumentError.new(<<~ERROR)
160
+ Invalid max_size: #{max_size.inspect}
161
+ max_size must be a number.
162
+ ERROR
163
+ end
164
+ return if max_size >= 1
181
165
 
182
- false
166
+ raise ArgumentError.new(<<~ERROR)
167
+ Invalid max_size: #{max_size.inspect}
168
+ max_size must be greater than or equal to 1.
169
+ ERROR
183
170
  end
184
171
 
185
- def valid_ignore_nil?(ignore_nil)
186
- return true if [true, false].include?(ignore_nil)
172
+ def validate_ttl!(ttl)
173
+ return if ttl == :none
174
+
175
+ unless ttl.is_a?(Numeric)
176
+ raise ArgumentError.new(<<~ERROR)
177
+ Invalid ttl: #{ttl.inspect}
178
+ ttl must be a number.
179
+ ERROR
180
+ end
181
+ return if ttl >= 0
187
182
 
188
- false
183
+ raise ArgumentError.new(<<~ERROR)
184
+ Invalid ttl: #{ttl.inspect}
185
+ ttl must be greater than or equal to 0.
186
+ ERROR
189
187
  end
190
188
 
191
- # for cache validation only, ensures all is sound
192
- def valid?
193
- @data_lru.size == @data_ttl.size
189
+ def validate_ignore_nil!(ignore_nil)
190
+ return if [true, false].include?(ignore_nil)
191
+
192
+ raise ArgumentError.new("Invalid ignore_nil: #{ignore_nil.inspect}")
193
+ end
194
+
195
+ def resize
196
+ evict_expired
197
+ evict_if_exceeded
194
198
  end
195
199
 
196
- def ttl_evict
200
+ def evict_expired
197
201
  return if @ttl == :none
198
202
 
199
- ttl_horizon = Time.now.to_f - @ttl
203
+ expiration_threshold = Time.now.to_f - @ttl
200
204
  key, time = @data_ttl.first
201
-
202
- until time.nil? || time > ttl_horizon
203
- @data_ttl.delete(key)
205
+ until time.nil? || time > expiration_threshold
204
206
  @data_lru.delete(key)
205
-
207
+ @data_ttl.delete(key)
206
208
  key, time = @data_ttl.first
207
209
  end
208
210
  end
209
211
 
210
- def resize
211
- ttl_evict
212
+ def evict_if_exceeded
213
+ evict_least_recently_used while @data_lru.size > @max_size
214
+ end
212
215
 
213
- while @data_lru.size > @max_size
214
- key, _ = @data_lru.first
216
+ def evict_least_recently_used
217
+ return if @data_lru.empty?
215
218
 
216
- @data_ttl.delete(key)
217
- @data_lru.delete(key)
219
+ oldest_key, _value = @data_lru.first
220
+ @data_lru.delete(oldest_key)
221
+ @data_ttl.delete(oldest_key)
222
+ end
223
+
224
+ def evict_nil
225
+ return unless @ignore_nil
226
+
227
+ @data_lru.reject! do |key, value|
228
+ if value.nil?
229
+ @data_ttl.delete(key)
230
+ true
231
+ else
232
+ false
233
+ end
234
+ end
235
+ end
236
+
237
+ def store_item(key, value)
238
+ @data_lru.delete(key)
239
+ @data_ttl.delete(key)
240
+ if !value.nil? || !@ignore_nil
241
+ @data_lru[key] = value
242
+ @data_ttl[key] = Time.now.to_f
218
243
  end
244
+ evict_if_exceeded
245
+ value
219
246
  end
220
247
  end
221
248
  end
@@ -107,6 +107,8 @@ module LruRedux
107
107
  end
108
108
  end
109
109
 
110
+ private
111
+
110
112
  def valid?
111
113
  synchronize do
112
114
  super
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LruRedux
4
- VERSION = '2.0.0'
4
+ VERSION = '2.1.0'
5
5
  end
metadata CHANGED
@@ -1,32 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sin_lru_redux
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Masahiro
8
- bindir: bin
8
+ bindir: exe
9
9
  cert_chain: []
10
- date: 2024-12-28 00:00:00.000000000 Z
10
+ date: 2024-12-30 00:00:00.000000000 Z
11
11
  dependencies: []
12
- description: An efficient implementation of an lru cache
12
+ description: |
13
+ Efficient and thread-safe LRU cache.
14
+ Forked from LruRedux.
13
15
  email:
14
16
  - watanabe@cadenza-tech.com
15
17
  executables: []
16
18
  extensions: []
17
19
  extra_rdoc_files: []
18
20
  files:
19
- - ".github/workflows/lint.yml"
20
- - ".github/workflows/test.yml"
21
- - ".gitignore"
22
21
  - ".rubocop.yml"
23
- - Gemfile
24
- - Guardfile
22
+ - CHANGELOG.md
23
+ - CODE_OF_CONDUCT.md
25
24
  - LICENSE.txt
26
25
  - README.md
27
26
  - Rakefile
28
- - bench/bench.rb
29
- - bench/bench_ttl.rb
30
27
  - lib/lru_redux.rb
31
28
  - lib/lru_redux/cache.rb
32
29
  - lib/lru_redux/thread_safe_cache.rb
@@ -36,20 +33,15 @@ files:
36
33
  - lib/lru_redux/util.rb
37
34
  - lib/lru_redux/util/safe_sync.rb
38
35
  - lib/lru_redux/version.rb
39
- - sin_lru_redux.gemspec
40
- - test/cache_test.rb
41
- - test/thread_safe_cache_test.rb
42
- - test/ttl/cache_test.rb
43
- - test/ttl/thread_safe_cache_test.rb
44
- homepage: https://github.com/cadenza-tech/sin_lru_redux/tree/2.0.0/sin_lru_redux
36
+ homepage: https://github.com/cadenza-tech/sin_lru_redux/tree/v2.1.0
45
37
  licenses:
46
38
  - MIT
47
39
  metadata:
48
- homepage_uri: https://github.com/cadenza-tech/sin_lru_redux/tree/2.0.0/sin_lru_redux
49
- source_code_uri: https://github.com/cadenza-tech/sin_lru_redux/tree/2.0.0/sin_lru_redux
50
- changelog_uri: https://github.com/cadenza-tech/sin_lru_redux/blob/2.0.0#changelog
40
+ homepage_uri: https://github.com/cadenza-tech/sin_lru_redux/tree/v2.1.0
41
+ source_code_uri: https://github.com/cadenza-tech/sin_lru_redux/tree/v2.1.0
42
+ changelog_uri: https://github.com/cadenza-tech/sin_lru_redux/blob/v2.1.0/CHANGELOG.md
51
43
  bug_tracker_uri: https://github.com/cadenza-tech/sin_lru_redux/issues
52
- documentation_uri: https://rubydoc.info/gems/sin_lru_redux/2.0.0
44
+ documentation_uri: https://rubydoc.info/gems/sin_lru_redux/2.1.0
53
45
  rubygems_mfa_required: 'true'
54
46
  rdoc_options: []
55
47
  require_paths:
@@ -67,5 +59,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
67
59
  requirements: []
68
60
  rubygems_version: 3.6.2
69
61
  specification_version: 4
70
- summary: An efficient implementation of an lru cache
62
+ summary: Efficient and thread-safe LRU cache.
71
63
  test_files: []
@@ -1,18 +0,0 @@
1
- name: Lint
2
- on:
3
- pull_request:
4
- paths:
5
- - '**/*.rb'
6
- permissions:
7
- contents: read
8
- jobs:
9
- lint:
10
- runs-on: ubuntu-latest
11
- steps:
12
- - uses: actions/checkout@v4
13
- - name: Set up Ruby
14
- uses: ruby/setup-ruby@v1
15
- with:
16
- bundler-cache: true
17
- - name: RuboCop
18
- run: bundle exec rake rubocop
@@ -1,21 +0,0 @@
1
- name: Test
2
- on:
3
- pull_request:
4
- permissions:
5
- contents: read
6
- jobs:
7
- test:
8
- runs-on: ubuntu-latest
9
- strategy:
10
- fail-fast: false
11
- matrix:
12
- ruby-version: [2.3, 2.4, 2.5, 2.6, 2.7, 3.0, 3.1, 3.2, 3.3, 3.4]
13
- steps:
14
- - uses: actions/checkout@v4
15
- - name: Set up Ruby
16
- uses: ruby/setup-ruby@v1
17
- with:
18
- ruby-version: ${{ matrix.ruby-version }}
19
- bundler-cache: true
20
- - name: Run tests
21
- run: bundle exec rake test
data/.gitignore DELETED
@@ -1,19 +0,0 @@
1
- *.gem
2
- *.rbc
3
- *.swp
4
- .bundle
5
- .config
6
- .vscode
7
- .yardoc
8
- _yardoc
9
- coverage
10
- doc/
11
- Gemfile.lock
12
- InstalledFiles
13
- lib/bundler/man
14
- pkg
15
- rdoc
16
- spec/reports
17
- test/tmp
18
- test/version_tmp
19
- tmp
data/Gemfile DELETED
@@ -1,19 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source 'https://rubygems.org'
4
-
5
- gemspec
6
-
7
- group :development, :test do
8
- gem 'bundler'
9
- gem 'guard'
10
- gem 'guard-minitest'
11
- gem 'minitest'
12
- gem 'rake'
13
- gem 'rb-inotify'
14
- gem 'rubocop'
15
- gem 'rubocop-minitest'
16
- gem 'rubocop-performance'
17
- gem 'rubocop-rake'
18
- gem 'timecop'
19
- end
data/Guardfile DELETED
@@ -1,6 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- guard 'minitest', focus_on_failed: true do
4
- watch(%r{^test/.+_test\.rb$})
5
- watch(%r{^lib/lru_redux/(.+)\.rb$}) { |m| "test/#{m[1]}_test.rb" }
6
- end
data/bench/bench.rb DELETED
@@ -1,46 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'bundler'
4
- require 'benchmark'
5
- require 'lru'
6
- require 'lru_cache'
7
- require 'threadsafe-lru'
8
-
9
- Bundler.require
10
-
11
- # Lru
12
- lru = Cache::LRU.new(max_elements: 1_000)
13
-
14
- # LruCache
15
- lru_cache = LRUCache.new(1_000)
16
-
17
- # ThreadSafeLru
18
- thread_safe_lru = ThreadSafeLru::LruCache.new(1_000)
19
-
20
- # LruRedux
21
- redux = LruRedux::Cache.new(1_000)
22
- redux_thread_safe = LruRedux::ThreadSafeCache.new(1_000)
23
-
24
- puts '** LRU Benchmarks **'
25
-
26
- Benchmark.bmbm do |bm|
27
- bm.report 'ThreadSafeLru' do
28
- 1_000_000.times { thread_safe_lru.get(rand(2_000)) { :value } }
29
- end
30
-
31
- bm.report 'LRU' do
32
- 1_000_000.times { lru[rand(2_000)] ||= :value }
33
- end
34
-
35
- bm.report 'LRUCache' do
36
- 1_000_000.times { lru_cache[rand(2_000)] ||= :value }
37
- end
38
-
39
- bm.report 'LruRedux::Cache' do
40
- 1_000_000.times { redux.getset(rand(2_000)) { :value } }
41
- end
42
-
43
- bm.report 'LruRedux::ThreadSafeCache' do
44
- 1_000_000.times { redux_thread_safe.getset(rand(2_000)) { :value } }
45
- end
46
- end
data/bench/bench_ttl.rb DELETED
@@ -1,35 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'bundler'
4
- require 'benchmark'
5
- require 'fast_cache'
6
-
7
- Bundler.require
8
-
9
- # FastCache
10
- fast_cache = FastCache::Cache.new(1_000, 5 * 60)
11
-
12
- # LruRedux
13
- redux_ttl = LruRedux::TTL::Cache.new(1_000, 5 * 60)
14
- redux_ttl_thread_safe = LruRedux::TTL::ThreadSafeCache.new(1_000, 5 * 60)
15
- redux_ttl_disabled = LruRedux::TTL::Cache.new(1_000, :none)
16
-
17
- puts '** TTL Benchmarks **'
18
-
19
- Benchmark.bmbm do |bm|
20
- bm.report 'FastCache' do
21
- 1_000_000.times { fast_cache.fetch(rand(2_000)) { :value } } # rubocop:disable Style/RedundantFetchBlock
22
- end
23
-
24
- bm.report 'LruRedux::TTL::Cache' do
25
- 1_000_000.times { redux_ttl.getset(rand(2_000)) { :value } }
26
- end
27
-
28
- bm.report 'LruRedux::TTL::ThreadSafeCache' do
29
- 1_000_000.times { redux_ttl_thread_safe.getset(rand(2_000)) { :value } }
30
- end
31
-
32
- bm.report 'LruRedux::TTL::Cache (TTL disabled)' do
33
- 1_000_000.times { redux_ttl_disabled.getset(rand(2_000)) { :value } }
34
- end
35
- end
@@ -1,32 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- lib = File.expand_path('lib', __dir__)
4
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
- require 'lru_redux/version'
6
-
7
- Gem::Specification.new do |spec|
8
- spec.name = 'sin_lru_redux'
9
- spec.version = LruRedux::VERSION
10
- spec.description = 'An efficient implementation of an lru cache'
11
- spec.summary = 'An efficient implementation of an lru cache'
12
- spec.authors = ['Masahiro']
13
- spec.email = ['watanabe@cadenza-tech.com']
14
- spec.license = 'MIT'
15
-
16
- github_root_uri = 'https://github.com/cadenza-tech/sin_lru_redux'
17
- spec.homepage = "#{github_root_uri}/tree/#{spec.version}/#{spec.name}"
18
- spec.metadata = {
19
- 'homepage_uri' => spec.homepage,
20
- 'source_code_uri' => spec.homepage,
21
- 'changelog_uri' => "#{github_root_uri}/blob/#{spec.version}#changelog",
22
- 'bug_tracker_uri' => "#{github_root_uri}/issues",
23
- 'documentation_uri' => "https://rubydoc.info/gems/#{spec.name}/#{spec.version}",
24
- 'rubygems_mfa_required' => 'true'
25
- }
26
-
27
- spec.required_ruby_version = '>= 2.3.0'
28
-
29
- spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
30
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
31
- spec.require_paths = ['lib']
32
- end