familia 2.0.0.pre19 → 2.0.0.pre22

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 (370) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/claude-code-review.yml +4 -9
  3. data/.github/workflows/code-smells.yml +64 -3
  4. data/.pre-commit-config.yaml +8 -6
  5. data/.reek.yml +10 -9
  6. data/.rubocop.yml +4 -0
  7. data/.talismanrc +5 -1
  8. data/CHANGELOG.rst +220 -112
  9. data/CLAUDE.md +28 -1
  10. data/Gemfile +1 -1
  11. data/Gemfile.lock +20 -17
  12. data/bin/try +16 -0
  13. data/bin/tryouts +16 -0
  14. data/docs/1106-participates_in-bidirectional-solution.md +129 -0
  15. data/docs/guides/encryption.md +486 -0
  16. data/docs/guides/feature-encrypted-fields.md +123 -7
  17. data/docs/guides/feature-expiration.md +161 -117
  18. data/docs/guides/feature-external-identifiers.md +415 -443
  19. data/docs/guides/feature-object-identifiers.md +400 -269
  20. data/docs/guides/feature-quantization.md +120 -6
  21. data/docs/guides/feature-relationships-indexing.md +318 -0
  22. data/docs/guides/feature-relationships-methods.md +146 -604
  23. data/docs/guides/feature-relationships-participation.md +263 -0
  24. data/docs/guides/feature-relationships.md +118 -136
  25. data/docs/guides/feature-system-devs.md +176 -693
  26. data/docs/guides/feature-system.md +119 -6
  27. data/docs/guides/feature-transient-fields.md +81 -0
  28. data/docs/guides/field-system.md +778 -0
  29. data/docs/guides/index.md +32 -15
  30. data/docs/guides/logging.md +187 -0
  31. data/docs/guides/optimized-loading.md +674 -0
  32. data/docs/guides/thread-safety-monitoring.md +61 -0
  33. data/docs/guides/{time-utilities.md → time-literals.md} +12 -12
  34. data/docs/migrating/v2.0.0-pre22.md +241 -0
  35. data/docs/overview.md +7 -9
  36. data/docs/reference/api-technical.md +267 -320
  37. data/examples/autoloader/mega_customer/features/deprecated_fields.rb +2 -0
  38. data/examples/autoloader/mega_customer/safe_dump_fields.rb +2 -0
  39. data/examples/autoloader/mega_customer.rb +2 -0
  40. data/examples/datatype_standalone.rb +4 -3
  41. data/examples/encrypted_fields.rb +2 -1
  42. data/examples/json_usage_patterns.rb +2 -0
  43. data/examples/relationships.rb +3 -0
  44. data/examples/safe_dump.rb +2 -1
  45. data/examples/sampling_demo.rb +53 -0
  46. data/examples/single_connection_transaction_confusions.rb +2 -1
  47. data/familia.gemspec +2 -1
  48. data/lib/familia/base.rb +2 -0
  49. data/lib/familia/connection/behavior.rb +2 -0
  50. data/lib/familia/connection/handlers.rb +2 -0
  51. data/lib/familia/connection/individual_command_proxy.rb +2 -0
  52. data/lib/familia/connection/middleware.rb +34 -24
  53. data/lib/familia/connection/operation_core.rb +3 -2
  54. data/lib/familia/connection/operations.rb +2 -0
  55. data/lib/familia/connection/pipelined_core.rb +3 -3
  56. data/lib/familia/connection/transaction_core.rb +69 -2
  57. data/lib/familia/connection.rb +18 -3
  58. data/lib/familia/data_type/class_methods.rb +3 -1
  59. data/lib/familia/data_type/connection.rb +2 -0
  60. data/lib/familia/data_type/database_commands.rb +2 -0
  61. data/lib/familia/data_type/serialization.rb +79 -52
  62. data/lib/familia/data_type/settings.rb +2 -0
  63. data/lib/familia/data_type/types/counter.rb +2 -0
  64. data/lib/familia/data_type/types/hashkey.rb +7 -5
  65. data/lib/familia/data_type/types/listkey.rb +2 -0
  66. data/lib/familia/data_type/types/lock.rb +2 -0
  67. data/lib/familia/data_type/types/sorted_set.rb +7 -10
  68. data/lib/familia/data_type/types/stringkey.rb +24 -0
  69. data/lib/familia/data_type/types/unsorted_set.rb +2 -0
  70. data/lib/familia/data_type.rb +2 -0
  71. data/lib/familia/encryption/encrypted_data.rb +4 -2
  72. data/lib/familia/encryption/manager.rb +2 -0
  73. data/lib/familia/encryption/provider.rb +2 -0
  74. data/lib/familia/encryption/providers/aes_gcm_provider.rb +2 -0
  75. data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +2 -0
  76. data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +2 -0
  77. data/lib/familia/encryption/registry.rb +2 -0
  78. data/lib/familia/encryption/request_cache.rb +2 -0
  79. data/lib/familia/encryption.rb +9 -2
  80. data/lib/familia/errors.rb +2 -0
  81. data/lib/familia/features/autoloader.rb +2 -0
  82. data/lib/familia/features/encrypted_fields/concealed_string.rb +2 -0
  83. data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +4 -0
  84. data/lib/familia/features/encrypted_fields.rb +2 -2
  85. data/lib/familia/features/expiration/extensions.rb +3 -1
  86. data/lib/familia/features/expiration.rb +12 -4
  87. data/lib/familia/features/external_identifier.rb +62 -7
  88. data/lib/familia/features/object_identifier.rb +49 -0
  89. data/lib/familia/features/quantization.rb +3 -1
  90. data/lib/familia/features/relationships/README.md +3 -1
  91. data/lib/familia/features/relationships/collection_operations.rb +2 -0
  92. data/lib/familia/features/relationships/indexing/multi_index_generators.rb +138 -9
  93. data/lib/familia/features/relationships/indexing/rebuild_strategies.rb +479 -0
  94. data/lib/familia/features/relationships/indexing/unique_index_generators.rb +97 -21
  95. data/lib/familia/features/relationships/indexing.rb +3 -0
  96. data/lib/familia/features/relationships/indexing_relationship.rb +3 -1
  97. data/lib/familia/features/relationships/participation/participant_methods.rb +131 -14
  98. data/lib/familia/features/relationships/participation/rebuild_strategies.md +41 -0
  99. data/lib/familia/features/relationships/participation/target_methods.rb +6 -6
  100. data/lib/familia/features/relationships/participation.rb +155 -69
  101. data/lib/familia/features/relationships/participation_membership.rb +69 -0
  102. data/lib/familia/features/relationships/participation_relationship.rb +34 -6
  103. data/lib/familia/features/relationships/score_encoding.rb +2 -0
  104. data/lib/familia/features/relationships.rb +5 -3
  105. data/lib/familia/features/safe_dump.rb +2 -0
  106. data/lib/familia/features/transient_fields/redacted_string.rb +2 -0
  107. data/lib/familia/features/transient_fields/single_use_redacted_string.rb +2 -0
  108. data/lib/familia/features/transient_fields/transient_field_type.rb +5 -3
  109. data/lib/familia/features/transient_fields.rb +2 -0
  110. data/lib/familia/features.rb +2 -0
  111. data/lib/familia/field_type.rb +3 -1
  112. data/lib/familia/horreum/connection.rb +17 -1
  113. data/lib/familia/horreum/database_commands.rb +8 -1
  114. data/lib/familia/horreum/definition.rb +16 -6
  115. data/lib/familia/horreum/management.rb +353 -52
  116. data/lib/familia/horreum/persistence.rb +179 -108
  117. data/lib/familia/horreum/related_fields.rb +2 -0
  118. data/lib/familia/horreum/serialization.rb +23 -4
  119. data/lib/familia/horreum/settings.rb +2 -0
  120. data/lib/familia/horreum/utils.rb +2 -0
  121. data/lib/familia/horreum.rb +15 -1
  122. data/lib/familia/identifier_extractor.rb +3 -1
  123. data/lib/familia/instrumentation.rb +156 -0
  124. data/lib/familia/json_serializer.rb +2 -0
  125. data/lib/familia/logging.rb +92 -32
  126. data/lib/familia/refinements/dear_json.rb +2 -0
  127. data/lib/familia/refinements/stylize_words.rb +2 -14
  128. data/lib/familia/refinements/time_literals.rb +2 -0
  129. data/lib/familia/refinements.rb +2 -0
  130. data/lib/familia/secure_identifier.rb +10 -2
  131. data/lib/familia/settings.rb +2 -0
  132. data/lib/familia/thread_safety/instrumented_mutex.rb +166 -0
  133. data/lib/familia/thread_safety/monitor.rb +328 -0
  134. data/lib/familia/utils.rb +13 -0
  135. data/lib/familia/verifiable_identifier.rb +3 -1
  136. data/lib/familia/version.rb +3 -1
  137. data/lib/familia.rb +31 -4
  138. data/lib/middleware/database_command_counter.rb +152 -0
  139. data/lib/middleware/database_logger.rb +295 -170
  140. data/lib/multi_result.rb +61 -31
  141. data/try/edge_cases/empty_identifiers_try.rb +2 -0
  142. data/try/edge_cases/hash_symbolization_try.rb +2 -0
  143. data/try/edge_cases/json_serialization_try.rb +2 -0
  144. data/try/edge_cases/legacy_data_detection/deserialization_edge_cases_try.rb +4 -0
  145. data/try/edge_cases/race_conditions_try.rb +4 -0
  146. data/try/edge_cases/reserved_keywords_try.rb +4 -0
  147. data/try/edge_cases/string_coercion_try.rb +2 -0
  148. data/try/edge_cases/ttl_side_effects_try.rb +4 -0
  149. data/try/features/count_any_edge_cases_try.rb +486 -0
  150. data/try/features/count_any_methods_try.rb +197 -0
  151. data/try/features/encrypted_fields/aad_protection_try.rb +4 -0
  152. data/try/features/encrypted_fields/concealed_string_core_try.rb +4 -0
  153. data/try/features/encrypted_fields/context_isolation_try.rb +4 -0
  154. data/try/features/encrypted_fields/encrypted_fields_core_try.rb +33 -0
  155. data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +4 -0
  156. data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +4 -0
  157. data/try/features/encrypted_fields/encrypted_fields_security_try.rb +4 -0
  158. data/try/features/encrypted_fields/error_conditions_try.rb +4 -0
  159. data/try/features/encrypted_fields/fresh_key_derivation_try.rb +4 -0
  160. data/try/features/encrypted_fields/fresh_key_try.rb +4 -0
  161. data/try/features/encrypted_fields/key_rotation_try.rb +4 -0
  162. data/try/features/encrypted_fields/memory_security_try.rb +4 -0
  163. data/try/features/encrypted_fields/missing_current_key_version_try.rb +4 -0
  164. data/try/features/encrypted_fields/nonce_uniqueness_try.rb +4 -0
  165. data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +4 -0
  166. data/try/features/encrypted_fields/thread_safety_try.rb +4 -0
  167. data/try/features/encrypted_fields/universal_serialization_safety_try.rb +4 -0
  168. data/try/features/encryption/config_persistence_try.rb +4 -0
  169. data/try/features/encryption/core_try.rb +4 -0
  170. data/try/features/encryption/instance_variable_scope_try.rb +4 -0
  171. data/try/features/encryption/module_loading_try.rb +4 -0
  172. data/try/features/encryption/providers/aes_gcm_provider_try.rb +4 -0
  173. data/try/features/encryption/providers/xchacha20_poly1305_provider_try.rb +4 -0
  174. data/try/features/encryption/roundtrip_validation_try.rb +4 -0
  175. data/try/features/encryption/secure_memory_handling_try.rb +4 -0
  176. data/try/features/expiration/expiration_try.rb +4 -0
  177. data/try/features/external_identifier/external_identifier_try.rb +305 -8
  178. data/try/features/feature_dependencies_try.rb +2 -0
  179. data/try/features/feature_improvements_try.rb +2 -0
  180. data/try/features/field_groups_try.rb +2 -0
  181. data/try/features/object_identifier/object_identifier_integration_try.rb +12 -9
  182. data/try/features/object_identifier/object_identifier_try.rb +140 -0
  183. data/try/features/quantization/quantization_try.rb +4 -0
  184. data/try/features/real_feature_integration_try.rb +2 -0
  185. data/try/features/relationships/indexing_commands_verification_try.rb +2 -0
  186. data/try/features/relationships/indexing_rebuild_try.rb +606 -0
  187. data/try/features/relationships/indexing_try.rb +2 -0
  188. data/try/features/relationships/participation_bidirectional_try.rb +242 -0
  189. data/try/features/relationships/participation_commands_verification_spec.rb +4 -0
  190. data/try/features/relationships/participation_commands_verification_try.rb +2 -0
  191. data/try/features/relationships/participation_performance_improvements_try.rb +11 -9
  192. data/try/features/relationships/participation_reverse_index_try.rb +15 -13
  193. data/try/features/relationships/participation_target_class_resolution_try.rb +209 -0
  194. data/try/features/relationships/participation_unresolved_target_try.rb +109 -0
  195. data/try/features/relationships/relationships_api_changes_try.rb +2 -0
  196. data/try/features/relationships/relationships_edge_cases_try.rb +4 -0
  197. data/try/features/relationships/relationships_performance_minimal_try.rb +4 -0
  198. data/try/features/relationships/relationships_performance_simple_try.rb +4 -0
  199. data/try/features/relationships/relationships_performance_try.rb +4 -0
  200. data/try/features/relationships/relationships_performance_working_try.rb +4 -0
  201. data/try/features/relationships/relationships_try.rb +6 -4
  202. data/try/features/safe_dump/safe_dump_advanced_try.rb +4 -0
  203. data/try/features/safe_dump/safe_dump_try.rb +4 -0
  204. data/try/features/transient_fields/redacted_string_try.rb +2 -0
  205. data/try/features/transient_fields/refresh_reset_try.rb +3 -0
  206. data/try/features/transient_fields/simple_refresh_test.rb +3 -0
  207. data/try/features/transient_fields/single_use_redacted_string_try.rb +2 -0
  208. data/try/features/transient_fields/transient_fields_core_try.rb +4 -0
  209. data/try/features/transient_fields/transient_fields_integration_try.rb +4 -0
  210. data/try/integration/connection/fiber_context_preservation_try.rb +4 -0
  211. data/try/integration/connection/handler_constraints_try.rb +4 -0
  212. data/try/integration/connection/isolated_dbclient_try.rb +4 -0
  213. data/try/integration/connection/middleware_reconnect_try.rb +2 -0
  214. data/try/integration/connection/operation_mode_guards_try.rb +4 -0
  215. data/try/integration/connection/pipeline_fallback_integration_try.rb +3 -0
  216. data/try/integration/connection/pools_try.rb +4 -0
  217. data/try/integration/connection/responsibility_chain_tracking_try.rb +4 -0
  218. data/try/integration/connection/transaction_fallback_integration_try.rb +4 -0
  219. data/try/integration/connection/transaction_mode_permissive_try.rb +4 -0
  220. data/try/integration/connection/transaction_mode_strict_try.rb +4 -0
  221. data/try/integration/connection/transaction_mode_warn_try.rb +4 -0
  222. data/try/integration/connection/transaction_modes_try.rb +4 -0
  223. data/try/integration/conventional_inheritance_try.rb +4 -0
  224. data/try/integration/create_method_try.rb +4 -0
  225. data/try/integration/cross_component_try.rb +4 -0
  226. data/try/integration/data_types/datatype_pipelines_try.rb +9 -3
  227. data/try/integration/data_types/datatype_transactions_try.rb +17 -7
  228. data/try/integration/database_consistency_try.rb +4 -0
  229. data/try/integration/familia_extended_try.rb +4 -0
  230. data/try/integration/familia_members_methods_try.rb +4 -0
  231. data/try/integration/models/customer_safe_dump_try.rb +4 -0
  232. data/try/integration/models/customer_try.rb +7 -3
  233. data/try/integration/models/datatype_base_try.rb +4 -0
  234. data/try/integration/models/familia_object_try.rb +4 -0
  235. data/try/integration/persistence_operations_try.rb +4 -0
  236. data/try/integration/relationships_persistence_round_trip_try.rb +17 -14
  237. data/try/integration/save_methods_consistency_try.rb +241 -0
  238. data/try/integration/scenarios_try.rb +4 -0
  239. data/try/integration/secure_identifier_try.rb +4 -0
  240. data/try/integration/transaction_safety_core_try.rb +176 -0
  241. data/try/integration/transaction_safety_workflow_try.rb +291 -0
  242. data/try/integration/verifiable_identifier_try.rb +4 -0
  243. data/try/investigation/pipeline_routing/README.md +228 -0
  244. data/try/performance/benchmarks_try.rb +4 -0
  245. data/try/performance/transaction_safety_benchmark_try.rb +238 -0
  246. data/try/support/benchmarks/deserialization_benchmark.rb +3 -1
  247. data/try/support/benchmarks/deserialization_correctness_test.rb +3 -1
  248. data/try/support/debugging/cache_behavior_tracer.rb +4 -0
  249. data/try/support/debugging/debug_aad_process.rb +3 -0
  250. data/try/support/debugging/debug_concealed_internal.rb +3 -0
  251. data/try/support/debugging/debug_concealed_reveal.rb +3 -0
  252. data/try/support/debugging/debug_context_aad.rb +3 -0
  253. data/try/support/debugging/debug_context_simple.rb +3 -0
  254. data/try/support/debugging/debug_cross_context.rb +3 -0
  255. data/try/support/debugging/debug_database_load.rb +3 -0
  256. data/try/support/debugging/debug_encrypted_json_check.rb +3 -0
  257. data/try/support/debugging/debug_encrypted_json_step_by_step.rb +3 -0
  258. data/try/support/debugging/debug_exists_lifecycle.rb +3 -0
  259. data/try/support/debugging/debug_field_decrypt.rb +3 -0
  260. data/try/support/debugging/debug_fresh_cross_context.rb +3 -0
  261. data/try/support/debugging/debug_load_path.rb +3 -0
  262. data/try/support/debugging/debug_method_definition.rb +3 -0
  263. data/try/support/debugging/debug_method_resolution.rb +3 -0
  264. data/try/support/debugging/debug_minimal.rb +3 -0
  265. data/try/support/debugging/debug_provider.rb +3 -0
  266. data/try/support/debugging/debug_secure_behavior.rb +3 -0
  267. data/try/support/debugging/debug_string_class.rb +3 -0
  268. data/try/support/debugging/debug_test.rb +3 -0
  269. data/try/support/debugging/debug_test_design.rb +3 -0
  270. data/try/support/debugging/encryption_method_tracer.rb +4 -0
  271. data/try/support/debugging/provider_diagnostics.rb +4 -0
  272. data/try/support/helpers/test_cleanup.rb +4 -0
  273. data/try/support/helpers/test_helpers.rb +5 -0
  274. data/try/support/memory/memory_basic_test.rb +4 -0
  275. data/try/support/memory/memory_detailed_test.rb +4 -0
  276. data/try/support/memory/memory_search_for_string.rb +4 -0
  277. data/try/support/memory/test_actual_redactedstring_protection.rb +4 -0
  278. data/try/support/prototypes/atomic_saves_v1_context_proxy.rb +4 -0
  279. data/try/support/prototypes/atomic_saves_v2_connection_switching.rb +4 -0
  280. data/try/support/prototypes/atomic_saves_v3_connection_pool.rb +4 -0
  281. data/try/support/prototypes/atomic_saves_v4.rb +4 -0
  282. data/try/support/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -0
  283. data/try/support/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
  284. data/try/support/prototypes/pooling/configurable_stress_test.rb +4 -0
  285. data/try/support/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
  286. data/try/support/prototypes/pooling/lib/connection_pool_metrics.rb +4 -0
  287. data/try/support/prototypes/pooling/lib/connection_pool_stress_test.rb +4 -0
  288. data/try/support/prototypes/pooling/lib/connection_pool_threading_models.rb +4 -0
  289. data/try/support/prototypes/pooling/lib/visualize_stress_results.rb +4 -2
  290. data/try/support/prototypes/pooling/pool_siege.rb +4 -2
  291. data/try/support/prototypes/pooling/run_stress_tests.rb +4 -2
  292. data/try/thread_safety/README.md +496 -0
  293. data/try/thread_safety/class_connection_chain_race_try.rb +265 -0
  294. data/try/thread_safety/connection_chain_race_try.rb +148 -0
  295. data/try/thread_safety/encryption_manager_cache_race_try.rb +166 -0
  296. data/try/thread_safety/feature_registry_race_try.rb +226 -0
  297. data/try/thread_safety/fiber_pipeline_isolation_try.rb +235 -0
  298. data/try/thread_safety/fiber_transaction_isolation_try.rb +208 -0
  299. data/try/thread_safety/field_registration_race_try.rb +222 -0
  300. data/try/thread_safety/logger_initialization_race_try.rb +170 -0
  301. data/try/thread_safety/middleware_registration_race_try.rb +154 -0
  302. data/try/thread_safety/module_config_race_try.rb +175 -0
  303. data/try/thread_safety/secure_identifier_cache_race_try.rb +226 -0
  304. data/try/unit/core/autoloader_try.rb +4 -0
  305. data/try/unit/core/base_enhancements_try.rb +4 -0
  306. data/try/unit/core/connection_try.rb +4 -0
  307. data/try/unit/core/errors_try.rb +4 -0
  308. data/try/unit/core/extensions_try.rb +4 -0
  309. data/try/unit/core/familia_logger_try.rb +2 -0
  310. data/try/unit/core/familia_try.rb +4 -0
  311. data/try/unit/core/middleware_sampling_try.rb +335 -0
  312. data/try/unit/core/middleware_test_helpers_bug_try.rb +58 -0
  313. data/try/unit/core/middleware_thread_safety_try.rb +245 -0
  314. data/try/unit/core/middleware_try.rb +4 -0
  315. data/try/unit/core/settings_try.rb +4 -0
  316. data/try/unit/core/time_utils_try.rb +4 -0
  317. data/try/unit/core/tools_try.rb +4 -0
  318. data/try/unit/core/utils_try.rb +37 -0
  319. data/try/unit/data_types/boolean_try.rb +39 -22
  320. data/try/unit/data_types/counter_try.rb +4 -0
  321. data/try/unit/data_types/datatype_base_try.rb +4 -0
  322. data/try/unit/data_types/hash_try.rb +6 -2
  323. data/try/unit/data_types/list_try.rb +4 -0
  324. data/try/unit/data_types/lock_try.rb +4 -0
  325. data/try/unit/data_types/serialization_try.rb +386 -0
  326. data/try/unit/data_types/sorted_set_try.rb +4 -0
  327. data/try/unit/data_types/sorted_set_zadd_options_try.rb +4 -0
  328. data/try/unit/data_types/string_try.rb +4 -0
  329. data/try/unit/data_types/unsortedset_try.rb +4 -0
  330. data/try/unit/familia_resolve_class_try.rb +116 -0
  331. data/try/unit/horreum/auto_indexing_on_save_try.rb +5 -1
  332. data/try/unit/horreum/automatic_index_validation_try.rb +2 -0
  333. data/try/unit/horreum/base_try.rb +4 -0
  334. data/try/unit/horreum/class_methods_try.rb +4 -0
  335. data/try/unit/horreum/commands_try.rb +4 -0
  336. data/try/unit/horreum/defensive_initialization_try.rb +4 -0
  337. data/try/unit/horreum/destroy_related_fields_cleanup_try.rb +6 -1
  338. data/try/unit/horreum/enhanced_conflict_handling_try.rb +4 -0
  339. data/try/unit/horreum/field_categories_try.rb +4 -0
  340. data/try/unit/horreum/field_definition_try.rb +4 -0
  341. data/try/unit/horreum/initialization_try.rb +4 -0
  342. data/try/unit/horreum/json_type_preservation_try.rb +2 -0
  343. data/try/unit/horreum/optimized_loading_try.rb +156 -0
  344. data/try/unit/horreum/relations_try.rb +4 -0
  345. data/try/unit/horreum/serialization_persistent_fields_try.rb +4 -0
  346. data/try/unit/horreum/serialization_try.rb +4 -0
  347. data/try/unit/horreum/settings_try.rb +4 -0
  348. data/try/unit/horreum/unique_index_edge_cases_try.rb +4 -0
  349. data/try/unit/horreum/unique_index_guard_validation_try.rb +2 -0
  350. data/try/unit/middleware/database_command_counter_methods_try.rb +139 -0
  351. data/try/unit/middleware/database_logger_methods_try.rb +251 -0
  352. data/try/unit/refinements/dear_json_array_methods_try.rb +4 -0
  353. data/try/unit/refinements/dear_json_hash_methods_try.rb +4 -0
  354. data/try/unit/refinements/time_literals_numeric_methods_try.rb +4 -0
  355. data/try/unit/refinements/time_literals_string_methods_try.rb +4 -0
  356. data/try/unit/thread_safety_monitor_try.rb +149 -0
  357. metadata +69 -17
  358. data/.github/workflows/code-quality.yml +0 -138
  359. data/changelog.d/20251011_012003_delano_159_datatype_transaction_pipeline_support.rst +0 -91
  360. data/changelog.d/20251011_203905_delano_next.rst +0 -30
  361. data/changelog.d/20251011_212633_delano_next.rst +0 -13
  362. data/changelog.d/20251011_221253_delano_next.rst +0 -26
  363. data/docs/archive/FAMILIA_RELATIONSHIPS.md +0 -210
  364. data/docs/archive/FAMILIA_TECHNICAL.md +0 -823
  365. data/docs/archive/FAMILIA_UPDATE.md +0 -226
  366. data/docs/archive/README.md +0 -64
  367. data/docs/archive/api-reference.md +0 -333
  368. data/docs/guides/core-field-system.md +0 -806
  369. data/docs/guides/implementation.md +0 -276
  370. data/docs/guides/security-model.md +0 -183
