sin_lru_redux 2.0.1 → 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,65 +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
149
+
150
+ private
171
151
 
172
- # for cache validation only, ensures all is sound
152
+ # For cache validation only, ensure all is valid
173
153
  def valid?
174
154
  @data_lru.size == @data_ttl.size
175
155
  end
176
156
 
177
- def ttl_evict
178
- return if @ttl == :none
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
179
165
 
180
- ttl_horizon = Time.now.to_f - @ttl
181
- key, time = @data_ttl.first
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
170
+ end
182
171
 
183
- until time.nil? || time > ttl_horizon
184
- @data_ttl.delete(key)
185
- @data_lru.delete(key)
172
+ def validate_ttl!(ttl)
173
+ return if ttl == :none
186
174
 
187
- key, time = @data_ttl.first
175
+ unless ttl.is_a?(Numeric)
176
+ raise ArgumentError.new(<<~ERROR)
177
+ Invalid ttl: #{ttl.inspect}
178
+ ttl must be a number.
179
+ ERROR
188
180
  end
181
+ return if ttl >= 0
182
+
183
+ raise ArgumentError.new(<<~ERROR)
184
+ Invalid ttl: #{ttl.inspect}
185
+ ttl must be greater than or equal to 0.
186
+ ERROR
187
+ end
188
+
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}")
189
193
  end
190
194
 
191
195
  def resize
192
- ttl_evict
196
+ evict_expired
197
+ evict_if_exceeded
198
+ end
193
199
 
194
- while @data_lru.size > @max_size
195
- key, _ = @data_lru.first
200
+ def evict_expired
201
+ return if @ttl == :none
196
202
 
197
- @data_ttl.delete(key)
203
+ expiration_threshold = Time.now.to_f - @ttl
204
+ key, time = @data_ttl.first
205
+ until time.nil? || time > expiration_threshold
198
206
  @data_lru.delete(key)
207
+ @data_ttl.delete(key)
208
+ key, time = @data_ttl.first
199
209
  end
200
210
  end
201
211
 
202
- private
212
+ def evict_if_exceeded
213
+ evict_least_recently_used while @data_lru.size > @max_size
214
+ end
203
215
 
204
- def valid_max_size?(max_size)
205
- return true if max_size.is_a?(Integer) && max_size >= 1
216
+ def evict_least_recently_used
217
+ return if @data_lru.empty?
206
218
 
207
- false
219
+ oldest_key, _value = @data_lru.first
220
+ @data_lru.delete(oldest_key)
221
+ @data_ttl.delete(oldest_key)
208
222
  end
209
223
 
210
- def valid_ttl?(ttl)
211
- return true if ttl == :none
212
- return true if ttl.is_a?(Numeric) && ttl >= 0
224
+ def evict_nil
225
+ return unless @ignore_nil
213
226
 
214
- false
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
215
235
  end
216
236
 
217
- def valid_ignore_nil?(ignore_nil)
218
- return true if [true, false].include?(ignore_nil)
219
-
220
- false
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
243
+ end
244
+ evict_if_exceeded
245
+ value
221
246
  end
222
247
  end
223
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.1'
4
+ VERSION = '2.1.0'
5
5
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sin_lru_redux
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.1
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
12
  description: |
13
13
  Efficient and thread-safe LRU cache.
@@ -18,17 +18,12 @@ executables: []
18
18
  extensions: []
19
19
  extra_rdoc_files: []
20
20
  files:
21
- - ".github/workflows/lint.yml"
22
- - ".github/workflows/test.yml"
23
- - ".gitignore"
24
21
  - ".rubocop.yml"
25
- - Gemfile
26
- - Guardfile
22
+ - CHANGELOG.md
23
+ - CODE_OF_CONDUCT.md
27
24
  - LICENSE.txt
28
25
  - README.md
29
26
  - Rakefile
30
- - bench/bench.rb
31
- - bench/bench_ttl.rb
32
27
  - lib/lru_redux.rb
33
28
  - lib/lru_redux/cache.rb
34
29
  - lib/lru_redux/thread_safe_cache.rb
@@ -38,20 +33,15 @@ files:
38
33
  - lib/lru_redux/util.rb
39
34
  - lib/lru_redux/util/safe_sync.rb
40
35
  - lib/lru_redux/version.rb
41
- - sin_lru_redux.gemspec
42
- - test/cache_test.rb
43
- - test/thread_safe_cache_test.rb
44
- - test/ttl/cache_test.rb
45
- - test/ttl/thread_safe_cache_test.rb
46
- homepage: https://github.com/cadenza-tech/sin_lru_redux/tree/v2.0.1
36
+ homepage: https://github.com/cadenza-tech/sin_lru_redux/tree/v2.1.0
47
37
  licenses:
48
38
  - MIT
49
39
  metadata:
50
- homepage_uri: https://github.com/cadenza-tech/sin_lru_redux/tree/v2.0.1
51
- source_code_uri: https://github.com/cadenza-tech/sin_lru_redux/tree/v2.0.1
52
- changelog_uri: https://github.com/cadenza-tech/sin_lru_redux/blob/2.0.1#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
53
43
  bug_tracker_uri: https://github.com/cadenza-tech/sin_lru_redux/issues
54
- documentation_uri: https://rubydoc.info/gems/sin_lru_redux/2.0.1
44
+ documentation_uri: https://rubydoc.info/gems/sin_lru_redux/2.1.0
55
45
  rubygems_mfa_required: 'true'
56
46
  rdoc_options: []
57
47
  require_paths:
@@ -1,19 +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
- ruby-version: 3.4
17
- bundler-cache: true
18
- - name: RuboCop
19
- 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,35 +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 = <<~DESCRIPTION
11
- Efficient and thread-safe LRU cache.
12
- Forked from LruRedux.
13
- DESCRIPTION
14
- spec.summary = 'Efficient and thread-safe LRU cache.'
15
- spec.authors = ['Masahiro']
16
- spec.email = ['watanabe@cadenza-tech.com']
17
- spec.license = 'MIT'
18
-
19
- github_root_uri = 'https://github.com/cadenza-tech/sin_lru_redux'
20
- spec.homepage = "#{github_root_uri}/tree/v#{spec.version}"
21
- spec.metadata = {
22
- 'homepage_uri' => spec.homepage,
23
- 'source_code_uri' => spec.homepage,
24
- 'changelog_uri' => "#{github_root_uri}/blob/#{spec.version}#changelog",
25
- 'bug_tracker_uri' => "#{github_root_uri}/issues",
26
- 'documentation_uri' => "https://rubydoc.info/gems/#{spec.name}/#{spec.version}",
27
- 'rubygems_mfa_required' => 'true'
28
- }
29
-
30
- spec.required_ruby_version = '>= 2.3.0'
31
-
32
- spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
33
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
34
- spec.require_paths = ['lib']
35
- end