familia 2.0.0.pre19 → 2.0.0.pre21
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/.github/workflows/claude-code-review.yml +4 -9
- data/.github/workflows/code-smells.yml +64 -3
- data/.pre-commit-config.yaml +8 -6
- data/.reek.yml +10 -9
- data/.rubocop.yml +4 -0
- data/CHANGELOG.rst +177 -112
- data/CLAUDE.md +28 -1
- data/Gemfile +1 -1
- data/Gemfile.lock +20 -17
- data/bin/try +16 -0
- data/bin/tryouts +16 -0
- data/changelog.d/20251105_flexible_external_identifier_format.rst +66 -0
- data/changelog.d/20251107_112554_delano_179_participation_asymmetry.rst +44 -0
- data/changelog.d/20251107_213121_delano_fix_thread_safety_races_011CUumCP492Twxm4NLt2FvL.rst +20 -0
- data/changelog.d/20251107_fix_participates_in_symbol_resolution.rst +91 -0
- data/changelog.d/20251107_optimized_redis_exists_checks.rst +94 -0
- data/changelog.d/20251108_frozen_string_literal_pragma.rst +44 -0
- data/docs/1106-participates_in-bidirectional-solution.md +129 -0
- data/docs/guides/encryption.md +486 -0
- data/docs/guides/feature-encrypted-fields.md +123 -7
- data/docs/guides/feature-expiration.md +161 -117
- data/docs/guides/feature-external-identifiers.md +415 -443
- data/docs/guides/feature-object-identifiers.md +400 -269
- data/docs/guides/feature-quantization.md +120 -6
- data/docs/guides/feature-relationships-indexing.md +318 -0
- data/docs/guides/feature-relationships-methods.md +146 -604
- data/docs/guides/feature-relationships-participation.md +263 -0
- data/docs/guides/feature-relationships.md +118 -136
- data/docs/guides/feature-system-devs.md +176 -693
- data/docs/guides/feature-system.md +119 -6
- data/docs/guides/feature-transient-fields.md +81 -0
- data/docs/guides/field-system.md +778 -0
- data/docs/guides/index.md +32 -15
- data/docs/guides/logging.md +187 -0
- data/docs/guides/optimized-loading.md +674 -0
- data/docs/guides/thread-safety-monitoring.md +61 -0
- data/docs/guides/{time-utilities.md → time-literals.md} +12 -12
- data/docs/migrating/v2.0.0-pre22.md +241 -0
- data/docs/overview.md +7 -9
- data/docs/reference/api-technical.md +267 -320
- data/examples/autoloader/mega_customer/features/deprecated_fields.rb +2 -0
- data/examples/autoloader/mega_customer/safe_dump_fields.rb +2 -0
- data/examples/autoloader/mega_customer.rb +2 -0
- data/examples/datatype_standalone.rb +4 -3
- data/examples/encrypted_fields.rb +2 -1
- data/examples/json_usage_patterns.rb +2 -0
- data/examples/relationships.rb +3 -0
- data/examples/safe_dump.rb +2 -1
- data/examples/sampling_demo.rb +53 -0
- data/examples/single_connection_transaction_confusions.rb +2 -1
- data/familia.gemspec +2 -1
- data/lib/familia/base.rb +2 -0
- data/lib/familia/connection/behavior.rb +2 -0
- data/lib/familia/connection/handlers.rb +2 -0
- data/lib/familia/connection/individual_command_proxy.rb +2 -0
- data/lib/familia/connection/middleware.rb +34 -24
- data/lib/familia/connection/operation_core.rb +2 -0
- data/lib/familia/connection/operations.rb +2 -0
- data/lib/familia/connection/pipelined_core.rb +2 -0
- data/lib/familia/connection/transaction_core.rb +68 -0
- data/lib/familia/connection.rb +18 -3
- data/lib/familia/data_type/class_methods.rb +3 -1
- data/lib/familia/data_type/connection.rb +2 -0
- data/lib/familia/data_type/database_commands.rb +2 -0
- data/lib/familia/data_type/serialization.rb +6 -4
- data/lib/familia/data_type/settings.rb +2 -0
- data/lib/familia/data_type/types/counter.rb +2 -0
- data/lib/familia/data_type/types/hashkey.rb +7 -5
- data/lib/familia/data_type/types/listkey.rb +2 -0
- data/lib/familia/data_type/types/lock.rb +2 -0
- data/lib/familia/data_type/types/sorted_set.rb +2 -0
- data/lib/familia/data_type/types/stringkey.rb +2 -0
- data/lib/familia/data_type/types/unsorted_set.rb +2 -0
- data/lib/familia/data_type.rb +2 -0
- data/lib/familia/encryption/encrypted_data.rb +4 -2
- data/lib/familia/encryption/manager.rb +2 -0
- data/lib/familia/encryption/provider.rb +2 -0
- data/lib/familia/encryption/providers/aes_gcm_provider.rb +2 -0
- data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +2 -0
- data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +2 -0
- data/lib/familia/encryption/registry.rb +2 -0
- data/lib/familia/encryption/request_cache.rb +2 -0
- data/lib/familia/encryption.rb +9 -2
- data/lib/familia/errors.rb +2 -0
- data/lib/familia/features/autoloader.rb +2 -0
- data/lib/familia/features/encrypted_fields/concealed_string.rb +2 -0
- data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +4 -0
- data/lib/familia/features/encrypted_fields.rb +2 -2
- data/lib/familia/features/expiration/extensions.rb +3 -1
- data/lib/familia/features/expiration.rb +12 -4
- data/lib/familia/features/external_identifier.rb +33 -7
- data/lib/familia/features/object_identifier.rb +2 -0
- data/lib/familia/features/quantization.rb +3 -1
- data/lib/familia/features/relationships/README.md +3 -1
- data/lib/familia/features/relationships/collection_operations.rb +2 -0
- data/lib/familia/features/relationships/indexing/multi_index_generators.rb +138 -9
- data/lib/familia/features/relationships/indexing/rebuild_strategies.rb +479 -0
- data/lib/familia/features/relationships/indexing/unique_index_generators.rb +89 -21
- data/lib/familia/features/relationships/indexing.rb +3 -0
- data/lib/familia/features/relationships/indexing_relationship.rb +3 -1
- data/lib/familia/features/relationships/participation/participant_methods.rb +131 -14
- data/lib/familia/features/relationships/participation/rebuild_strategies.md +41 -0
- data/lib/familia/features/relationships/participation/target_methods.rb +6 -6
- data/lib/familia/features/relationships/participation.rb +155 -69
- data/lib/familia/features/relationships/participation_membership.rb +69 -0
- data/lib/familia/features/relationships/participation_relationship.rb +34 -6
- data/lib/familia/features/relationships/score_encoding.rb +2 -0
- data/lib/familia/features/relationships.rb +5 -3
- data/lib/familia/features/safe_dump.rb +2 -0
- data/lib/familia/features/transient_fields/redacted_string.rb +2 -0
- data/lib/familia/features/transient_fields/single_use_redacted_string.rb +2 -0
- data/lib/familia/features/transient_fields/transient_field_type.rb +5 -3
- data/lib/familia/features/transient_fields.rb +2 -0
- data/lib/familia/features.rb +2 -0
- data/lib/familia/field_type.rb +3 -1
- data/lib/familia/horreum/connection.rb +17 -1
- data/lib/familia/horreum/database_commands.rb +2 -0
- data/lib/familia/horreum/definition.rb +16 -6
- data/lib/familia/horreum/management.rb +212 -42
- data/lib/familia/horreum/persistence.rb +176 -108
- data/lib/familia/horreum/related_fields.rb +2 -0
- data/lib/familia/horreum/serialization.rb +23 -4
- data/lib/familia/horreum/settings.rb +2 -0
- data/lib/familia/horreum/utils.rb +2 -0
- data/lib/familia/horreum.rb +15 -1
- data/lib/familia/identifier_extractor.rb +2 -0
- data/lib/familia/instrumentation.rb +156 -0
- data/lib/familia/json_serializer.rb +2 -0
- data/lib/familia/logging.rb +92 -32
- data/lib/familia/refinements/dear_json.rb +2 -0
- data/lib/familia/refinements/stylize_words.rb +2 -14
- data/lib/familia/refinements/time_literals.rb +2 -0
- data/lib/familia/refinements.rb +2 -0
- data/lib/familia/secure_identifier.rb +10 -2
- data/lib/familia/settings.rb +2 -0
- data/lib/familia/thread_safety/instrumented_mutex.rb +166 -0
- data/lib/familia/thread_safety/monitor.rb +328 -0
- data/lib/familia/utils.rb +13 -0
- data/lib/familia/verifiable_identifier.rb +3 -1
- data/lib/familia/version.rb +3 -1
- data/lib/familia.rb +31 -4
- data/lib/middleware/database_command_counter.rb +152 -0
- data/lib/middleware/database_logger.rb +295 -170
- data/lib/multi_result.rb +2 -0
- data/try/edge_cases/empty_identifiers_try.rb +2 -0
- data/try/edge_cases/hash_symbolization_try.rb +2 -0
- data/try/edge_cases/json_serialization_try.rb +2 -0
- data/try/edge_cases/legacy_data_detection/deserialization_edge_cases_try.rb +4 -0
- data/try/edge_cases/race_conditions_try.rb +4 -0
- data/try/edge_cases/reserved_keywords_try.rb +4 -0
- data/try/edge_cases/string_coercion_try.rb +2 -0
- data/try/edge_cases/ttl_side_effects_try.rb +4 -0
- data/try/features/encrypted_fields/aad_protection_try.rb +4 -0
- data/try/features/encrypted_fields/concealed_string_core_try.rb +4 -0
- data/try/features/encrypted_fields/context_isolation_try.rb +4 -0
- data/try/features/encrypted_fields/encrypted_fields_core_try.rb +33 -0
- data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +4 -0
- data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +4 -0
- data/try/features/encrypted_fields/encrypted_fields_security_try.rb +4 -0
- data/try/features/encrypted_fields/error_conditions_try.rb +4 -0
- data/try/features/encrypted_fields/fresh_key_derivation_try.rb +4 -0
- data/try/features/encrypted_fields/fresh_key_try.rb +4 -0
- data/try/features/encrypted_fields/key_rotation_try.rb +4 -0
- data/try/features/encrypted_fields/memory_security_try.rb +4 -0
- data/try/features/encrypted_fields/missing_current_key_version_try.rb +4 -0
- data/try/features/encrypted_fields/nonce_uniqueness_try.rb +4 -0
- data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +4 -0
- data/try/features/encrypted_fields/thread_safety_try.rb +4 -0
- data/try/features/encrypted_fields/universal_serialization_safety_try.rb +4 -0
- data/try/features/encryption/config_persistence_try.rb +4 -0
- data/try/features/encryption/core_try.rb +4 -0
- data/try/features/encryption/instance_variable_scope_try.rb +4 -0
- data/try/features/encryption/module_loading_try.rb +4 -0
- data/try/features/encryption/providers/aes_gcm_provider_try.rb +4 -0
- data/try/features/encryption/providers/xchacha20_poly1305_provider_try.rb +4 -0
- data/try/features/encryption/roundtrip_validation_try.rb +4 -0
- data/try/features/encryption/secure_memory_handling_try.rb +4 -0
- data/try/features/expiration/expiration_try.rb +4 -0
- data/try/features/external_identifier/external_identifier_try.rb +171 -8
- data/try/features/feature_dependencies_try.rb +2 -0
- data/try/features/feature_improvements_try.rb +2 -0
- data/try/features/field_groups_try.rb +2 -0
- data/try/features/object_identifier/object_identifier_integration_try.rb +12 -9
- data/try/features/object_identifier/object_identifier_try.rb +2 -0
- data/try/features/quantization/quantization_try.rb +4 -0
- data/try/features/real_feature_integration_try.rb +2 -0
- data/try/features/relationships/indexing_commands_verification_try.rb +2 -0
- data/try/features/relationships/indexing_rebuild_try.rb +600 -0
- data/try/features/relationships/indexing_try.rb +2 -0
- data/try/features/relationships/participation_bidirectional_try.rb +242 -0
- data/try/features/relationships/participation_commands_verification_spec.rb +4 -0
- data/try/features/relationships/participation_commands_verification_try.rb +2 -0
- data/try/features/relationships/participation_performance_improvements_try.rb +11 -9
- data/try/features/relationships/participation_reverse_index_try.rb +15 -13
- data/try/features/relationships/participation_target_class_resolution_try.rb +209 -0
- data/try/features/relationships/participation_unresolved_target_try.rb +109 -0
- data/try/features/relationships/relationships_api_changes_try.rb +2 -0
- data/try/features/relationships/relationships_edge_cases_try.rb +4 -0
- data/try/features/relationships/relationships_performance_minimal_try.rb +4 -0
- data/try/features/relationships/relationships_performance_simple_try.rb +4 -0
- data/try/features/relationships/relationships_performance_try.rb +4 -0
- data/try/features/relationships/relationships_performance_working_try.rb +4 -0
- data/try/features/relationships/relationships_try.rb +6 -4
- data/try/features/safe_dump/safe_dump_advanced_try.rb +4 -0
- data/try/features/safe_dump/safe_dump_try.rb +4 -0
- data/try/features/transient_fields/redacted_string_try.rb +2 -0
- data/try/features/transient_fields/refresh_reset_try.rb +3 -0
- data/try/features/transient_fields/simple_refresh_test.rb +3 -0
- data/try/features/transient_fields/single_use_redacted_string_try.rb +2 -0
- data/try/features/transient_fields/transient_fields_core_try.rb +4 -0
- data/try/features/transient_fields/transient_fields_integration_try.rb +4 -0
- data/try/integration/connection/fiber_context_preservation_try.rb +4 -0
- data/try/integration/connection/handler_constraints_try.rb +4 -0
- data/try/integration/connection/isolated_dbclient_try.rb +4 -0
- data/try/integration/connection/middleware_reconnect_try.rb +2 -0
- data/try/integration/connection/operation_mode_guards_try.rb +4 -0
- data/try/integration/connection/pipeline_fallback_integration_try.rb +3 -0
- data/try/integration/connection/pools_try.rb +4 -0
- data/try/integration/connection/responsibility_chain_tracking_try.rb +4 -0
- data/try/integration/connection/transaction_fallback_integration_try.rb +4 -0
- data/try/integration/connection/transaction_mode_permissive_try.rb +4 -0
- data/try/integration/connection/transaction_mode_strict_try.rb +4 -0
- data/try/integration/connection/transaction_mode_warn_try.rb +4 -0
- data/try/integration/connection/transaction_modes_try.rb +4 -0
- data/try/integration/conventional_inheritance_try.rb +4 -0
- data/try/integration/create_method_try.rb +4 -0
- data/try/integration/cross_component_try.rb +4 -0
- data/try/integration/data_types/datatype_pipelines_try.rb +4 -0
- data/try/integration/data_types/datatype_transactions_try.rb +4 -0
- data/try/integration/database_consistency_try.rb +4 -0
- data/try/integration/familia_extended_try.rb +4 -0
- data/try/integration/familia_members_methods_try.rb +4 -0
- data/try/integration/models/customer_safe_dump_try.rb +4 -0
- data/try/integration/models/customer_try.rb +4 -0
- data/try/integration/models/datatype_base_try.rb +4 -0
- data/try/integration/models/familia_object_try.rb +4 -0
- data/try/integration/persistence_operations_try.rb +4 -0
- data/try/integration/relationships_persistence_round_trip_try.rb +17 -14
- data/try/integration/save_methods_consistency_try.rb +241 -0
- data/try/integration/scenarios_try.rb +4 -0
- data/try/integration/secure_identifier_try.rb +4 -0
- data/try/integration/transaction_safety_core_try.rb +176 -0
- data/try/integration/transaction_safety_workflow_try.rb +291 -0
- data/try/integration/verifiable_identifier_try.rb +4 -0
- data/try/investigation/pipeline_routing/README.md +228 -0
- data/try/performance/benchmarks_try.rb +4 -0
- data/try/performance/transaction_safety_benchmark_try.rb +238 -0
- data/try/support/benchmarks/deserialization_benchmark.rb +3 -1
- data/try/support/benchmarks/deserialization_correctness_test.rb +3 -1
- data/try/support/debugging/cache_behavior_tracer.rb +4 -0
- data/try/support/debugging/debug_aad_process.rb +3 -0
- data/try/support/debugging/debug_concealed_internal.rb +3 -0
- data/try/support/debugging/debug_concealed_reveal.rb +3 -0
- data/try/support/debugging/debug_context_aad.rb +3 -0
- data/try/support/debugging/debug_context_simple.rb +3 -0
- data/try/support/debugging/debug_cross_context.rb +3 -0
- data/try/support/debugging/debug_database_load.rb +3 -0
- data/try/support/debugging/debug_encrypted_json_check.rb +3 -0
- data/try/support/debugging/debug_encrypted_json_step_by_step.rb +3 -0
- data/try/support/debugging/debug_exists_lifecycle.rb +3 -0
- data/try/support/debugging/debug_field_decrypt.rb +3 -0
- data/try/support/debugging/debug_fresh_cross_context.rb +3 -0
- data/try/support/debugging/debug_load_path.rb +3 -0
- data/try/support/debugging/debug_method_definition.rb +3 -0
- data/try/support/debugging/debug_method_resolution.rb +3 -0
- data/try/support/debugging/debug_minimal.rb +3 -0
- data/try/support/debugging/debug_provider.rb +3 -0
- data/try/support/debugging/debug_secure_behavior.rb +3 -0
- data/try/support/debugging/debug_string_class.rb +3 -0
- data/try/support/debugging/debug_test.rb +3 -0
- data/try/support/debugging/debug_test_design.rb +3 -0
- data/try/support/debugging/encryption_method_tracer.rb +4 -0
- data/try/support/debugging/provider_diagnostics.rb +4 -0
- data/try/support/helpers/test_cleanup.rb +4 -0
- data/try/support/helpers/test_helpers.rb +5 -0
- data/try/support/memory/memory_basic_test.rb +4 -0
- data/try/support/memory/memory_detailed_test.rb +4 -0
- data/try/support/memory/memory_search_for_string.rb +4 -0
- data/try/support/memory/test_actual_redactedstring_protection.rb +4 -0
- data/try/support/prototypes/atomic_saves_v1_context_proxy.rb +4 -0
- data/try/support/prototypes/atomic_saves_v2_connection_switching.rb +4 -0
- data/try/support/prototypes/atomic_saves_v3_connection_pool.rb +4 -0
- data/try/support/prototypes/atomic_saves_v4.rb +4 -0
- data/try/support/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -0
- data/try/support/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
- data/try/support/prototypes/pooling/configurable_stress_test.rb +4 -0
- data/try/support/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
- data/try/support/prototypes/pooling/lib/connection_pool_metrics.rb +4 -0
- data/try/support/prototypes/pooling/lib/connection_pool_stress_test.rb +4 -0
- data/try/support/prototypes/pooling/lib/connection_pool_threading_models.rb +4 -0
- data/try/support/prototypes/pooling/lib/visualize_stress_results.rb +4 -2
- data/try/support/prototypes/pooling/pool_siege.rb +4 -2
- data/try/support/prototypes/pooling/run_stress_tests.rb +4 -2
- data/try/thread_safety/README.md +496 -0
- data/try/thread_safety/class_connection_chain_race_try.rb +265 -0
- data/try/thread_safety/connection_chain_race_try.rb +148 -0
- data/try/thread_safety/encryption_manager_cache_race_try.rb +166 -0
- data/try/thread_safety/feature_registry_race_try.rb +226 -0
- data/try/thread_safety/fiber_pipeline_isolation_try.rb +235 -0
- data/try/thread_safety/fiber_transaction_isolation_try.rb +208 -0
- data/try/thread_safety/field_registration_race_try.rb +222 -0
- data/try/thread_safety/logger_initialization_race_try.rb +170 -0
- data/try/thread_safety/middleware_registration_race_try.rb +154 -0
- data/try/thread_safety/module_config_race_try.rb +175 -0
- data/try/thread_safety/secure_identifier_cache_race_try.rb +226 -0
- data/try/unit/core/autoloader_try.rb +4 -0
- data/try/unit/core/base_enhancements_try.rb +4 -0
- data/try/unit/core/connection_try.rb +4 -0
- data/try/unit/core/errors_try.rb +4 -0
- data/try/unit/core/extensions_try.rb +4 -0
- data/try/unit/core/familia_logger_try.rb +2 -0
- data/try/unit/core/familia_try.rb +4 -0
- data/try/unit/core/middleware_sampling_try.rb +335 -0
- data/try/unit/core/middleware_test_helpers_bug_try.rb +58 -0
- data/try/unit/core/middleware_thread_safety_try.rb +245 -0
- data/try/unit/core/middleware_try.rb +4 -0
- data/try/unit/core/settings_try.rb +4 -0
- data/try/unit/core/time_utils_try.rb +4 -0
- data/try/unit/core/tools_try.rb +4 -0
- data/try/unit/core/utils_try.rb +37 -0
- data/try/unit/data_types/boolean_try.rb +4 -0
- data/try/unit/data_types/counter_try.rb +4 -0
- data/try/unit/data_types/datatype_base_try.rb +4 -0
- data/try/unit/data_types/hash_try.rb +4 -0
- data/try/unit/data_types/list_try.rb +4 -0
- data/try/unit/data_types/lock_try.rb +4 -0
- data/try/unit/data_types/sorted_set_try.rb +4 -0
- data/try/unit/data_types/sorted_set_zadd_options_try.rb +4 -0
- data/try/unit/data_types/string_try.rb +4 -0
- data/try/unit/data_types/unsortedset_try.rb +4 -0
- data/try/unit/familia_resolve_class_try.rb +116 -0
- data/try/unit/horreum/auto_indexing_on_save_try.rb +5 -1
- data/try/unit/horreum/automatic_index_validation_try.rb +2 -0
- data/try/unit/horreum/base_try.rb +4 -0
- data/try/unit/horreum/class_methods_try.rb +4 -0
- data/try/unit/horreum/commands_try.rb +4 -0
- data/try/unit/horreum/defensive_initialization_try.rb +4 -0
- data/try/unit/horreum/destroy_related_fields_cleanup_try.rb +4 -0
- data/try/unit/horreum/enhanced_conflict_handling_try.rb +4 -0
- data/try/unit/horreum/field_categories_try.rb +4 -0
- data/try/unit/horreum/field_definition_try.rb +4 -0
- data/try/unit/horreum/initialization_try.rb +4 -0
- data/try/unit/horreum/json_type_preservation_try.rb +2 -0
- data/try/unit/horreum/optimized_loading_try.rb +156 -0
- data/try/unit/horreum/relations_try.rb +4 -0
- data/try/unit/horreum/serialization_persistent_fields_try.rb +4 -0
- data/try/unit/horreum/serialization_try.rb +4 -0
- data/try/unit/horreum/settings_try.rb +4 -0
- data/try/unit/horreum/unique_index_edge_cases_try.rb +4 -0
- data/try/unit/horreum/unique_index_guard_validation_try.rb +2 -0
- data/try/unit/middleware/database_command_counter_methods_try.rb +139 -0
- data/try/unit/middleware/database_logger_methods_try.rb +251 -0
- data/try/unit/refinements/dear_json_array_methods_try.rb +4 -0
- data/try/unit/refinements/dear_json_hash_methods_try.rb +4 -0
- data/try/unit/refinements/time_literals_numeric_methods_try.rb +4 -0
- data/try/unit/refinements/time_literals_string_methods_try.rb +4 -0
- data/try/unit/thread_safety_monitor_try.rb +149 -0
- metadata +72 -17
- data/.github/workflows/code-quality.yml +0 -138
- data/changelog.d/20251011_012003_delano_159_datatype_transaction_pipeline_support.rst +0 -91
- data/changelog.d/20251011_203905_delano_next.rst +0 -30
- data/changelog.d/20251011_212633_delano_next.rst +0 -13
- data/changelog.d/20251011_221253_delano_next.rst +0 -26
- data/docs/archive/FAMILIA_RELATIONSHIPS.md +0 -210
- data/docs/archive/FAMILIA_TECHNICAL.md +0 -823
- data/docs/archive/FAMILIA_UPDATE.md +0 -226
- data/docs/archive/README.md +0 -64
- data/docs/archive/api-reference.md +0 -333
- data/docs/guides/core-field-system.md +0 -806
- data/docs/guides/implementation.md +0 -276
- 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
|