@@ -0,0 +1,222 @@
1
+ # try/thread_safety/field_registration_race_try.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
5
+ require_relative '../support/helpers/test_helpers'
6
+
7
+ # Thread safety tests for field registration and collections
8
+ #
9
+ # Tests concurrent field definition to ensure that lazy initialization
10
+ # of @fields, @field_types, and @field_groups collections doesn't result
11
+ # in corruption or missing field definitions.
12
+ #
13
+ # These tests verify:
14
+ # 1. Concurrent field definitions on same class
15
+ # 2. Concurrent field group registrations
16
+ # 3. Concurrent DataType field registrations (list, set, zset, hashkey)
17
+ # 4. Field inheritance during concurrent subclass creation
18
+
19
+ ## Concurrent field definitions on same class
20
+ class ConcurrentFieldModel < Familia::Horreum
21
+ identifier_field :test_id
22
+ field :test_id
23
+
24
+ def init
25
+ @test_id ||= SecureRandom.hex(4)
26
+ end
27
+ end
28
+
29
+ barrier = Concurrent::CyclicBarrier.new(50)
30
+ field_names = Concurrent::Array.new
31
+
32
+ threads = 50.times.map do |i|
33
+ Thread.new do
34
+ barrier.wait
35
+ field_name = "field_#{i}".to_sym
36
+ ConcurrentFieldModel.field(field_name)
37
+ field_names << field_name
38
+ end
39
+ end
40
+
41
+ threads.each(&:join)
42
+ field_names.size
43
+ #=> 50
44
+
45
+ ## Concurrent field group registrations
46
+ class GroupedFieldModel < Familia::Horreum
47
+ identifier_field :test_id
48
+ field :test_id
49
+
50
+ def init
51
+ @test_id ||= SecureRandom.hex(4)
52
+ end
53
+ end
54
+
55
+ barrier = Concurrent::CyclicBarrier.new(20)
56
+ groups = Concurrent::Array.new
57
+
58
+ threads = 20.times.map do |i|
59
+ Thread.new do
60
+ barrier.wait
61
+ field_name = "field_#{i}".to_sym
62
+ GroupedFieldModel.field(field_name)
63
+ groups << field_name
64
+ end
65
+ end
66
+
67
+ threads.each(&:join)
68
+ groups.size
69
+ #=> 20
70
+
71
+ ## Concurrent DataType field registrations (list, set, zset, hashkey)
72
+ class DataTypeFieldModel < Familia::Horreum
73
+ identifier_field :test_id
74
+ field :test_id
75
+
76
+ def init
77
+ @test_id ||= SecureRandom.hex(4)
78
+ end
79
+ end
80
+
81
+ barrier = Concurrent::CyclicBarrier.new(40)
82
+ datatypes = Concurrent::Array.new
83
+
84
+ threads = 40.times.map do |i|
85
+ Thread.new do
86
+ barrier.wait
87
+ case i % 4
88
+ when 0
89
+ DataTypeFieldModel.list("list_#{i}".to_sym)
90
+ datatypes << :list
91
+ when 1
92
+ DataTypeFieldModel.set("set_#{i}".to_sym)
93
+ datatypes << :set
94
+ when 2
95
+ DataTypeFieldModel.zset("zset_#{i}".to_sym)
96
+ datatypes << :zset
97
+ when 3
98
+ DataTypeFieldModel.hashkey("hash_#{i}".to_sym)
99
+ datatypes << :hashkey
100
+ end
101
+ end
102
+ end
103
+
104
+ threads.each(&:join)
105
+ datatypes.count(:list)
106
+ #=> 10
107
+
108
+ ## Field type tracking during concurrent registration
109
+ class TypeTrackedModel < Familia::Horreum
110
+ identifier_field :test_id
111
+ field :test_id
112
+
113
+ def init
114
+ @test_id ||= SecureRandom.hex(4)
115
+ end
116
+ end
117
+
118
+ barrier = Concurrent::CyclicBarrier.new(30)
119
+ field_types = Concurrent::Array.new
120
+
121
+ threads = 30.times.map do |i|
122
+ Thread.new do
123
+ barrier.wait
124
+ field_name = "typed_field_#{i}".to_sym
125
+ TypeTrackedModel.field(field_name)
126
+ field_types << TypeTrackedModel.field_types[field_name]
127
+ end
128
+ end
129
+
130
+ threads.each(&:join)
131
+ field_types.size
132
+ #=> 30
133
+
134
+ ## Concurrent field definitions with default values
135
+ class DefaultValueModel < Familia::Horreum
136
+ identifier_field :test_id
137
+ field :test_id
138
+
139
+ def init
140
+ @test_id ||= SecureRandom.hex(4)
141
+ end
142
+ end
143
+
144
+ barrier = Concurrent::CyclicBarrier.new(25)
145
+ defaults = Concurrent::Array.new
146
+
147
+ threads = 25.times.map do |i|
148
+ Thread.new do
149
+ barrier.wait
150
+ field_name = "default_field_#{i}".to_sym
151
+ DefaultValueModel.field(field_name)
152
+ defaults << field_name
153
+ end
154
+ end
155
+
156
+ threads.each(&:join)
157
+ defaults.size
158
+ #=> 25
159
+
160
+ ## Field registration during object instantiation
161
+ class InstantiationModel < Familia::Horreum
162
+ identifier_field :test_id
163
+ field :test_id
164
+
165
+ def init
166
+ @test_id ||= SecureRandom.hex(4)
167
+ end
168
+ field :name
169
+ field :value
170
+ end
171
+
172
+ barrier = Concurrent::CyclicBarrier.new(30)
173
+ instances = Concurrent::Array.new
174
+
175
+ threads = 30.times.map do |i|
176
+ Thread.new do
177
+ barrier.wait
178
+ obj = InstantiationModel.new(name: "obj_#{i}", value: i)
179
+ instances << obj.name
180
+ end
181
+ end
182
+
183
+ threads.each(&:join)
184
+ instances.size
185
+ #=> 30
186
+
187
+ ## Concurrent class-level DataType registrations
188
+ class ClassDataTypeModel < Familia::Horreum
189
+ identifier_field :test_id
190
+ field :test_id
191
+
192
+ def init
193
+ @test_id ||= SecureRandom.hex(4)
194
+ end
195
+ end
196
+
197
+ barrier = Concurrent::CyclicBarrier.new(20)
198
+ class_types = Concurrent::Array.new
199
+
200
+ threads = 20.times.map do |i|
201
+ Thread.new do
202
+ barrier.wait
203
+ case i % 4
204
+ when 0
205
+ ClassDataTypeModel.class_list("class_list_#{i}".to_sym)
206
+ class_types << :class_list
207
+ when 1
208
+ ClassDataTypeModel.class_set("class_set_#{i}".to_sym)
209
+ class_types << :class_set
210
+ when 2
211
+ ClassDataTypeModel.class_zset("class_zset_#{i}".to_sym)
212
+ class_types << :class_zset
213
+ when 3
214
+ ClassDataTypeModel.class_hashkey("class_hash_#{i}".to_sym)
215
+ class_types << :class_hashkey
216
+ end
217
+ end
218
+ end
219
+
220
+ threads.each(&:join)
221
+ class_types.count(:class_list)
222
+ #=> 5
@@ -0,0 +1,170 @@
1
+ #
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../support/helpers/test_helpers'
5
+
6
+ # Thread safety tests for logger lazy initialization
7
+ #
8
+ # Tests concurrent logger access to ensure that only a single logger
9
+ # instance is created even under high concurrent load.
10
+ #
11
+ # These tests verify:
12
+ # 1. Concurrent logger initialization (default logger)
13
+ # 2. Logger singleton property
14
+ # 3. Maximum contention with CyclicBarrier pattern
15
+ # 4. Custom logger setter thread safety
16
+
17
+ ## Concurrent logger initialization with 50 threads
18
+ # Reset the logger and mutex to simulate first access
19
+ Familia.instance_variable_set(:@logger, nil)
20
+ # Mutex is now initialized eagerly in Logging.extended hook
21
+
22
+ barrier = Concurrent::CyclicBarrier.new(50)
23
+ loggers = Concurrent::Array.new
24
+
25
+ threads = 50.times.map do
26
+ Thread.new do
27
+ barrier.wait
28
+ # Get the logger instance
29
+ logger = Familia.logger
30
+ loggers << logger.object_id
31
+ end
32
+ end
33
+
34
+ threads.each(&:join)
35
+
36
+ # All threads should get the same logger instance
37
+ [loggers.any?(nil), loggers.uniq.size, loggers.size]
38
+ #=> [false, 1, 50]
39
+
40
+
41
+ ## Logger is instance of FamiliaLogger
42
+ Familia.instance_variable_set(:@logger, nil)
43
+ # Mutex is now initialized eagerly in Logging.extended hook
44
+
45
+ logger = Familia.logger
46
+ logger.class.name
47
+ #=> 'Familia::FamiliaLogger'
48
+
49
+
50
+ ## Logger has correct progname set
51
+ Familia.instance_variable_set(:@logger, nil)
52
+ # Mutex is now initialized eagerly in Logging.extended hook
53
+
54
+ logger = Familia.logger
55
+ logger.progname
56
+ #=> 'Familia'
57
+
58
+
59
+ ## Logger has correct formatter set
60
+ Familia.instance_variable_set(:@logger, nil)
61
+ # Mutex is now initialized eagerly in Logging.extended hook
62
+
63
+ logger = Familia.logger
64
+ logger.formatter.class.name
65
+ #=> 'Familia::LogFormatter'
66
+
67
+
68
+ ## Maximum contention with concurrent logging operations
69
+ # Reset logger and test actual logging under concurrent load
70
+ Familia.instance_variable_set(:@logger, nil)
71
+ # Mutex is now initialized eagerly in Logging.extended hook
72
+
73
+ barrier = Concurrent::CyclicBarrier.new(50)
74
+ logger_ids = Concurrent::Array.new
75
+ errors = Concurrent::Array.new
76
+
77
+ threads = 50.times.map do |i|
78
+ Thread.new do
79
+ begin
80
+ barrier.wait
81
+ # Each thread logs a message
82
+ logger = Familia.logger
83
+ logger_ids << logger.object_id
84
+
85
+ # Capture output to avoid cluttering test output
86
+ original_stderr = $stderr
87
+ $stderr = StringIO.new
88
+ logger.info "Thread #{i} logging"
89
+ $stderr = original_stderr
90
+ rescue => e
91
+ errors << e.message
92
+ end
93
+ end
94
+ end
95
+
96
+ threads.each(&:join)
97
+
98
+ # All threads should use same logger instance and have no errors
99
+ [logger_ids.uniq.size, errors.empty?]
100
+ #=> [1, true]
101
+
102
+
103
+ ## Custom logger setter clears atomic reference
104
+ # Set a custom logger
105
+ custom_logger = Logger.new($stderr)
106
+ custom_logger.progname = 'CustomApp'
107
+
108
+ Familia.logger = custom_logger
109
+
110
+ # Getting logger should return the custom one
111
+ retrieved_logger = Familia.logger
112
+ [retrieved_logger.object_id == custom_logger.object_id, retrieved_logger.progname]
113
+ #=> [true, 'CustomApp']
114
+
115
+
116
+ ## Setting logger multiple times is thread-safe
117
+ # Reset to default logger first
118
+ Familia.instance_variable_set(:@logger, nil)
119
+ # Mutex is now initialized eagerly in Logging.extended hook
120
+ Familia.logger # Initialize default
121
+
122
+ barrier = Concurrent::CyclicBarrier.new(20)
123
+ final_loggers = Concurrent::Array.new
124
+
125
+ threads = 20.times.map do |i|
126
+ Thread.new do
127
+ barrier.wait
128
+ # Half threads set new logger, half read
129
+ if i.even?
130
+ custom = Logger.new($stderr)
131
+ custom.progname = "Thread#{i}"
132
+ Familia.logger = custom
133
+ else
134
+ final_loggers << Familia.logger.object_id
135
+ end
136
+ end
137
+ end
138
+
139
+ threads.each(&:join)
140
+
141
+ # After concurrent setter/getter operations, logger should still be valid
142
+ final_logger = Familia.logger
143
+ final_logger.class
144
+ #=> Logger
145
+
146
+
147
+ ## Rapid sequential access maintains singleton
148
+ Familia.instance_variable_set(:@logger, nil)
149
+ # Mutex is now initialized eagerly in Logging.extended hook
150
+
151
+ logger_ids = []
152
+ 100.times do
153
+ logger_ids << Familia.logger.object_id
154
+ end
155
+
156
+ logger_ids.uniq.size
157
+ #=> 1
158
+
159
+
160
+ ## Mutex is used for thread safety
161
+ Familia.instance_variable_set(:@logger, nil)
162
+ # Mutex is now initialized eagerly in Logging.extended hook
163
+
164
+ # Trigger lazy initialization
165
+ Familia.logger
166
+
167
+ # Should have created a Mutex for synchronization
168
+ mutex = Familia.instance_variable_get(:@logger_mutex)
169
+ mutex.class.name
170
+ #=> 'Thread::Mutex'
@@ -0,0 +1,154 @@
1
+ # try/thread_safety/middleware_registration_race_try.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
5
+ require_relative '../support/helpers/test_helpers'
6
+
7
+ # Thread safety tests for middleware registration
8
+ #
9
+ # Tests concurrent middleware registration to ensure that race conditions
10
+ # in the check-then-act pattern don't result in duplicate middleware
11
+ # registration or corrupted middleware chains.
12
+ #
13
+ # These tests verify:
14
+ # 1. Concurrent reconnect with middleware enabled
15
+ # 2. Concurrent middleware version increment atomicity
16
+ # 3. Middleware state consistency under concurrent access
17
+
18
+ @original_logger_flag = nil
19
+ @original_counter_flag = nil
20
+ @original_middleware_registered = nil
21
+ @original_logger_enabled = nil
22
+ @original_counter_enabled = nil
23
+
24
+ # Setup: Store original middleware state
25
+ @original_logger_flag = Familia.instance_variable_get(:@logger_registered)
26
+ @original_counter_flag = Familia.instance_variable_get(:@counter_registered)
27
+ @original_middleware_registered = Familia.instance_variable_get(:@middleware_registered)
28
+ @original_logger_enabled = Familia.enable_database_logging
29
+ @original_counter_enabled = Familia.enable_database_counter
30
+
31
+ ## Concurrent reconnect calls with middleware enabled
32
+ Familia.enable_database_logging = true
33
+ Familia.enable_database_counter = true
34
+ barrier = Concurrent::CyclicBarrier.new(20)
35
+ results = Concurrent::Array.new
36
+
37
+ threads = 20.times.map do
38
+ Thread.new do
39
+ barrier.wait
40
+ Familia.reconnect!
41
+ results << Familia.instance_variable_get(:@middleware_registered)
42
+ end
43
+ end
44
+
45
+ threads.each(&:join)
46
+ results.size
47
+ #=> 20
48
+
49
+ ## Concurrent middleware version increments preserve all updates
50
+ initial_version = Familia.middleware_version
51
+ barrier = Concurrent::CyclicBarrier.new(100)
52
+
53
+ threads = 100.times.map do
54
+ Thread.new do
55
+ barrier.wait
56
+ Familia.increment_middleware_version!
57
+ end
58
+ end
59
+
60
+ threads.each(&:join)
61
+ Familia.middleware_version - initial_version
62
+ #=> 100
63
+
64
+ ## Concurrent reconnect and version check
65
+ Familia.enable_database_logging = true
66
+ barrier = Concurrent::CyclicBarrier.new(15)
67
+ versions = Concurrent::Array.new
68
+
69
+ threads = 15.times.map do |i|
70
+ Thread.new do
71
+ barrier.wait
72
+ if i < 5
73
+ Familia.reconnect!
74
+ end
75
+ versions << Familia.middleware_version
76
+ end
77
+ end
78
+
79
+ threads.each(&:join)
80
+ versions.size
81
+ #=> 15
82
+
83
+ ## Concurrent middleware state reads during reconnects
84
+ barrier = Concurrent::CyclicBarrier.new(30)
85
+ state_reads = Concurrent::Array.new
86
+
87
+ threads = 30.times.map do |i|
88
+ Thread.new do
89
+ barrier.wait
90
+ if i < 10
91
+ Familia.reconnect!
92
+ end
93
+ state_reads << [
94
+ Familia.instance_variable_get(:@logger_registered),
95
+ Familia.instance_variable_get(:@counter_registered)
96
+ ]
97
+ end
98
+ end
99
+
100
+ threads.each(&:join)
101
+ state_reads.size
102
+ #=> 30
103
+
104
+ ## Rapid sequential reconnects from multiple threads
105
+ barrier = Concurrent::CyclicBarrier.new(10)
106
+ reconnect_counts = Concurrent::Array.new
107
+
108
+ threads = 10.times.map do
109
+ Thread.new do
110
+ barrier.wait
111
+ count = 0
112
+ 10.times do
113
+ Familia.reconnect!
114
+ count += 1
115
+ end
116
+ reconnect_counts << count
117
+ end
118
+ end
119
+
120
+ threads.each(&:join)
121
+ reconnect_counts.all? { |c| c == 10 }
122
+ #=> true
123
+
124
+ ## Concurrent middleware enable/disable during reconnect
125
+ Familia.enable_database_logging = false
126
+ Familia.enable_database_counter = false
127
+ barrier = Concurrent::CyclicBarrier.new(25)
128
+ enable_results = Concurrent::Array.new
129
+
130
+ threads = 25.times.map do |i|
131
+ Thread.new do
132
+ barrier.wait
133
+ case i % 3
134
+ when 0
135
+ Familia.enable_database_logging = true
136
+ when 1
137
+ Familia.enable_database_counter = true
138
+ when 2
139
+ Familia.reconnect!
140
+ end
141
+ enable_results << :completed
142
+ end
143
+ end
144
+
145
+ threads.each(&:join)
146
+ enable_results.size
147
+ #=> 25
148
+
149
+ # Teardown: Restore original middleware state
150
+ Familia.instance_variable_set(:@logger_registered, @original_logger_flag)
151
+ Familia.instance_variable_set(:@counter_registered, @original_counter_flag)
152
+ Familia.instance_variable_set(:@middleware_registered, @original_middleware_registered)
153
+ Familia.enable_database_logging = @original_logger_enabled
154
+ Familia.enable_database_counter = @original_counter_enabled
@@ -0,0 +1,175 @@
1
+ # try/thread_safety/module_config_race_try.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
5
+ require_relative '../support/helpers/test_helpers'
6
+
7
+ # Thread safety tests for module-level configuration state
8
+ #
9
+ # Tests concurrent modification of Familia module configuration to ensure
10
+ # that race conditions don't cause inconsistent behavior or invalid state.
11
+ #
12
+ # These tests verify:
13
+ # 1. Concurrent URI configuration changes
14
+ # 2. Concurrent prefix/suffix configuration
15
+ # 3. Concurrent delimiter configuration during key generation
16
+ # 4. Configuration consistency across threads
17
+
18
+ @original_uri = nil
19
+ @original_prefix = nil
20
+ @original_suffix = nil
21
+ @original_delim = nil
22
+
23
+ # Setup: Store original configuration
24
+ @original_uri = Familia.uri
25
+ @original_prefix = Familia.prefix
26
+ @original_suffix = Familia.suffix
27
+ @original_delim = Familia.delim
28
+
29
+ ## Concurrent URI configuration changes result in valid state
30
+ barrier = Concurrent::CyclicBarrier.new(20)
31
+ uris = Concurrent::Array.new
32
+
33
+ threads = 20.times.map do |i|
34
+ Thread.new do
35
+ barrier.wait
36
+ new_uri = "redis://localhost:#{6379 + i}"
37
+ Familia.uri = new_uri
38
+ uris << Familia.uri.to_s
39
+ end
40
+ end
41
+
42
+ threads.each(&:join)
43
+ uris.size
44
+ #=> 20
45
+
46
+ ## Concurrent prefix configuration maintains valid state
47
+ Familia.prefix = nil
48
+ barrier = Concurrent::CyclicBarrier.new(30)
49
+ prefixes = Concurrent::Array.new
50
+
51
+ threads = 30.times.map do |i|
52
+ Thread.new do
53
+ barrier.wait
54
+ Familia.prefix = "prefix_#{i}"
55
+ prefixes << Familia.prefix
56
+ end
57
+ end
58
+
59
+ threads.each(&:join)
60
+ prefixes.size
61
+ #=> 30
62
+
63
+ ## Concurrent suffix configuration maintains valid state
64
+ Familia.suffix = :object
65
+ barrier = Concurrent::CyclicBarrier.new(30)
66
+ suffixes = Concurrent::Array.new
67
+
68
+ threads = 30.times.map do |i|
69
+ Thread.new do
70
+ barrier.wait
71
+ Familia.suffix = "suffix_#{i}".to_sym
72
+ suffixes << Familia.suffix
73
+ end
74
+ end
75
+
76
+ threads.each(&:join)
77
+ suffixes.size
78
+ #=> 30
79
+
80
+ ## Concurrent delimiter configuration during key generation
81
+ class DelimiterTestModel < Familia::Horreum
82
+ identifier_field :test_id
83
+ field :test_id
84
+ field :value
85
+ end
86
+
87
+ Familia.delim = ':'
88
+ barrier = Concurrent::CyclicBarrier.new(30)
89
+ keys = Concurrent::Array.new
90
+
91
+ threads = 30.times.map do |i|
92
+ Thread.new do
93
+ barrier.wait
94
+ if i % 3 == 0
95
+ Familia.delim = '::'
96
+ end
97
+ obj = DelimiterTestModel.new(test_id: "id_#{i}", value: "test")
98
+ keys << obj.dbkey
99
+ end
100
+ end
101
+
102
+ threads.each(&:join)
103
+ keys.size
104
+ #=> 30
105
+
106
+ ## Concurrent prefix and suffix changes together
107
+ Familia.prefix = nil
108
+ Familia.suffix = :object
109
+ barrier = Concurrent::CyclicBarrier.new(50)
110
+ configs = Concurrent::Array.new
111
+
112
+ threads = 50.times.map do |i|
113
+ Thread.new do
114
+ barrier.wait
115
+ if i.even?
116
+ Familia.prefix = "prefix_#{i}"
117
+ else
118
+ Familia.suffix = "suffix_#{i}"
119
+ end
120
+ configs << [Familia.prefix, Familia.suffix]
121
+ end
122
+ end
123
+
124
+ threads.each(&:join)
125
+ configs.size
126
+ #=> 50
127
+
128
+ ## Configuration reads during concurrent writes are consistent
129
+ Familia.prefix = 'test'
130
+ barrier = Concurrent::CyclicBarrier.new(40)
131
+ read_results = Concurrent::Array.new
132
+
133
+ threads = 40.times.map do |i|
134
+ Thread.new do
135
+ barrier.wait
136
+ if i < 5
137
+ # A few threads write
138
+ Familia.prefix = "new_prefix_#{i}"
139
+ else
140
+ # Most threads read
141
+ read_results << Familia.prefix
142
+ end
143
+ end
144
+ end
145
+
146
+ threads.each(&:join)
147
+ read_results.size
148
+ #=> 35
149
+
150
+ ## Concurrent URI and connection provider changes
151
+ @original_provider = Familia.connection_provider
152
+ barrier = Concurrent::CyclicBarrier.new(20)
153
+ results = Concurrent::Array.new
154
+
155
+ threads = 20.times.map do |i|
156
+ Thread.new do
157
+ barrier.wait
158
+ if i.even?
159
+ Familia.uri = "redis://localhost:#{6379 + i}"
160
+ else
161
+ results << Familia.uri.to_s
162
+ end
163
+ end
164
+ end
165
+
166
+ threads.each(&:join)
167
+ results.size
168
+ #=> 10
169
+
170
+ # Teardown: Restore original configuration
171
+ Familia.connection_provider = @original_provider
172
+ Familia.uri = @original_uri
173
+ Familia.prefix = @original_prefix
174
+ Familia.suffix = @original_suffix
175
+ Familia.delim = @original_delim