familia 1.2.3 → 2.0.0.pre.pre

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.
Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +68 -0
  3. data/.github/workflows/docs.yml +64 -0
  4. data/.gitignore +3 -0
  5. data/.pre-commit-config.yaml +3 -1
  6. data/.rubocop.yml +16 -9
  7. data/.rubocop_todo.yml +177 -31
  8. data/.yardopts +9 -0
  9. data/CLAUDE.md +141 -0
  10. data/Gemfile +15 -2
  11. data/Gemfile.lock +61 -61
  12. data/README.md +39 -23
  13. data/bin/irb +3 -0
  14. data/docs/connection_pooling.md +317 -0
  15. data/familia.gemspec +8 -5
  16. data/lib/familia/base.rb +19 -9
  17. data/lib/familia/connection.rb +232 -65
  18. data/lib/familia/core_ext.rb +1 -1
  19. data/lib/familia/datatype/commands.rb +59 -0
  20. data/lib/familia/{redistype → datatype}/serialization.rb +9 -13
  21. data/lib/familia/{redistype → datatype}/types/hashkey.rb +25 -25
  22. data/lib/familia/{redistype → datatype}/types/list.rb +13 -13
  23. data/lib/familia/{redistype → datatype}/types/sorted_set.rb +20 -20
  24. data/lib/familia/{redistype → datatype}/types/string.rb +22 -21
  25. data/lib/familia/{redistype → datatype}/types/unsorted_set.rb +11 -11
  26. data/lib/familia/datatype.rb +243 -0
  27. data/lib/familia/errors.rb +5 -2
  28. data/lib/familia/features/expiration.rb +33 -34
  29. data/lib/familia/features/quantization.rb +9 -3
  30. data/lib/familia/features/safe_dump.rb +2 -3
  31. data/lib/familia/features.rb +2 -2
  32. data/lib/familia/horreum/class_methods.rb +97 -130
  33. data/lib/familia/horreum/commands.rb +46 -51
  34. data/lib/familia/horreum/connection.rb +82 -0
  35. data/lib/familia/horreum/{relations_management.rb → related_fields_management.rb} +37 -35
  36. data/lib/familia/horreum/serialization.rb +61 -198
  37. data/lib/familia/horreum/settings.rb +6 -17
  38. data/lib/familia/horreum/utils.rb +11 -10
  39. data/lib/familia/horreum.rb +69 -60
  40. data/lib/familia/logging.rb +12 -12
  41. data/lib/familia/multi_result.rb +72 -0
  42. data/lib/familia/refinements.rb +7 -44
  43. data/lib/familia/settings.rb +11 -11
  44. data/lib/familia/utils.rb +123 -90
  45. data/lib/familia/version.rb +4 -21
  46. data/lib/familia.rb +17 -12
  47. data/lib/middleware/database_middleware.rb +150 -0
  48. data/try/configuration/scenarios_try.rb +65 -0
  49. data/try/core/connection_try.rb +58 -0
  50. data/try/core/errors_try.rb +93 -0
  51. data/try/core/extensions_try.rb +26 -0
  52. data/try/{10_familia_try.rb → core/familia_extended_try.rb} +11 -10
  53. data/try/{00_familia_try.rb → core/familia_try.rb} +5 -3
  54. data/try/core/middleware_try.rb +68 -0
  55. data/try/core/refinements_try.rb +39 -0
  56. data/try/core/settings_try.rb +76 -0
  57. data/try/core/tools_try.rb +54 -0
  58. data/try/core/utils_try.rb +189 -0
  59. data/try/{26_redis_bool_try.rb → datatypes/boolean_try.rb} +4 -2
  60. data/try/datatypes/datatype_base_try.rb +69 -0
  61. data/try/{25_redis_type_hash_try.rb → datatypes/hash_try.rb} +5 -3
  62. data/try/{23_redis_type_list_try.rb → datatypes/list_try.rb} +5 -3
  63. data/try/{22_redis_type_set_try.rb → datatypes/set_try.rb} +5 -3
  64. data/try/{21_redis_type_zset_try.rb → datatypes/sorted_set_try.rb} +6 -4
  65. data/try/{24_redis_type_string_try.rb → datatypes/string_try.rb} +8 -8
  66. data/try/edge_cases/empty_identifiers_try.rb +48 -0
  67. data/try/{92_symbolize_try.rb → edge_cases/hash_symbolization_try.rb} +12 -8
  68. data/try/edge_cases/json_serialization_try.rb +85 -0
  69. data/try/edge_cases/race_conditions_try.rb +60 -0
  70. data/try/edge_cases/reserved_keywords_try.rb +59 -0
  71. data/try/{93_string_coercion_try.rb → edge_cases/string_coercion_try.rb} +63 -60
  72. data/try/edge_cases/ttl_side_effects_try.rb +51 -0
  73. data/try/features/expiration_try.rb +86 -0
  74. data/try/features/quantization_try.rb +90 -0
  75. data/try/{35_feature_safedump_try.rb → features/safe_dump_advanced_try.rb} +7 -6
  76. data/try/features/safe_dump_try.rb +137 -0
  77. data/try/{test_helpers.rb → helpers/test_helpers.rb} +25 -60
  78. data/try/{27_redis_horreum_try.rb → horreum/base_try.rb} +39 -14
  79. data/try/horreum/class_methods_try.rb +41 -0
  80. data/try/horreum/commands_try.rb +49 -0
  81. data/try/{29_redis_horreum_initialization_try.rb → horreum/initialization_try.rb} +9 -7
  82. data/try/horreum/relations_try.rb +146 -0
  83. data/try/{28_redis_horreum_serialization_try.rb → horreum/serialization_try.rb} +13 -11
  84. data/try/horreum/settings_try.rb +43 -0
  85. data/try/integration/cross_component_try.rb +46 -0
  86. data/try/{41_customer_safedump_try.rb → models/customer_safe_dump_try.rb} +9 -7
  87. data/try/{40_customer_try.rb → models/customer_try.rb} +20 -17
  88. data/try/models/datatype_base_try.rb +101 -0
  89. data/try/{30_familia_object_try.rb → models/familia_object_try.rb} +18 -16
  90. data/try/performance/benchmarks_try.rb +55 -0
  91. data/try/pooling/README.md +20 -0
  92. data/try/pooling/configurable_stress_test_try.rb +435 -0
  93. data/try/pooling/connection_pool_test_try.rb +273 -0
  94. data/try/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +192 -0
  95. data/try/pooling/lib/connection_pool_metrics.rb +372 -0
  96. data/try/pooling/lib/connection_pool_stress_test.rb +959 -0
  97. data/try/pooling/lib/connection_pool_threading_models.rb +421 -0
  98. data/try/pooling/lib/visualize_stress_results.rb +434 -0
  99. data/try/pooling/pool_siege_try.rb +509 -0
  100. data/try/pooling/run_stress_tests_try.rb +482 -0
  101. data/try/prototypes/atomic_saves_v1_context_proxy.rb +121 -0
  102. data/try/prototypes/atomic_saves_v2_connection_switching.rb +161 -0
  103. data/try/prototypes/atomic_saves_v3_connection_pool.rb +189 -0
  104. data/try/prototypes/atomic_saves_v4.rb +105 -0
  105. data/try/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +124 -0
  106. data/try/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +192 -0
  107. metadata +124 -38
  108. data/.github/workflows/ruby.yml +0 -71
  109. data/VERSION.yml +0 -4
  110. data/lib/familia/redistype/commands.rb +0 -59
  111. data/lib/familia/redistype.rb +0 -228
  112. data/lib/familia/tools.rb +0 -68
  113. data/lib/redis_middleware.rb +0 -109
  114. data/try/20_redis_type_try.rb +0 -70
  115. data/try/91_json_bug_try.rb +0 -86
