sin_lru_redux 2.0.1 → 2.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.
@@ -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