lazy_init 0.1.1 → 0.2.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.
@@ -84,91 +84,109 @@ module LazyInit
84
84
  def lazy_once(max_entries: nil, ttl: nil, &block)
85
85
  raise ArgumentError, 'Block is required' unless block
86
86
 
87
- # apply global configuration defaults
87
+ # Apply global configuration defaults
88
88
  max_entries ||= LazyInit.configuration.max_lazy_once_entries
89
89
  ttl ||= LazyInit.configuration.lazy_once_ttl
90
90
 
91
- # generate cache key from caller location for automatic memoization
92
- call_location = caller_locations(1, 1).first
93
- location_key = "#{call_location.path}:#{call_location.lineno}"
91
+ # Use simplified version ONLY if no advanced features needed
92
+ if LazyInit::RubyCapabilities::IMPROVED_EVAL_PERFORMANCE &&
93
+ max_entries.nil? && ttl.nil?
94
94
 
95
- # ensure thread-safe cache initialization
96
- @lazy_once_mutex ||= Mutex.new
95
+ # Fast path for Ruby 3+ with no TTL/limits
96
+ location_key = caller_locations(1, 1).first.lineno
97
+
98
+ @lazy_once_simple ||= {}
99
+ return @lazy_once_simple[location_key] if @lazy_once_simple.key?(location_key)
100
+
101
+ result = block.call
102
+ @lazy_once_simple[location_key] = result
103
+ result
104
+ else
105
+ # Use existing full implementation for TTL/limits/statistics
106
+ # (existing lazy_once code with all features)
107
+
108
+ # Generate cache key from caller location for automatic memoization
109
+ call_location = caller_locations(1, 1).first
110
+ location_key = "#{call_location.path}:#{call_location.lineno}"
111
+
112
+ # Ensure thread-safe cache initialization
113
+ @lazy_once_mutex ||= Mutex.new
114
+
115
+ # Fast path: check cache outside mutex for performance
116
+ if @lazy_once_cache&.key?(location_key)
117
+ cached_entry = @lazy_once_cache[location_key]
97
118
 
98
- # fast path: check cache outside mutex for performance
99
- if @lazy_once_cache&.key?(location_key)
100
- cached_entry = @lazy_once_cache[location_key]
101
-
102
- # handle TTL expiration if configured
103
- if ttl && Time.now - cached_entry[:created_at] > ttl
104
- @lazy_once_mutex.synchronize do
105
- # double-check TTL after acquiring lock
106
- if @lazy_once_cache&.key?(location_key)
107
- cached_entry = @lazy_once_cache[location_key]
108
- if Time.now - cached_entry[:created_at] > ttl
109
- @lazy_once_cache.delete(location_key)
110
- else
111
- # entry is still valid, update access tracking and return
119
+ # Handle TTL expiration if configured
120
+ if ttl && Time.now - cached_entry[:created_at] > ttl
121
+ @lazy_once_mutex.synchronize do
122
+ # Double-check TTL after acquiring lock
123
+ if @lazy_once_cache&.key?(location_key)
124
+ cached_entry = @lazy_once_cache[location_key]
125
+ if Time.now - cached_entry[:created_at] > ttl
126
+ @lazy_once_cache.delete(location_key)
127
+ else
128
+ # Entry is still valid, update access tracking and return
129
+ cached_entry[:access_count] += 1
130
+ cached_entry[:last_accessed] = Time.now if ttl
131
+ return cached_entry[:value]
132
+ end
133
+ end
134
+ end
135
+ else
136
+ # Cache hit: update access tracking in thread-safe manner
137
+ @lazy_once_mutex.synchronize do
138
+ if @lazy_once_cache&.key?(location_key)
139
+ cached_entry = @lazy_once_cache[location_key]
112
140
  cached_entry[:access_count] += 1
113
141
  cached_entry[:last_accessed] = Time.now if ttl
114
142
  return cached_entry[:value]
115
143
  end
116
144
  end
117
145
  end
118
- else
119
- # cache hit: update access tracking in thread-safe manner
120
- @lazy_once_mutex.synchronize do
121
- if @lazy_once_cache&.key?(location_key)
122
- cached_entry = @lazy_once_cache[location_key]
146
+ end
147
+
148
+ # Slow path: compute value and cache result
149
+ @lazy_once_mutex.synchronize do
150
+ # Double-check pattern
151
+ if @lazy_once_cache&.key?(location_key)
152
+ cached_entry = @lazy_once_cache[location_key]
153
+
154
+ # Verify TTL hasn't expired while we waited for the lock
155
+ if ttl && Time.now - cached_entry[:created_at] > ttl
156
+ @lazy_once_cache.delete(location_key)
157
+ else
123
158
  cached_entry[:access_count] += 1