@@ -0,0 +1,482 @@
1
+ #!/usr/bin/env ruby
2
+ # try/prototypes/run_stress_tests.rb
3
+ #
4
+ # Main Test Runner for Connection Pool Stress Tests
5
+ #
6
+ # This script orchestrates comprehensive stress testing of the connection pool
7
+ # implementation across different scenarios, threading models, and configurations.
8
+ # It generates detailed reports and comparisons to identify bottlenecks and
9
+ # failure modes.
10
+
11
+ require 'optparse'
12
+ require 'fileutils'
13
+
14
+ require_relative 'lib/connection_pool_stress_test'
15
+ require_relative 'lib/connection_pool_threading_models'
16
+ require_relative 'lib/connection_pool_metrics'
17
+ require_relative 'lib/visualize_stress_results'
18
+
19
+ class StressTestRunner
20
+ # Updated to use StressTestConfig systematically
21
+ PREDEFINED_CONFIGS = {
22
+ light: StressTestConfig.for_development,
23
+ moderate: StressTestConfig.for_ci,
24
+ heavy: StressTestConfig.for_production_validation,
25
+ extreme: StressTestConfig.for_bottleneck_analysis,
26
+ tuning: StressTestConfig.for_performance_tuning
27
+ }
28
+
29
+ def initialize(options = {})
30
+ @options = {
31
+ config_set: :moderate,
32
+ output_dir: "stress_test_results_#{Time.now.strftime('%Y%m%d_%H%M%S')}",
33
+ threading_models: [:traditional, :thread_pool, :fiber],
34
+ operation_mixes: [:balanced, :read_heavy, :write_heavy],
35
+ generate_visualizations: true,
36
+ verbose: false,
37
+ use_runtime_config: false
38
+ }.merge(options)
39
+
40
+ # Use runtime config from environment if requested
41
+ if @options[:use_runtime_config]
42
+ runtime_overrides = StressTestConfig.runtime_config
43
+ puts "Using runtime configuration overrides from environment" if @options[:verbose]
44
+ @config_set = runtime_overrides
45
+ else
46
+ @config_set = PREDEFINED_CONFIGS[@options[:config_set]]
47
+ raise ArgumentError, "Unknown config set: #{@options[:config_set]}" unless @config_set
48
+ end
49
+
50
+ @results_aggregator = ConnectionPoolMetrics::ResultAggregator.new
51
+
52
+ setup_output_directory
53
+ end
54
+
55
+ def run_all_tests
56
+ puts "=" * 80
57
+ puts "CONNECTION POOL STRESS TEST SUITE"
58
+ puts "=" * 80
59
+ puts "Configuration: #{@options[:config_set]}"
60
+ puts "Output directory: #{@options[:output_dir]}"
61
+ puts "Threading models: #{@options[:threading_models].join(', ')}"
62
+ puts "=" * 80
63
+
64
+ total_tests = calculate_total_tests(@config_set)
65
+ current_test = 0
66
+
67
+ puts "Total tests to run: #{total_tests}"
68
+ puts ""
69
+
70
+ start_time = Time.now
71
+
72
+ @config_set[:scenarios].each do |scenario|
73
+ puts "\n--- Testing Scenario: #{scenario} ---"
74
+
75
+ @config_set[:thread_counts].each do |thread_count|
76
+ @config_set[:operations_per_thread].each do |ops_per_thread|
77
+ @config_set[:pool_sizes].each do |pool_size|
78
+ @config_set[:pool_timeouts].each do |pool_timeout|
79
+ @options[:operation_mixes].each do |operation_mix|
80
+ @options[:threading_models].each do |threading_model|
81
+ current_test += 1
82
+
83
+ test_config = StressTestConfig.merge_and_validate(
84
+ StressTestConfig.default,
85
+ {
86
+ thread_count: thread_count,
87
+ operations_per_thread: ops_per_thread,
88
+ pool_size: pool_size,
89
+ pool_timeout: pool_timeout,
90
+ operation_mix: operation_mix,
91
+ scenario: scenario,
92
+ threading_model: threading_model
93
+ }
94
+ )
95
+
96
+ puts sprintf("[%d/%d] Running test: %s",
97
+ current_test, total_tests, format_test_config(test_config))
98
+
99
+ run_single_test(test_config)
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ duration = Time.now - start_time
109
+ puts "\n" + "=" * 80
110
+ puts "ALL TESTS COMPLETED"
111
+ puts "Total duration: #{format_duration(duration)}"
112
+ puts "Results saved to: #{@options[:output_dir]}"
113
+
114
+ generate_final_reports
115
+
116
+ puts "=" * 80
117
+ end
118
+
119
+ def run_single_test(config)
120
+ # Clean database
121
+ BankAccount.dbclient.flushdb
122
+
123
+ begin
124
+ if config[:threading_model] == :traditional
125
+ # Use original stress test for traditional threading
126
+ test = ConnectionPoolStressTest.new(config)
127
+ test.run
128
+ metrics_summary = test.metrics.summary
129
+ model_info = { name: 'traditional', details: {} }
130
+ else
131
+ # Use enhanced test for other threading models
132
+ test = EnhancedConnectionPoolStressTest.new(config)
133
+ model_info = test.run_with_model(config[:threading_model])
134
+ metrics_summary = test.metrics.summary
135
+ end
136
+
137
+ # Save detailed results
138
+ save_test_results(config, test.metrics, model_info)
139
+
140
+ # Add to aggregator
141
+ @results_aggregator.add_result(config, metrics_summary, model_info)
142
+
143
+ # Print summary if verbose
144
+ if @options[:verbose]
145
+ puts " Success rate: #{metrics_summary[:success_rate]}%"
146
+ puts " Avg duration: #{(metrics_summary[:avg_duration] * 1000).round(2)}ms"
147
+ puts " Errors: #{metrics_summary[:failed_operations]}"
148
+ end
149
+
150
+ return true
151
+
152
+ rescue => e
153
+ puts " ERROR: #{e.message}"
154
+ puts " #{e.backtrace.first}" if @options[:verbose]
155
+
156
+ # Record failed test
157
+ error_info = {
158
+ error: e.class.name,
159
+ message: e.message,
160
+ backtrace: e.backtrace.first(5)
161
+ }
162
+
163
+ @results_aggregator.add_result(
164
+ config,
165
+ { success_rate: 0, failed_operations: 1, total_operations: 0 },
166
+ { name: config[:threading_model], error: error_info }
167
+ )
168
+
169
+ return false
170
+ end
171
+ end
172
+
173
+ private
174
+
175
+ def setup_output_directory
176
+ FileUtils.mkdir_p(@options[:output_dir])
177
+ FileUtils.mkdir_p(File.join(@options[:output_dir], 'individual_tests'))
178
+
179
+ # Create README
180
+ readme_content = generate_readme
181
+ File.write(File.join(@options[:output_dir], 'README.md'), readme_content)
182
+ end
183
+
184
+ def calculate_total_tests(config_set)
185
+ config_set[:scenarios].size *
186
+ config_set[:thread_counts].size *
187
+ config_set[:operations_per_thread].size *
188
+ config_set[:pool_sizes].size *
189
+ config_set[:pool_timeouts].size *
190
+ @options[:operation_mixes].size *
191
+ @options[:threading_models].size
192
+ end
193
+
194
+ def format_test_config(config)
195
+ "#{config[:threading_model]}/#{config[:scenario]}/T#{config[:thread_count]}/O#{config[:operations_per_thread]}/P#{config[:pool_size]}/#{config[:operation_mix]}"
196
+ end
197
+
198
+ def format_duration(seconds)
199
+ if seconds < 60
200
+ "#{seconds.round(2)}s"
201
+ elsif seconds < 3600
202
+ "#{(seconds / 60).round(2)}m"
203
+ else
204
+ "#{(seconds / 3600).round(2)}h"
205
+ end
206
+ end
207
+
208
+ def save_test_results(config, metrics, model_info)
209
+ timestamp = Time.now.strftime('%Y%m%d_%H%M%S_%L')
210
+ test_id = "#{config[:threading_model]}_#{config[:scenario]}_#{timestamp}"
211
+
212
+ # Export detailed CSV files
213
+ if metrics.respond_to?(:export_detailed_csv)
214
+ csv_prefix = File.join(@options[:output_dir], 'individual_tests', test_id)
215
+ metrics.export_detailed_csv(csv_prefix)
216
+ end
217
+
218
+ # Save test configuration and results
219
+ test_data = {
220
+ timestamp: Time.now,
221
+ config: config,
222
+ model_info: model_info,
223
+ summary: metrics.respond_to?(:detailed_summary) ? metrics.detailed_summary : metrics.summary
224
+ }
225
+
226
+ File.write(
227
+ File.join(@options[:output_dir], 'individual_tests', "#{test_id}_config.json"),
228
+ JSON.pretty_generate(test_data)
229
+ )
230
+ end
231
+
232
+ def generate_final_reports
233
+ puts "\nGenerating final reports..."
234
+
235
+ # Export aggregated comparison
236
+ comparison_file = File.join(@options[:output_dir], 'comparison_results.csv')
237
+ @results_aggregator.export_comparison_csv(comparison_file)
238
+
239
+ # Generate comparison report
240
+ comparison_report = @results_aggregator.generate_comparison_report
241
+ File.write(File.join(@options[:output_dir], 'comparison_report.md'), comparison_report)
242
+
243
+ # Generate visualizations if requested
244
+ if @options[:generate_visualizations]
245
+ generate_visualizations(comparison_file)
246
+ end
247
+
248
+ # Create executive summary
249
+ executive_summary = generate_executive_summary
250
+ File.write(File.join(@options[:output_dir], 'executive_summary.md'), executive_summary)
251
+
252
+ puts "Reports generated:"
253
+ puts " - comparison_results.csv"
254
+ puts " - comparison_report.md"
255
+ puts " - executive_summary.md"
256
+ puts " - visualization_report.md" if @options[:generate_visualizations]
257
+ end
258
+
259
+ def generate_visualizations(comparison_file)
260
+ visualizer = StressTestVisualizer.new([comparison_file])
261
+ report = visualizer.generate_report
262
+
263
+ File.write(File.join(@options[:output_dir], 'visualization_report.md'), report)
264
+ end
265
+
266
+ def generate_readme
267
+ <<~README
268
+ # Connection Pool Stress Test Results
269
+
270
+ Generated: #{Time.now}
271
+ Configuration: #{@options[:config_set]}
272
+
273
+ ## Directory Structure
274
+
275
+ - `comparison_results.csv` - Aggregated comparison data
276
+ - `comparison_report.md` - Analysis of all test configurations
277
+ - `executive_summary.md` - High-level summary and recommendations
278
+ - `visualization_report.md` - Charts and graphs (if generated)
279
+ - `individual_tests/` - Detailed results for each test run
280
+
281
+ ## Test Configuration
282
+
283
+ - **Threading models tested**: #{@options[:threading_models].join(', ')}
284
+ - **Operation mixes tested**: #{@options[:operation_mixes].join(', ')}
285
+ - **Scenarios covered**: #{PREDEFINED_CONFIGS[@options[:config_set]][:scenarios].join(', ')}
286
+
287
+ ## How to Analyze Results
288
+
289
+ 1. Start with `executive_summary.md` for key findings
290
+ 2. Review `comparison_report.md` for detailed analysis
291
+ 3. Check `visualization_report.md` for charts
292
+ 4. Examine individual test files in `individual_tests/` for deep dives
293
+
294
+ ## Reproducing Tests
295
+
296
+ To reproduce these tests, run:
297
+
298
+ ```bash
299
+ ruby run_stress_tests.rb --config #{@options[:config_set]} --output #{@options[:output_dir]}
300
+ ```
301
+ README
302
+ end
303
+
304
+ def generate_executive_summary
305
+ summary = <<~SUMMARY
306
+ # Executive Summary - Connection Pool Stress Testing
307
+
308
+ **Generated**: #{Time.now}
309
+ **Test Configuration**: #{@options[:config_set]}
310
+
311
+ ## Key Findings
312
+
313
+ *[This would be populated with actual analysis results in a real implementation]*
314
+
315
+ ### Performance Highlights
316
+
317
+ - **Best performing threading model**: *TBD based on results*
318
+ - **Most reliable configuration**: *TBD based on results*
319
+ - **Recommended pool size**: *TBD based on results*
320
+
321
+ ### Identified Issues
322
+
323
+ - **Connection starvation threshold**: *TBD*
324
+ - **Error patterns**: *TBD*
325
+ - **Performance bottlenecks**: *TBD*
326
+
327
+ ## Recommendations
328
+
329
+ 1. **Production Configuration**:
330
+ - Pool size: *TBD*
331
+ - Timeout: *TBD*
332
+ - Threading model: *TBD*
333
+
334
+ 2. **Monitoring**:
335
+ - Watch for pool utilization > X%
336
+ - Alert on connection wait times > X seconds
337
+ - Monitor error rates by operation type
338
+
339
+ 3. **Future Testing**:
340
+ - Test with production-like workloads
341
+ - Validate under network latency
342
+ - Test failover scenarios
343
+
344
+ ## Files for Deep Dive
345
+
346
+ - `comparison_results.csv` - Raw performance data
347
+ - `visualization_report.md` - Performance charts
348
+ - `individual_tests/` - Detailed test results
349
+ SUMMARY
350
+ end
351
+ end
352
+
353
+ # Command-line interface
354
+ if __FILE__ == $0
355
+ options = {}
356
+
357
+ OptionParser.new do |opts|
358
+ opts.banner = "Usage: run_stress_tests.rb [options]"
359
+
360
+ opts.on("-c", "--config CONFIG", "Test configuration: light, moderate, heavy, extreme, tuning") do |config|
361
+ options[:config_set] = config.to_sym
362
+ end
363
+
364
+ opts.on("-o", "--output DIR", "Output directory") do |dir|
365
+ options[:output_dir] = dir
366
+ end
367
+
368
+ opts.on("-m", "--models MODELS", "Threading models (comma-separated): traditional,fiber,thread_pool,hybrid,actor") do |models|
369
+ options[:threading_models] = models.split(',').map(&:strip).map(&:to_sym)
370
+ end
371
+
372
+ opts.on("-x", "--mixes MIXES", "Operation mixes (comma-separated): balanced,read_heavy,write_heavy,transaction_heavy") do |mixes|
373
+ options[:operation_mixes] = mixes.split(',').map(&:strip).map(&:to_sym)
374
+ end
375
+
376
+ opts.on("-v", "--verbose", "Verbose output") do
377
+ options[:verbose] = true
378
+ end
379
+
380
+ opts.on("--no-visualizations", "Skip visualization generation") do
381
+ options[:generate_visualizations] = false
382
+ end
383
+
384
+ opts.on("--runtime-config", "Use configuration from environment variables") do
385
+ options[:use_runtime_config] = true
386
+ end
387
+
388
+ opts.on("--validate-config", "Validate configuration and show warnings") do
389
+ options[:validate_only] = true
390
+ end
391
+
392
+ opts.on("--list-configs", "List available configurations") do
393
+ puts "Available configurations:"
394
+ StressTestRunner::PREDEFINED_CONFIGS.each do |name, config|
395
+ puts " #{name}: #{config[:scenarios].join(', ')}"
396
+ end
397
+ puts "\nEnvironment variables for runtime config:"
398
+ puts " STRESS_THREADS=5,10,20 - Thread counts to test"
399
+ puts " STRESS_OPS=50,100 - Operations per thread"
400
+ puts " STRESS_POOLS=5,10,20 - Pool sizes to test"
401
+ puts " STRESS_TIMEOUTS=5,10 - Pool timeouts (seconds)"
402
+ puts " STRESS_SCENARIOS=rapid_fire,mixed_workload - Scenarios to run"
403
+ puts " STRESS_MIXES=balanced,read_heavy - Operation mixes"
404
+ exit
405
+ end
406
+
407
+ opts.on("--list-scenarios", "List available test scenarios") do
408
+ puts "Available test scenarios:"
409
+ StressTestConfig::SCENARIOS.each do |scenario|
410
+ puts " #{scenario}"
411
+ end
412
+ exit
413
+ end
414
+
415
+ opts.on("-h", "--help", "Show this help") do
416
+ puts opts
417
+ puts "\nExamples:"
418
+ puts " # Quick development test"
419
+ puts " ruby run_stress_tests.rb --config light --verbose"
420
+ puts ""
421
+ puts " # Use environment configuration"
422
+ puts " STRESS_THREADS=10,50 STRESS_POOLS=5,10 ruby run_stress_tests.rb --runtime-config"
423
+ puts ""
424
+ puts " # Validate a configuration"
425
+ puts " ruby run_stress_tests.rb --config extreme --validate-config"
426
+ exit
427
+ end
428
+ end.parse!
429
+
430
+ # Validate configuration
431
+ if options[:config_set] && !StressTestRunner::PREDEFINED_CONFIGS.key?(options[:config_set])
432
+ puts "Error: Unknown configuration '#{options[:config_set]}'"
433
+ puts "Available: #{StressTestRunner::PREDEFINED_CONFIGS.keys.join(', ')}"
434
+ exit 1
435
+ end
436
+
437
+ # Initialize Familia
438
+ require_relative '../helpers/test_helpers'
439
+ Familia.debug = false
440
+
441
+ # Handle validation-only mode
442
+ if options[:validate_only]
443
+ puts "Validating configuration: #{options[:config_set] || 'runtime'}"
444
+
445
+ if options[:use_runtime_config]
446
+ config = StressTestConfig.runtime_config
447
+ puts "Runtime configuration from environment:"
448
+ config.each do |key, value|
449
+ puts " #{key}: #{value.join(', ')}"
450
+ end
451
+ else
452
+ config_set = options[:config_set] || :moderate
453
+ config = StressTestRunner::PREDEFINED_CONFIGS[config_set]
454
+ puts "Predefined configuration: #{config_set}"
455
+ config.each do |key, value|
456
+ puts " #{key}: #{value.join(', ')}"
457
+ end
458
+ end
459
+
460
+ # Sample validation
461
+ sample_config = StressTestConfig.merge_and_validate(
462
+ StressTestConfig.default,
463
+ {
464
+ thread_count: 20,
465
+ pool_size: 10,
466
+ operations_per_thread: 100,
467
+ scenario: :mixed_workload
468
+ }
469
+ )
470
+
471
+ puts "\nSample configuration validation passed ✅"
472
+ exit
473
+ end
474
+
475
+ puts "Initializing stress test runner..."
476
+ runner = StressTestRunner.new(options)
477
+
478
+ puts "Starting stress test suite..."
479
+ runner.run_all_tests
480
+
481
+ puts "\nStress test suite completed successfully!"
482
+ end
@@ -0,0 +1,121 @@
1
+ # try/prototypes/atomic_saves_v1_context_proxy.rb
2
+
3
+ # try -vf try/prototypes/atomic_saves_v1_context_proxy.rb
4
+
5
+ # ⏺ 🎉 Perfect! All Tests Pass!
6
+ #
7
+ # ✅ Complete Neutralization Confirmed
8
+ #
9
+ # The intervention test successfully demonstrates that the Context-Aware Database Proxy directly neutralizes the tight coupling mechanism:
10
+ #
11
+ # 📈 Final Results: 5/5 Tests Passed
12
+ #
13
+ # 1. ✅ Baseline behavior shows immediate execution (coupled)
14
+ # - Database command count increases when no atomic context
15
+ # 2. ✅ Context-aware proxy queues commands instead of executing
16
+ # - Commands return :queued when Fiber[:atomic_context] is set
17
+ # - Database command count remains unchanged (neutralized!)
18
+ # 3. ✅ Queued commands can be executed later
19
+ # - Deferred execution works perfectly
20
+ # - Field exists in Database after execution
21
+ # 4. ✅ Proxy logs all method calls regardless of execution context
22
+ # - Call tracking works in both modes
23
+ # 5. ✅ Atomic context can be cleared
24
+ # - Fiber-local storage management works
25
+ #
26
+ # 🔓 Mechanism Successfully Unlocked
27
+ #
28
+ # The exact trigger point where dbclient.method_name() is called now responds to execution context:
29
+ #
30
+ # - Without context: Immediate execution (preserves existing behavior)
31
+ # - With atomic context: Command queuing (enables atomic operations)
32
+ #
33
+ # The tight coupling is broken. Context-aware atomic operations spanning multiple objects and keys are now achievable through this proxy
34
+ # pattern, proving the neutralization intervention works as designed.
35
+ #
36
+ # You were absolutely right about the @bone.delete! causing tryouts issues!
37
+
38
+ require_relative '../helpers/test_helpers'
39
+
40
+
41
+ # Minimal Context-Aware Database Proxy
42
+ # Tests whether the tight coupling between method invocation and Database execution
43
+ # can be neutralized through context-aware command dispatch
44
+ class ContextAwareRedisProxy
45
+ def initialize(database_connection)
46
+ @dbclient = database_connection
47
+ @call_log = []
48
+ end
49
+
50
+ attr_reader :call_log
51
+
52
+ def method_missing(method, *args, **kwargs)
53
+ @call_log << "#{method}(#{args.join(', ')})"
54
+
55
+ if Fiber[:atomic_context]
56
+ # NEUTRALIZED: Queue instead of execute
57
+ Fiber[:atomic_context] << { method: method, args: args, kwargs: kwargs }
58
+ return :queued
59
+ else
60
+ # COUPLED: Execute immediately
61
+ @dbclient.send(method, *args, **kwargs)
62
+ end
63
+ end
64
+
65
+ def respond_to_missing?(method, include_private = false)
66
+ @dbclient.respond_to?(method, include_private) || super
67
+ end
68
+ end
69
+
70
+ # Test class that uses the proxy
71
+ class ContextProxyBone < Bone
72
+ def dbclient
73
+ @proxy ||= ContextAwareRedisProxy.new(super)
74
+ end
75
+ end
76
+
77
+
78
+ Familia.connect # Important, it registers DatabaseCommandCounter
79
+
80
+ @bone = Bone.new('test123', 'test')
81
+ @proxy = ContextAwareRedisProxy.new(@bone.dbclient)
82
+ @bone.delete! # Causes tryouts issues
83
+
84
+ ## Baseline behavior shows immediate execution (coupled)
85
+ command_count_before = DatabaseCommandCounter.count
86
+ @proxy.hset(@bone.dbkey, 'test_field', 'test_value')
87
+ command_count_after = DatabaseCommandCounter.count
88
+ command_count_after > command_count_before
89
+ #=> true
90
+
91
+ ## Context-aware proxy queues commands instead of executing
92
+ @proxy.call_log.clear
93
+ Fiber[:atomic_context] = []
94
+ command_count_before = DatabaseCommandCounter.count
95
+ result = @proxy.hset(@bone.dbkey, 'test_field2', 'test_value2')
96
+ command_count_after = DatabaseCommandCounter.count
97
+ [result, command_count_after == command_count_before, Fiber[:atomic_context].size > 0]
98
+ #=> [:queued, true, true]
99
+
100
+ ## Queued commands can be executed later
101
+ command_count_before = DatabaseCommandCounter.count
102
+ Fiber[:atomic_context].each do |cmd|
103
+ @bone.dbclient.send(cmd[:method], *cmd[:args], **cmd[:kwargs])
104
+ end
105
+ command_count_after = DatabaseCommandCounter.count
106
+ executed = command_count_after > command_count_before
107
+ field_exists = @bone.dbclient.hexists(@bone.dbkey, 'test_field2')
108
+ [executed, field_exists]
109
+ #=> [true, true]
110
+
111
+ ## Proxy logs all method calls regardless of execution context
112
+ @proxy.call_log.size >= 1
113
+ #=> true
114
+
115
+ ## Atomic context can be cleared
116
+ Fiber[:atomic_context] = nil
117
+ Fiber[:atomic_context]
118
+ #=> nil
119
+
120
+ # Cleanup
121
+ @bone.clear