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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -2
- data/README.md +26 -8
- data/benchmarks/benchmark.rb +616 -145
- data/benchmarks/benchmark_performance.rb +3 -66
- data/benchmarks/benchmark_threads.rb +83 -89
- data/lazy_init.gemspec +5 -5
- data/lib/lazy_init/class_methods.rb +565 -242
- data/lib/lazy_init/instance_methods.rb +90 -66
- data/lib/lazy_init/ruby_capabilities.rb +39 -0
- data/lib/lazy_init/version.rb +1 -1
- data/lib/lazy_init.rb +1 -0
- metadata +19 -18
@@ -1,68 +1,5 @@
|
|
1
1
|
require 'benchmark'
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
# LazyInit vs Manual Performance Benchmark
|
6
|
-
# Testing with realistic production-like expensive operations:
|
7
|
-
|
8
|
-
# --- Configuration Parsing ---
|
9
|
-
# Description: JSON/YAML parsing with complex nested structures
|
10
|
-
|
11
|
-
# Warming up (performing actual expensive computation)...
|
12
|
-
# Initial computation time:
|
13
|
-
# Manual: 14.25ms
|
14
|
-
# LazyInit: 13.28ms
|
15
|
-
# Difference: -0.96ms
|
16
|
-
|
17
|
-
# Benchmarking cached access (100,000 iterations):
|
18
|
-
# user system total real
|
19
|
-
# Manual 0.012272 0.000034 0.012306 ( 0.012490)
|
20
|
-
# LazyInit 0.041375 0.000231 0.041606 ( 0.041994)
|
21
|
-
|
22
|
-
# ============================================================
|
23
|
-
|
24
|
-
# --- Cryptographic Operations ---
|
25
|
-
# Description: Secure token generation with multiple hash rounds
|
26
|
-
|
27
|
-
# Warming up (performing actual expensive computation)...
|
28
|
-
# Initial computation time:
|
29
|
-
# Manual: 4.65ms
|
30
|
-
# LazyInit: 2.37ms
|
31
|
-
# Difference: -2.28ms
|
32
|
-
|
33
|
-
# Benchmarking cached access (100,000 iterations):
|
34
|
-
# user system total real
|
35
|
-
# Manual 0.012351 0.000057 0.012408 ( 0.012445)
|
36
|
-
# LazyInit 0.042226 0.000220 0.042446 ( 0.042752)
|
37
|
-
|
38
|
-
# ============================================================
|
39
|
-
|
40
|
-
# --- Data Processing ---
|
41
|
-
# Description: ETL-style data aggregation and grouping
|
42
|
-
|
43
|
-
# Warming up (performing actual expensive computation)...
|
44
|
-
# Initial computation time:
|
45
|
-
# Manual: 2.61ms
|
46
|
-
# LazyInit: 2.84ms
|
47
|
-
# Difference: 0.22ms
|
48
|
-
|
49
|
-
# Benchmarking cached access (100,000 iterations):
|
50
|
-
# user system total real
|
51
|
-
# Manual 0.012233 0.000048 0.012281 ( 0.012375)
|
52
|
-
# LazyInit 0.044622 0.000262 0.044884 ( 0.045389)
|
53
|
-
|
54
|
-
# ============================================================
|
55
|
-
|
56
|
-
# Thread Safety Test
|
57
|
-
# Testing concurrent access to verify no race conditions...
|
58
|
-
# Computing in thread: 60
|
59
|
-
# Thread safety results:
|
60
|
-
# Unique object IDs: 1 (should be 1)
|
61
|
-
# All threads got same object: ✅ PASS
|
62
|
-
# Total threads: 10
|
63
|
-
# Test Environment: Ruby 3.0.2, x86_64
|
64
|
-
# Platform: x86_64-darwin19
|
65
|
-
#✅ Thread safety confirmed: All threads received the same computed object
|
2
|
+
require_relative '../lib/lazy_init.rb'
|
66
3
|
|
67
4
|
module ExpensiveOperations
|
68
5
|
def parse_configuration
|
@@ -244,7 +181,7 @@ puts "Test Environment: Ruby #{RUBY_VERSION}, #{RbConfig::CONFIG['target_cpu']}"
|
|
244
181
|
puts "Platform: #{RUBY_PLATFORM}"
|
245
182
|
|
246
183
|
if results.uniq.size == 1
|
247
|
-
puts "\n
|
184
|
+
puts "\n Thread safety confirmed: All threads received the same computed object"
|
248
185
|
else
|
249
|
-
puts "\n
|
186
|
+
puts "\n Thread safety failed: Race condition detected!"
|
250
187
|
end
|
@@ -114,60 +114,60 @@ module ProductionOperations
|
|
114
114
|
host: 'prod-db.company.com',
|
115
115
|
port: 5432,
|
116
116
|
pool_size: 20,
|
117
|
-
connections: Array.new(20) { |i| "conn_#{i}_#{rand(
|
117
|
+
connections: Array.new(20) { |i| "conn_#{i}_#{rand(10_000)}" }
|
118
118
|
}
|
119
119
|
end
|
120
|
-
|
120
|
+
|
121
121
|
def create_connection_pool
|
122
122
|
puts "Creating connection pool in thread: #{Thread.current.object_id}"
|
123
123
|
sleep(0.2)
|
124
|
-
pool_id = rand(
|
124
|
+
pool_id = rand(100_000)
|
125
125
|
{
|
126
126
|
id: pool_id,
|
127
127
|
connections: Array.new(50) { |i| "active_conn_#{pool_id}_#{i}" },
|
128
128
|
created_at: Time.now
|
129
129
|
}
|
130
130
|
end
|
131
|
-
|
131
|
+
|
132
132
|
def fetch_user_permissions
|
133
133
|
puts "Fetching permissions in thread: #{Thread.current.object_id}"
|
134
134
|
sleep(0.15)
|
135
135
|
permissions = {}
|
136
|
-
(1..1000).each { |i| permissions["user_#{i}"] = [
|
136
|
+
(1..1000).each { |i| permissions["user_#{i}"] = %w[read write admin].sample }
|
137
137
|
permissions
|
138
138
|
end
|
139
|
-
|
139
|
+
|
140
140
|
def initialize_api_client
|
141
|
-
puts "Initializing API client in thread: #{Thread.current.object_id}"
|
141
|
+
puts "Initializing API client in thread: #{Thread.current.object_id}"
|
142
142
|
sleep(0.08)
|
143
143
|
{
|
144
|
-
client_id: "api_#{rand(
|
145
|
-
token: "bearer_#{rand(
|
144
|
+
client_id: "api_#{rand(50_000)}",
|
145
|
+
token: "bearer_#{rand(1_000_000)}",
|
146
146
|
endpoints: %w[users orders payments analytics],
|
147
147
|
initialized_at: Time.now
|
148
148
|
}
|
149
149
|
end
|
150
150
|
end
|
151
151
|
|
152
|
-
puts
|
152
|
+
puts ' === THREAD SAFETY TESTS ==='
|
153
153
|
puts "Testing LazyInit in production-like concurrent scenarios\n\n"
|
154
154
|
|
155
|
-
puts
|
156
|
-
puts
|
157
|
-
puts
|
155
|
+
puts ' TEST 1: Traffic Spike Simulation'
|
156
|
+
puts 'Simulating 200 simultaneous user requests hitting cached resources'
|
157
|
+
puts '-' * 60
|
158
158
|
|
159
159
|
class TrafficSpikeService
|
160
160
|
extend LazyInit
|
161
161
|
include ProductionOperations
|
162
|
-
|
162
|
+
|
163
163
|
lazy_attr_reader :db_config do
|
164
164
|
load_database_config
|
165
165
|
end
|
166
|
-
|
166
|
+
|
167
167
|
lazy_attr_reader :connection_pool do
|
168
|
-
create_connection_pool
|
168
|
+
create_connection_pool
|
169
169
|
end
|
170
|
-
|
170
|
+
|
171
171
|
lazy_attr_reader :user_permissions do
|
172
172
|
fetch_user_permissions
|
173
173
|
end
|
@@ -177,26 +177,24 @@ spike_service = TrafficSpikeService.new
|
|
177
177
|
spike_results = []
|
178
178
|
spike_errors = []
|
179
179
|
|
180
|
-
puts
|
180
|
+
puts 'Starting traffic spike test...'
|
181
181
|
start_time = Time.now
|
182
182
|
|
183
183
|
spike_threads = 200.times.map do |i|
|
184
184
|
Thread.new do
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
spike_errors << { thread: i, error: e.message }
|
199
|
-
end
|
185
|
+
config = spike_service.db_config
|
186
|
+
pool = spike_service.connection_pool
|
187
|
+
permissions = spike_service.user_permissions
|
188
|
+
|
189
|
+
spike_results << {
|
190
|
+
thread: i,
|
191
|
+
config_id: config.object_id,
|
192
|
+
pool_id: pool.object_id,
|
193
|
+
permissions_id: permissions.object_id,
|
194
|
+
timestamp: Time.now
|
195
|
+
}
|
196
|
+
rescue StandardError => e
|
197
|
+
spike_errors << { thread: i, error: e.message }
|
200
198
|
end
|
201
199
|
end
|
202
200
|
|
@@ -210,7 +208,7 @@ puts " Successful requests: #{spike_results.size}"
|
|
210
208
|
puts " Errors: #{spike_errors.size}"
|
211
209
|
|
212
210
|
config_ids = spike_results.map { |r| r[:config_id] }.uniq
|
213
|
-
pool_ids = spike_results.map { |r| r[:pool_id] }.uniq
|
211
|
+
pool_ids = spike_results.map { |r| r[:pool_id] }.uniq
|
214
212
|
permission_ids = spike_results.map { |r| r[:permissions_id] }.uniq
|
215
213
|
|
216
214
|
puts "\n Race Condition Analysis:"
|
@@ -221,20 +219,20 @@ puts " Permissions - Unique objects: #{permission_ids.size} (should be 1)"
|
|
221
219
|
spike_passed = config_ids.size == 1 && pool_ids.size == 1 && permission_ids.size == 1
|
222
220
|
puts " Result: #{spike_passed ? '✅ PASS' : '❌ FAIL'} - No race conditions detected"
|
223
221
|
|
224
|
-
puts "\n" +
|
222
|
+
puts "\n" + '=' * 60 + "\n"
|
225
223
|
|
226
|
-
puts
|
227
|
-
puts
|
228
|
-
puts
|
224
|
+
puts 'TEST 2: Sustained Load Simulation'
|
225
|
+
puts 'Simulating background workers with sustained concurrent access'
|
226
|
+
puts '-' * 60
|
229
227
|
|
230
228
|
class WorkerService
|
231
229
|
extend LazyInit
|
232
230
|
include ProductionOperations
|
233
|
-
|
231
|
+
|
234
232
|
lazy_attr_reader :api_client do
|
235
233
|
initialize_api_client
|
236
234
|
end
|
237
|
-
|
235
|
+
|
238
236
|
lazy_attr_reader :shared_cache do
|
239
237
|
fetch_user_permissions
|
240
238
|
end
|
@@ -244,29 +242,27 @@ worker_service = WorkerService.new
|
|
244
242
|
worker_results = []
|
245
243
|
worker_errors = []
|
246
244
|
|
247
|
-
puts
|
245
|
+
puts 'Starting sustained load test (30 workers x 50 operations each)...'
|
248
246
|
|
249
247
|
worker_threads = 30.times.map do |worker_id|
|
250
248
|
Thread.new do
|
251
249
|
thread_results = []
|
252
250
|
50.times do |operation|
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
worker_errors << { worker: worker_id, operation: operation, error: e.message }
|
267
|
-
end
|
251
|
+
api = worker_service.api_client
|
252
|
+
cache = worker_service.shared_cache
|
253
|
+
|
254
|
+
thread_results << {
|
255
|
+
worker: worker_id,
|
256
|
+
operation: operation,
|
257
|
+
api_id: api.object_id,
|
258
|
+
cache_id: cache.object_id
|
259
|
+
}
|
260
|
+
|
261
|
+
sleep(0.001) if operation % 10 == 0
|
262
|
+
rescue StandardError => e
|
263
|
+
worker_errors << { worker: worker_id, operation: operation, error: e.message }
|
268
264
|
end
|
269
|
-
|
265
|
+
|
270
266
|
worker_results.concat(thread_results)
|
271
267
|
end
|
272
268
|
end
|
@@ -291,32 +287,32 @@ puts " Shared Cache - Unique objects: #{cache_ids.size} (should be 1)"
|
|
291
287
|
worker_passed = api_ids.size == 1 && cache_ids.size == 1
|
292
288
|
puts " Result: #{worker_passed ? '✅ PASS' : '❌ FAIL'} - Consistency maintained"
|
293
289
|
|
294
|
-
puts "\n" +
|
290
|
+
puts "\n" + '=' * 60 + "\n"
|
295
291
|
|
296
|
-
puts
|
297
|
-
puts
|
298
|
-
puts
|
292
|
+
puts ' TEST 3: Dependency Chain Stress Test'
|
293
|
+
puts 'Testing complex dependency resolution under concurrent pressure'
|
294
|
+
puts '-' * 60
|
299
295
|
|
300
296
|
class DependencyChainService
|
301
297
|
extend LazyInit
|
302
298
|
include ProductionOperations
|
303
|
-
|
299
|
+
|
304
300
|
lazy_attr_reader :base_config do
|
305
301
|
puts "Computing base_config in thread: #{Thread.current.object_id}"
|
306
302
|
load_database_config
|
307
303
|
end
|
308
|
-
|
304
|
+
|
309
305
|
lazy_attr_reader :connection_manager, depends_on: [:base_config] do
|
310
306
|
puts "Computing connection_manager in thread: #{Thread.current.object_id}"
|
311
307
|
create_connection_pool
|
312
308
|
end
|
313
|
-
|
309
|
+
|
314
310
|
lazy_attr_reader :auth_service, depends_on: [:base_config] do
|
315
311
|
puts "Computing auth_service in thread: #{Thread.current.object_id}"
|
316
312
|
fetch_user_permissions
|
317
313
|
end
|
318
|
-
|
319
|
-
lazy_attr_reader :api_gateway, depends_on: [
|
314
|
+
|
315
|
+
lazy_attr_reader :api_gateway, depends_on: %i[connection_manager auth_service] do
|
320
316
|
puts "Computing api_gateway in thread: #{Thread.current.object_id}"
|
321
317
|
initialize_api_client
|
322
318
|
end
|
@@ -325,12 +321,12 @@ end
|
|
325
321
|
dependency_service = DependencyChainService.new
|
326
322
|
dependency_results = []
|
327
323
|
|
328
|
-
puts
|
324
|
+
puts 'Starting dependency chain test (100 concurrent accesses to complex dependency)...'
|
329
325
|
|
330
326
|
dependency_threads = 100.times.map do |i|
|
331
327
|
Thread.new do
|
332
328
|
gateway = dependency_service.api_gateway
|
333
|
-
|
329
|
+
|
334
330
|
dependency_results << {
|
335
331
|
thread: i,
|
336
332
|
gateway_id: gateway.object_id,
|
@@ -356,16 +352,16 @@ puts " Unique gateway objects: #{gateway_ids.size} (should be 1)"
|
|
356
352
|
dependency_passed = gateway_ids.size == 1
|
357
353
|
puts " Result: #{dependency_passed ? '✅ PASS' : '❌ FAIL'} - Dependency resolution works correctly"
|
358
354
|
|
359
|
-
puts "\n" +
|
355
|
+
puts "\n" + '=' * 60 + "\n"
|
360
356
|
|
361
|
-
puts
|
362
|
-
puts
|
363
|
-
puts
|
357
|
+
puts 'TEST 4: Reset and Recovery Under Load'
|
358
|
+
puts 'Testing reset operations during concurrent access (production maintenance scenario)'
|
359
|
+
puts '-' * 60
|
364
360
|
|
365
361
|
class ResetTestService
|
366
362
|
extend LazyInit
|
367
363
|
include ProductionOperations
|
368
|
-
|
364
|
+
|
369
365
|
lazy_attr_reader :service_config do
|
370
366
|
load_database_config
|
371
367
|
end
|
@@ -375,19 +371,17 @@ reset_service = ResetTestService.new
|
|
375
371
|
reset_results = []
|
376
372
|
reset_stats = { resets: 0, access_attempts: 0, successes: 0 }
|
377
373
|
|
378
|
-
puts
|
374
|
+
puts 'Starting reset test (readers + periodic resets)...'
|
379
375
|
|
380
376
|
reader_threads = 20.times.map do |i|
|
381
377
|
Thread.new do
|
382
378
|
50.times do |attempt|
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
rescue => e
|
390
|
-
end
|
379
|
+
reset_stats[:access_attempts] += 1
|
380
|
+
config = reset_service.service_config
|
381
|
+
reset_results << { thread: i, attempt: attempt, config_id: config.object_id }
|
382
|
+
reset_stats[:successes] += 1
|
383
|
+
sleep(0.01)
|
384
|
+
rescue StandardError => e
|
391
385
|
end
|
392
386
|
end
|
393
387
|
end
|
@@ -415,19 +409,19 @@ puts " Unique config objects: #{unique_configs.size} (should be > 1 due to rese
|
|
415
409
|
reset_passed = unique_configs.size > 1 && reset_stats[:successes] > 0
|
416
410
|
puts " Result: #{reset_passed ? '✅ PASS' : '❌ FAIL'} - Reset and recovery works correctly"
|
417
411
|
|
418
|
-
puts "\n" +
|
412
|
+
puts "\n" + '=' * 60 + "\n"
|
419
413
|
|
420
414
|
all_tests_passed = spike_passed && worker_passed && dependency_passed && reset_passed
|
421
415
|
|
422
|
-
puts
|
416
|
+
puts ' === FINAL THREAD SAFETY SUMMARY ==='
|
423
417
|
puts "Test Environment: Ruby #{RUBY_VERSION}, #{RbConfig::CONFIG['target_cpu']}"
|
424
418
|
puts "Platform: #{RUBY_PLATFORM}"
|
425
|
-
puts
|
426
|
-
puts
|
419
|
+
puts ''
|
420
|
+
puts 'Results:'
|
427
421
|
puts " ✅ Traffic Spike (200 concurrent): #{spike_passed ? 'PASS' : 'FAIL'}"
|
428
|
-
puts " ✅ Sustained Load (1500 operations): #{worker_passed ? 'PASS' : 'FAIL'}"
|
422
|
+
puts " ✅ Sustained Load (1500 operations): #{worker_passed ? 'PASS' : 'FAIL'}"
|
429
423
|
puts " ✅ Dependency Chain (100 concurrent): #{dependency_passed ? 'PASS' : 'FAIL'}"
|
430
424
|
puts " ✅ Reset Recovery (periodic resets): #{reset_passed ? 'PASS' : 'FAIL'}"
|
431
|
-
puts
|
425
|
+
puts ''
|
432
426
|
puts "Overall Result: #{all_tests_passed ? '✅ ALL TESTS PASSED' : '❌ SOME TESTS FAILED'}"
|
433
|
-
puts
|
427
|
+
puts ''
|
data/lazy_init.gemspec
CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.metadata['source_code_uri'] = 'https://github.com/N3BCKN/lazy_init'
|
19
19
|
spec.metadata['changelog_uri'] = 'https://github.com/N3BCKN/lazy_init/blob/main/CHANGELOG.md'
|
20
20
|
spec.metadata['bug_tracker_uri'] = 'https://github.com/N3BCKN/lazy_init/issues'
|
21
|
-
spec.metadata['documentation_uri'] =
|
21
|
+
spec.metadata['documentation_uri'] = "https://rubydoc.info/gems/lazy_init/#{LazyInit::VERSION}"
|
22
22
|
|
23
23
|
# Specify which files should be added to the gem when it is released.
|
24
24
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
@@ -30,11 +30,11 @@ Gem::Specification.new do |spec|
|
|
30
30
|
spec.require_paths = ['lib']
|
31
31
|
|
32
32
|
spec.required_ruby_version = '>= 2.6'
|
33
|
-
|
33
|
+
|
34
34
|
# Development dependencies
|
35
|
-
spec.add_development_dependency 'rspec', '~> 3.12'
|
36
35
|
spec.add_development_dependency 'benchmark-ips', '~> 2.10'
|
36
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
37
|
+
spec.add_development_dependency 'rspec', '~> 3.12'
|
37
38
|
spec.add_development_dependency 'rubocop', '~> 1.50.2'
|
38
39
|
spec.add_development_dependency 'yard', '~> 0.9'
|
39
|
-
|
40
|
-
end
|
40
|
+
end
|