124
159
  cached_entry[:last_accessed] = Time.now if ttl
125
160
  return cached_entry[:value]
126
161
  end
127
162
  end
128
- end
129
- end
130
-
131
- # slow path: compute value and cache result
132
- @lazy_once_mutex.synchronize do
133
- # double-check pattern: another thread might have computed while we waited
134
- if @lazy_once_cache&.key?(location_key)
135
- cached_entry = @lazy_once_cache[location_key]
136
-
137
- # verify TTL hasn't expired while we waited for the lock
138
- if ttl && Time.now - cached_entry[:created_at] > ttl
139
- @lazy_once_cache.delete(location_key)
140
- else
141
- cached_entry[:access_count] += 1
142
- cached_entry[:last_accessed] = Time.now if ttl
143
- return cached_entry[:value]
144
- end
145
- end
146
163
 
147
- # initialize cache storage if this is the first lazy_once call
148
- @lazy_once_cache ||= {}
164
+ # Initialize cache storage if this is the first lazy_once call
165
+ @lazy_once_cache ||= {}
149
166
 
150
- # perform LRU cleanup if cache is getting too large
151
- cleanup_lazy_once_cache_simple!(max_entries) if @lazy_once_cache.size >= max_entries
167
+ # Perform LRU cleanup if cache is getting too large
168
+ cleanup_lazy_once_cache_simple!(max_entries) if @lazy_once_cache.size >= max_entries
152
169
 
153
- # compute the value and store in cache with minimal metadata
154
- begin
155
- computed_value = block.call
170
+ # Compute the value and store in cache
171
+ begin
172
+ computed_value = block.call
156
173
 
157
- # create cache entry with minimal required metadata for performance
158
- cache_entry = {
159
- value: computed_value,
160
- access_count: 1
161
- }
174
+ # Create cache entry with metadata
175
+ cache_entry = {
176
+ value: computed_value,
177
+ access_count: 1
178
+ }
162
179
 
163
- # add optional metadata only when features are actually used
164
- cache_entry[:created_at] = Time.now if ttl
165
- cache_entry[:last_accessed] = Time.now if ttl
180
+ # Add optional metadata only when features are actually used
181
+ cache_entry[:created_at] = Time.now if ttl
182
+ cache_entry[:last_accessed] = Time.now if ttl
166
183
 
167
- @lazy_once_cache[location_key] = cache_entry
168
- computed_value
169
- rescue StandardError => e
170
- # don't cache exceptions to keep implementation simple
171
- raise
184
+ @lazy_once_cache[location_key] = cache_entry
185
+ computed_value
186
+ rescue StandardError => e
187
+ # Don't cache exceptions to keep implementation simple
188
+ raise
189
+ end
172
190
  end
173
191
  end
174
192
  end
@@ -232,14 +250,20 @@ module LazyInit
232
250
  def lazy_once_statistics
233
251
  @lazy_once_mutex ||= Mutex.new
234
252
  @lazy_once_mutex.synchronize do
253
+ simple_cache = @lazy_once_simple || {}
254
+ complex_cache = @lazy_once_cache || {}
255
+
256
+ total_entries = simple_cache.size + complex_cache.size
257
+ total_accesses = complex_cache.values.sum { |entry| entry[:access_count] || 1 } + simple_cache.size
258
+
235
259
  # return empty stats if no cache exists yet
236
260
  unless @lazy_once_cache
237
261
  return {
238
- total_entries: 0,
239
- computed_entries: 0,
262
+ total_entries: total_entries,
263
+ computed_entries: total_entries,
240
264
  oldest_entry: nil,
241
265
  newest_entry: nil,
242
- total_accesses: 0,
266
+ total_accesses: total_accesses,
243
267
  average_accesses: 0
244
268
  }
245
269
  end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LazyInit
