sin_lru_redux 2.0.0 → 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,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