4
+ # Detects Ruby version capabilities for performance optimizations.
5
+ #
6
+ # This module automatically detects which Ruby version features are available
7
+ # and enables appropriate optimizations without requiring configuration.
8
+ # All detection is done at load time for zero runtime overhead.
9
+ #
10
+ # @since 0.2.0
11
+ module RubyCapabilities
12
+ # Currently used features
13
+ # Ruby 3.0+ introduces significant performance improvements
14
+ RUBY_3_PLUS = (RUBY_VERSION.split('.').map(&:to_i) <=> [3, 0, 0]) >= 0
15
+
16
+ # Improved eval performance in Ruby 3.0+
17
+ # Ruby 3+ has significantly better eval performance than define_method for generated code
18
+ IMPROVED_EVAL_PERFORMANCE = RUBY_3_PLUS
19
+
20
+ # Future optimization opportunities:
21
+ # RUBY_3_2_PLUS = (RUBY_VERSION.split('.').map(&:to_i) <=> [3, 2, 0]) >= 0
22
+ # OBJECT_SHAPES_AVAILABLE = RUBY_3_2_PLUS # Faster ivar access
23
+ # MN_SCHEDULER_AVAILABLE = RUBY_3_2_PLUS # Better thread coordination
24
+ # YJIT_AVAILABLE = RUBY_3_PLUS && !!defined?(RubyVM::YJIT)
25
+ # IMPROVED_MUTEX_PERFORMANCE = RUBY_3_PLUS
26
+ # FIBER_SCHEDULER_AVAILABLE = RUBY_3_PLUS && !!defined?(Fiber.set_scheduler)
27
+
28
+ # Debug information for troubleshooting (only in development)
29
+ unless defined?(Rails) && Rails.env.production?
30
+ def self.report_capabilities
31
+ {
32
+ ruby_version: RUBY_VERSION,
33
+ ruby_3_plus: RUBY_3_PLUS,
34
+ improved_eval: IMPROVED_EVAL_PERFORMANCE
35
+ }
36
+ end
37
+ end
38
+ end
39
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LazyInit
4
- VERSION = '0.1.1'
4
+ VERSION = '0.2.0'
5
5
  end
data/lib/lazy_init.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'lazy_init/version'
4
+ require_relative 'lazy_init/ruby_capabilities'
4
5
  require_relative 'lazy_init/lazy_value'
5
6
  require_relative 'lazy_init/class_methods'
6
7
  require_relative 'lazy_init/instance_methods'
metadata CHANGED
@@ -1,85 +1,85 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lazy_init
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Konstanty Koszewski
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-07-06 00:00:00.000000000 Z
11
+ date: 2025-07-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: rspec
14
+ name: benchmark-ips
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '3.12'
19
+ version: '2.10'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '3.12'
26
+ version: '2.10'
27
27
  - !ruby/object:Gem::Dependency
28
- name: benchmark-ips
28
+ name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '2.10'
33
+ version: '13.0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '2.10'
40
+ version: '13.0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: rubocop
42
+ name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 1.50.2
47
+ version: '3.12'
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: 1.50.2
54
+ version: '3.12'
55
55
  - !ruby/object:Gem::Dependency
56
- name: yard
56
+ name: rubocop
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '0.9'
61
+ version: 1.50.2
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '0.9'
68
+ version: 1.50.2
69
69
  - !ruby/object:Gem::Dependency
70
- name: rake
70
+ name: yard
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '13.0'
75
+ version: '0.9'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '13.0'
82
+ version: '0.9'
83
83
  description: Provides thread-safe lazy initialization with clean, Ruby-idiomatic API.
84
84
  Eliminates race conditions in lazy attribute initialization while maintaining performance.
85
85
  email:
@@ -109,6 +109,7 @@ files:
109
109
  - lib/lazy_init/errors.rb
110
110
  - lib/lazy_init/instance_methods.rb
111
111
  - lib/lazy_init/lazy_value.rb
112
+ - lib/lazy_init/ruby_capabilities.rb
112
113
  - lib/lazy_init/version.rb
113
114
  homepage: https://github.com/N3BCKN/lazy_init
114
115
  licenses:
@@ -118,7 +119,7 @@ metadata:
118
119
  source_code_uri: https://github.com/N3BCKN/lazy_init
119
120
  changelog_uri: https://github.com/N3BCKN/lazy_init/blob/main/CHANGELOG.md
120
121
  bug_tracker_uri: https://github.com/N3BCKN/lazy_init/issues
121
- documentation_uri: https://rubydoc.info/gems/lazy_init
122
+ documentation_uri: https://rubydoc.info/gems/lazy_init/0.2.0
122
123
  post_install_message:
123
124
  rdoc_options: []
124
125
  require_paths: