familia 2.0.0.pre18 → 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 +205 -88
- data/CLAUDE.md +62 -10
- data/Gemfile +3 -3
- data/Gemfile.lock +27 -62
- data/README.md +39 -0
- 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 +177 -133
- 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-pre19.md +197 -0
- 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 +282 -0
- 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 +254 -0
- data/lib/familia/connection/handlers.rb +97 -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 +3 -1
- data/lib/familia/connection/operations.rb +2 -0
- data/lib/familia/connection/{pipeline_core.rb → pipelined_core.rb} +4 -2
- data/lib/familia/connection/transaction_core.rb +75 -9
- data/lib/familia/connection.rb +21 -5
- data/lib/familia/data_type/class_methods.rb +3 -1
- data/lib/familia/data_type/connection.rb +153 -7
- data/lib/familia/data_type/database_commands.rb +9 -4
- data/lib/familia/data_type/serialization.rb +10 -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 +8 -6
- 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 +53 -14
- 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 +11 -11
- data/lib/familia/features/expiration.rb +29 -21
- 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 +177 -47
- data/lib/familia/features/relationships/indexing/rebuild_strategies.rb +479 -0
- data/lib/familia/features/relationships/indexing/unique_index_generators.rb +203 -63
- data/lib/familia/features/relationships/indexing.rb +40 -42
- data/lib/familia/features/relationships/indexing_relationship.rb +17 -5
- 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 +5 -2
- data/lib/familia/horreum/connection.rb +28 -36
- data/lib/familia/horreum/database_commands.rb +131 -10
- data/lib/familia/horreum/definition.rb +18 -7
- data/lib/familia/horreum/management.rb +233 -57
- data/lib/familia/horreum/persistence.rb +314 -122
- data/lib/familia/horreum/related_fields.rb +2 -0
- data/lib/familia/horreum/serialization.rb +26 -4
- data/lib/familia/horreum/settings.rb +2 -0
- data/lib/familia/horreum/utils.rb +2 -8
- data/lib/familia/horreum.rb +46 -13
- 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 +94 -37
- 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 +9 -7
- 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 +325 -129
- 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 +6 -4
- 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 +5 -1
- 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 +30 -4
- 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 +6 -4
- 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 +7 -3
- 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 +5 -1
- data/try/integration/connection/pipeline_fallback_integration_try.rb +15 -12
- 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 +26 -22
- data/try/integration/cross_component_try.rb +4 -0
- data/try/integration/data_types/datatype_pipelines_try.rb +108 -0
- data/try/integration/data_types/datatype_transactions_try.rb +251 -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 +9 -1
- 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 +5 -1
- data/try/integration/persistence_operations_try.rb +166 -10
- 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 +5 -1
- 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 +5 -1
- 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 +36 -16
- data/try/unit/horreum/automatic_index_validation_try.rb +255 -0
- data/try/unit/horreum/base_try.rb +5 -1
- data/try/unit/horreum/class_methods_try.rb +6 -2
- 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 +5 -1
- 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 +8 -4
- data/try/unit/horreum/serialization_persistent_fields_try.rb +4 -0
- data/try/unit/horreum/serialization_try.rb +6 -2
- data/try/unit/horreum/settings_try.rb +4 -0
- data/try/unit/horreum/unique_index_edge_cases_try.rb +380 -0
- data/try/unit/horreum/unique_index_guard_validation_try.rb +283 -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 +81 -14
- data/.github/workflows/code-quality.yml +0 -138
- 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,265 @@
|
|
|
1
|
+
# try/thread_safety/class_connection_chain_race_try.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
require_relative '../support/helpers/test_helpers'
|
|
6
|
+
|
|
7
|
+
# Thread safety tests for class-level connection chain initialization
|
|
8
|
+
#
|
|
9
|
+
# Tests concurrent class-level connection chain initialization to ensure
|
|
10
|
+
# that each Horreum subclass properly initializes its own connection chain
|
|
11
|
+
# without race conditions or shared state issues.
|
|
12
|
+
#
|
|
13
|
+
# These tests verify:
|
|
14
|
+
# 1. Concurrent class-level connection chain initialization
|
|
15
|
+
# 2. Multiple model classes initialized concurrently
|
|
16
|
+
# 3. Inheritance chain with concurrent access
|
|
17
|
+
# 4. Connection chain isolation between classes
|
|
18
|
+
|
|
19
|
+
## Concurrent class-level connection chain initialization
|
|
20
|
+
class TestModel1 < Familia::Horreum
|
|
21
|
+
identifier_field :test_id
|
|
22
|
+
field :test_id
|
|
23
|
+
|
|
24
|
+
def init
|
|
25
|
+
@test_id ||= SecureRandom.hex(4)
|
|
26
|
+
end
|
|
27
|
+
field :name
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
TestModel1.instance_variable_set(:@class_connection_chain, nil)
|
|
31
|
+
# Mutex is now initialized eagerly in Connection.included hook
|
|
32
|
+
barrier = Concurrent::CyclicBarrier.new(50)
|
|
33
|
+
chains = Concurrent::Array.new
|
|
34
|
+
|
|
35
|
+
threads = 50.times.map do
|
|
36
|
+
Thread.new do
|
|
37
|
+
barrier.wait
|
|
38
|
+
client = TestModel1.dbclient
|
|
39
|
+
# Store the actual connection chain object ID to detect singleton violations
|
|
40
|
+
chain = TestModel1.instance_variable_get(:@class_connection_chain)
|
|
41
|
+
chains << chain.object_id
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
threads.each(&:join)
|
|
46
|
+
# Test multiple invariants (pattern from middleware tests):
|
|
47
|
+
# - No nil entries (corruption check)
|
|
48
|
+
# - All chains are same object (singleton property)
|
|
49
|
+
# - Got expected number of results
|
|
50
|
+
[chains.any?(nil), chains.uniq.size, chains.size]
|
|
51
|
+
#=> [false, 1, 50]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
## Multiple model classes initialized concurrently
|
|
55
|
+
barrier = Concurrent::CyclicBarrier.new(10)
|
|
56
|
+
models = Concurrent::Array.new
|
|
57
|
+
|
|
58
|
+
threads = 10.times.map do |i|
|
|
59
|
+
Thread.new do
|
|
60
|
+
model_class = Class.new(Familia::Horreum) do
|
|
61
|
+
def self.name
|
|
62
|
+
"ConcurrentTestModel#{Thread.current.object_id}"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
identifier_field :test_id
|
|
66
|
+
field :test_id
|
|
67
|
+
field :value
|
|
68
|
+
|
|
69
|
+
def init
|
|
70
|
+
@test_id ||= SecureRandom.hex(4)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
barrier.wait
|
|
75
|
+
client = model_class.dbclient
|
|
76
|
+
models << [model_class.name, client.class.name]
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
threads.each(&:join)
|
|
81
|
+
models.size
|
|
82
|
+
#=> 10
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
## Connection chain per-class isolation
|
|
86
|
+
class TestModel2 < Familia::Horreum
|
|
87
|
+
identifier_field :test_id
|
|
88
|
+
field :test_id
|
|
89
|
+
|
|
90
|
+
def init
|
|
91
|
+
@test_id ||= SecureRandom.hex(4)
|
|
92
|
+
end
|
|
93
|
+
field :name
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
class TestModel3 < Familia::Horreum
|
|
97
|
+
identifier_field :test_id
|
|
98
|
+
field :test_id
|
|
99
|
+
|
|
100
|
+
def init
|
|
101
|
+
@test_id ||= SecureRandom.hex(4)
|
|
102
|
+
end
|
|
103
|
+
field :name
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
TestModel2.instance_variable_set(:@class_connection_chain, nil)
|
|
107
|
+
# Mutex is now initialized eagerly in Connection.included hook
|
|
108
|
+
TestModel3.instance_variable_set(:@class_connection_chain, nil)
|
|
109
|
+
# Mutex is now initialized eagerly in Connection.included hook
|
|
110
|
+
|
|
111
|
+
barrier = Concurrent::CyclicBarrier.new(20)
|
|
112
|
+
results = Concurrent::Array.new
|
|
113
|
+
|
|
114
|
+
threads = 20.times.map do |i|
|
|
115
|
+
Thread.new do
|
|
116
|
+
barrier.wait
|
|
117
|
+
if i.even?
|
|
118
|
+
client = TestModel2.dbclient
|
|
119
|
+
results << [:model2, client.class.name]
|
|
120
|
+
else
|
|
121
|
+
client = TestModel3.dbclient
|
|
122
|
+
results << [:model3, client.class.name]
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
threads.each(&:join)
|
|
128
|
+
results.size
|
|
129
|
+
#=> 20
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
## Inheritance chain with concurrent access
|
|
133
|
+
class BaseModel < Familia::Horreum
|
|
134
|
+
identifier_field :test_id
|
|
135
|
+
field :test_id
|
|
136
|
+
|
|
137
|
+
def init
|
|
138
|
+
@test_id ||= SecureRandom.hex(4)
|
|
139
|
+
end
|
|
140
|
+
field :base_field
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
class ChildModel1 < BaseModel
|
|
144
|
+
field :child_field
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
class ChildModel2 < BaseModel
|
|
148
|
+
field :other_field
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
ChildModel1.instance_variable_set(:@class_connection_chain, nil)
|
|
152
|
+
# Mutex is now initialized eagerly in Connection.included hook
|
|
153
|
+
ChildModel2.instance_variable_set(:@class_connection_chain, nil)
|
|
154
|
+
# Mutex is now initialized eagerly in Connection.included hook
|
|
155
|
+
|
|
156
|
+
barrier = Concurrent::CyclicBarrier.new(30)
|
|
157
|
+
chains = Concurrent::Array.new
|
|
158
|
+
|
|
159
|
+
threads = 30.times.map do |i|
|
|
160
|
+
Thread.new do
|
|
161
|
+
barrier.wait
|
|
162
|
+
case i % 3
|
|
163
|
+
when 0
|
|
164
|
+
client = BaseModel.dbclient
|
|
165
|
+
chains << [:base, client.class.name]
|
|
166
|
+
when 1
|
|
167
|
+
client = ChildModel1.dbclient
|
|
168
|
+
chains << [:child1, client.class.name]
|
|
169
|
+
when 2
|
|
170
|
+
client = ChildModel2.dbclient
|
|
171
|
+
chains << [:child2, client.class.name]
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
threads.each(&:join)
|
|
177
|
+
chains.size
|
|
178
|
+
#=> 30
|
|
179
|
+
|
|
180
|
+
## Concurrent database operations through class connection chain
|
|
181
|
+
class OperationTestModel < Familia::Horreum
|
|
182
|
+
identifier_field :test_id
|
|
183
|
+
field :test_id
|
|
184
|
+
field :value
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
barrier = Concurrent::CyclicBarrier.new(25)
|
|
188
|
+
results = Concurrent::Array.new
|
|
189
|
+
|
|
190
|
+
threads = 25.times.map do |i|
|
|
191
|
+
Thread.new do
|
|
192
|
+
barrier.wait
|
|
193
|
+
obj = OperationTestModel.new(test_id: "test_#{i}", value: i)
|
|
194
|
+
obj.save
|
|
195
|
+
results << obj.test_id
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
threads.each(&:join)
|
|
200
|
+
results.size
|
|
201
|
+
#=> 25
|
|
202
|
+
|
|
203
|
+
## Class connection chain rebuilds after reconnect
|
|
204
|
+
class ReconnectTestModel < Familia::Horreum
|
|
205
|
+
identifier_field :test_id
|
|
206
|
+
field :test_id
|
|
207
|
+
|
|
208
|
+
def init
|
|
209
|
+
@test_id ||= SecureRandom.hex(4)
|
|
210
|
+
end
|
|
211
|
+
field :name
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
ReconnectTestModel.instance_variable_set(:@class_connection_chain, nil)
|
|
215
|
+
# Mutex is now initialized eagerly in Connection.included hook
|
|
216
|
+
barrier = Concurrent::CyclicBarrier.new(20)
|
|
217
|
+
results = Concurrent::Array.new
|
|
218
|
+
|
|
219
|
+
threads = 20.times.map do |i|
|
|
220
|
+
Thread.new do
|
|
221
|
+
barrier.wait
|
|
222
|
+
if i < 5
|
|
223
|
+
# Some threads trigger reconnect
|
|
224
|
+
Familia.reconnect!
|
|
225
|
+
end
|
|
226
|
+
# All threads access the chain
|
|
227
|
+
client = ReconnectTestModel.dbclient
|
|
228
|
+
results << client.class.name
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
threads.each(&:join)
|
|
233
|
+
results.size
|
|
234
|
+
#=> 20
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
## Rapid sequential access to class connection chain
|
|
238
|
+
class RapidAccessModel < Familia::Horreum
|
|
239
|
+
identifier_field :test_id
|
|
240
|
+
field :test_id
|
|
241
|
+
|
|
242
|
+
def init
|
|
243
|
+
@test_id ||= SecureRandom.hex(4)
|
|
244
|
+
end
|
|
245
|
+
field :value
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
barrier = Concurrent::CyclicBarrier.new(15)
|
|
249
|
+
access_counts = Concurrent::Array.new
|
|
250
|
+
|
|
251
|
+
threads = 15.times.map do
|
|
252
|
+
Thread.new do
|
|
253
|
+
barrier.wait
|
|
254
|
+
count = 0
|
|
255
|
+
20.times do
|
|
256
|
+
RapidAccessModel.dbclient
|
|
257
|
+
count += 1
|
|
258
|
+
end
|
|
259
|
+
access_counts << count
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
threads.each(&:join)
|
|
264
|
+
access_counts.size
|
|
265
|
+
#=> 15
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# try/thread_safety/connection_chain_race_try.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
require_relative '../support/helpers/test_helpers'
|
|
6
|
+
|
|
7
|
+
# Thread safety tests for connection chain lazy initialization
|
|
8
|
+
#
|
|
9
|
+
# Tests concurrent connection chain initialization to ensure that the
|
|
10
|
+
# lazy initialization pattern (@connection_chain ||= build_connection_chain)
|
|
11
|
+
# doesn't result in duplicate chain instances or inconsistent state.
|
|
12
|
+
#
|
|
13
|
+
# These tests verify:
|
|
14
|
+
# 1. Concurrent module-level connection chain initialization
|
|
15
|
+
# 2. Connection chain consistency after concurrent reconnect
|
|
16
|
+
# 3. Thread-safe chain access during reconnection
|
|
17
|
+
|
|
18
|
+
@original_chain = nil
|
|
19
|
+
|
|
20
|
+
# Setup: Store original connection chain
|
|
21
|
+
@original_chain = Familia.instance_variable_get(:@connection_chain)
|
|
22
|
+
|
|
23
|
+
## Concurrent connection chain initialization builds only one chain
|
|
24
|
+
Familia.instance_variable_set(:@connection_chain, nil)
|
|
25
|
+
barrier = Concurrent::CyclicBarrier.new(50)
|
|
26
|
+
chain_ids = Concurrent::Array.new
|
|
27
|
+
|
|
28
|
+
threads = 50.times.map do
|
|
29
|
+
Thread.new do
|
|
30
|
+
barrier.wait
|
|
31
|
+
Familia.dbclient('redis://127.0.0.1:6379') # Trigger chain initialization
|
|
32
|
+
chain_ids << Familia.instance_variable_get(:@connection_chain).object_id
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
threads.each(&:join)
|
|
37
|
+
# Test multiple invariants:
|
|
38
|
+
# - No nil entries (array corruption check from middleware tests)
|
|
39
|
+
# - All threads see same connection chain object (singleton property)
|
|
40
|
+
[chain_ids.any?(nil), chain_ids.uniq.size]
|
|
41
|
+
#=> [false, 1]
|
|
42
|
+
|
|
43
|
+
## Connection chain remains functional after concurrent access
|
|
44
|
+
Familia.instance_variable_set(:@connection_chain, nil)
|
|
45
|
+
barrier = Concurrent::CyclicBarrier.new(30)
|
|
46
|
+
results = Concurrent::Array.new
|
|
47
|
+
|
|
48
|
+
threads = 30.times.map do
|
|
49
|
+
Thread.new do
|
|
50
|
+
barrier.wait
|
|
51
|
+
begin
|
|
52
|
+
client = Familia.dbclient
|
|
53
|
+
result = client.call('PING')
|
|
54
|
+
results << result
|
|
55
|
+
rescue => e
|
|
56
|
+
results << e.class.name
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
threads.each(&:join)
|
|
62
|
+
# Test multiple invariants (pattern from middleware tests):
|
|
63
|
+
# - No nil entries (corruption check)
|
|
64
|
+
# - All successful PONG responses (correctness check)
|
|
65
|
+
[results.any?(nil), results.all? { |r| r == 'PONG' }]
|
|
66
|
+
#=> [false, true]
|
|
67
|
+
|
|
68
|
+
## Concurrent reconnect calls maintain chain consistency
|
|
69
|
+
barrier = Concurrent::CyclicBarrier.new(20)
|
|
70
|
+
errors = Concurrent::Array.new
|
|
71
|
+
successes = Concurrent::Array.new
|
|
72
|
+
|
|
73
|
+
threads = 20.times.map do |i|
|
|
74
|
+
Thread.new do
|
|
75
|
+
barrier.wait
|
|
76
|
+
begin
|
|
77
|
+
if i < 10
|
|
78
|
+
# Half the threads reconnect
|
|
79
|
+
Familia.reconnect!
|
|
80
|
+
successes << :reconnect
|
|
81
|
+
else
|
|
82
|
+
# Half access the chain
|
|
83
|
+
client = Familia.dbclient
|
|
84
|
+
client.call('PING')
|
|
85
|
+
successes << :ping
|
|
86
|
+
end
|
|
87
|
+
rescue => e
|
|
88
|
+
errors << e.class.name
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
threads.each(&:join)
|
|
94
|
+
(successes.size >= 15)
|
|
95
|
+
#=> true
|
|
96
|
+
|
|
97
|
+
## Connection chain rebuilds correctly after nil assignment
|
|
98
|
+
original = Familia.instance_variable_get(:@connection_chain)
|
|
99
|
+
barrier = Concurrent::CyclicBarrier.new(40)
|
|
100
|
+
results = Concurrent::Array.new
|
|
101
|
+
|
|
102
|
+
threads = 40.times.map do |i|
|
|
103
|
+
Thread.new do
|
|
104
|
+
barrier.wait
|
|
105
|
+
if i == 0
|
|
106
|
+
# One thread clears the chain
|
|
107
|
+
Familia.instance_variable_set(:@connection_chain, nil)
|
|
108
|
+
end
|
|
109
|
+
# All threads try to use the chain
|
|
110
|
+
begin
|
|
111
|
+
client = Familia.dbclient
|
|
112
|
+
results << client.class.name
|
|
113
|
+
rescue => e
|
|
114
|
+
results << e.class.name
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
threads.each(&:join)
|
|
120
|
+
# Test multiple invariants:
|
|
121
|
+
# - No nil entries from concurrent chain rebuilding
|
|
122
|
+
# - All got valid Redis instances (protected by Mutex)
|
|
123
|
+
# - All are actually instances, not errors
|
|
124
|
+
[results.any?(nil), results.all? { |r| r == 'Redis' }, results.size]
|
|
125
|
+
#=> [false, true, 40]
|
|
126
|
+
|
|
127
|
+
## Rapid sequential reconnects from multiple threads
|
|
128
|
+
barrier = Concurrent::CyclicBarrier.new(10)
|
|
129
|
+
reconnect_counts = Concurrent::Array.new
|
|
130
|
+
|
|
131
|
+
threads = 10.times.map do
|
|
132
|
+
Thread.new do
|
|
133
|
+
barrier.wait
|
|
134
|
+
count = 0
|
|
135
|
+
10.times do
|
|
136
|
+
Familia.reconnect!
|
|
137
|
+
count += 1
|
|
138
|
+
end
|
|
139
|
+
reconnect_counts << count
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
threads.each(&:join)
|
|
144
|
+
reconnect_counts.all? { |c| c == 10 }
|
|
145
|
+
#=> true
|
|
146
|
+
|
|
147
|
+
# Teardown: Restore original connection chain
|
|
148
|
+
Familia.instance_variable_set(:@connection_chain, @original_chain)
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# try/thread_safety/encryption_manager_cache_race_try.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
require_relative '../support/helpers/test_helpers'
|
|
6
|
+
require 'base64'
|
|
7
|
+
|
|
8
|
+
# Thread safety tests for encryption manager cache initialization
|
|
9
|
+
#
|
|
10
|
+
# Tests concurrent encryption manager cache access to ensure that only
|
|
11
|
+
# a single Manager instance is created per algorithm even under high
|
|
12
|
+
# concurrent load.
|
|
13
|
+
#
|
|
14
|
+
# These tests verify:
|
|
15
|
+
# 1. Concurrent manager cache initialization (single algorithm)
|
|
16
|
+
# 2. Multiple algorithms initialized concurrently
|
|
17
|
+
# 3. Maximum contention with CyclicBarrier pattern
|
|
18
|
+
# 4. Manager singleton property per algorithm
|
|
19
|
+
|
|
20
|
+
# Setup encryption keys for testing
|
|
21
|
+
test_keys = {
|
|
22
|
+
v1: Base64.strict_encode64('a' * 32),
|
|
23
|
+
v2: Base64.strict_encode64('b' * 32)
|
|
24
|
+
}
|
|
25
|
+
Familia.config.encryption_keys = test_keys
|
|
26
|
+
Familia.config.current_key_version = :v1
|
|
27
|
+
|
|
28
|
+
## Concurrent manager cache initialization for default algorithm
|
|
29
|
+
# Reset the managers cache to nil to simulate first access
|
|
30
|
+
Familia::Encryption.instance_variable_set(:@managers, nil)
|
|
31
|
+
|
|
32
|
+
barrier = Concurrent::CyclicBarrier.new(50)
|
|
33
|
+
managers = Concurrent::Array.new
|
|
34
|
+
|
|
35
|
+
threads = 50.times.map do
|
|
36
|
+
Thread.new do
|
|
37
|
+
barrier.wait
|
|
38
|
+
# Get manager for default algorithm (nil)
|
|
39
|
+
mgr = Familia::Encryption.manager(algorithm: nil)
|
|
40
|
+
managers << mgr.object_id
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
threads.each(&:join)
|
|
45
|
+
|
|
46
|
+
# All threads should get the same manager instance
|
|
47
|
+
[managers.any?(nil), managers.uniq.size, managers.size]
|
|
48
|
+
#=> [false, 1, 50]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
## Concurrent manager cache initialization for default algorithm only
|
|
52
|
+
# Testing with just default (nil) algorithm to avoid provider availability issues
|
|
53
|
+
Familia::Encryption.instance_variable_set(:@managers, nil)
|
|
54
|
+
|
|
55
|
+
barrier = Concurrent::CyclicBarrier.new(30)
|
|
56
|
+
results = Concurrent::Array.new
|
|
57
|
+
|
|
58
|
+
threads = 30.times.map do |i|
|
|
59
|
+
Thread.new do
|
|
60
|
+
barrier.wait
|
|
61
|
+
# All use default algorithm for simplicity
|
|
62
|
+
mgr = Familia::Encryption.manager(algorithm: nil)
|
|
63
|
+
results << mgr.object_id
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
threads.each(&:join)
|
|
68
|
+
|
|
69
|
+
# All threads should get the same manager instance
|
|
70
|
+
[results.uniq.size, results.size]
|
|
71
|
+
#=> [1, 30]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
## Maximum contention test with encryption operations
|
|
75
|
+
Familia::Encryption.instance_variable_set(:@managers, nil)
|
|
76
|
+
Familia::Encryption.reset_derivation_count!
|
|
77
|
+
|
|
78
|
+
barrier = Concurrent::CyclicBarrier.new(50)
|
|
79
|
+
@encrypt_errors = Concurrent::Array.new
|
|
80
|
+
@encrypted_values = Concurrent::Array.new
|
|
81
|
+
|
|
82
|
+
threads = 50.times.map do |i|
|
|
83
|
+
Thread.new do
|
|
84
|
+
begin
|
|
85
|
+
barrier.wait
|
|
86
|
+
# All threads encrypt concurrently, forcing manager cache access
|
|
87
|
+
plaintext = "secret-#{i}"
|
|
88
|
+
context = "test:#{i}"
|
|
89
|
+
encrypted = Familia::Encryption.encrypt(plaintext, context: context)
|
|
90
|
+
@encrypted_values << encrypted
|
|
91
|
+
rescue => e
|
|
92
|
+
@encrypt_errors << e
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
threads.each(&:join)
|
|
98
|
+
|
|
99
|
+
# Verify no errors and all encryptions succeeded
|
|
100
|
+
[@encrypt_errors.empty?, @encrypted_values.size]
|
|
101
|
+
#=> [true, 50]
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
## Manager singleton property maintained across concurrent access
|
|
105
|
+
# Access the managers cache and verify singleton property
|
|
106
|
+
managers_cache = Familia::Encryption.instance_variable_get(:@managers)
|
|
107
|
+
|
|
108
|
+
# Should be a Concurrent::Map
|
|
109
|
+
managers_cache.class.name
|
|
110
|
+
#=> "Concurrent::Map"
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
## Concurrent decryption operations with manager cache
|
|
114
|
+
barrier = Concurrent::CyclicBarrier.new(25)
|
|
115
|
+
@decrypted_values = Concurrent::Array.new
|
|
116
|
+
@decrypt_errors = Concurrent::Array.new
|
|
117
|
+
|
|
118
|
+
# First, create some encrypted values
|
|
119
|
+
test_encrypted = 25.times.map do |i|
|
|
120
|
+
Familia::Encryption.encrypt("value-#{i}", context: "test:#{i}")
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
threads = 25.times.map do |i|
|
|
124
|
+
Thread.new do
|
|
125
|
+
begin
|
|
126
|
+
barrier.wait
|
|
127
|
+
decrypted = Familia::Encryption.decrypt(test_encrypted[i], context: "test:#{i}")
|
|
128
|
+
@decrypted_values << decrypted
|
|
129
|
+
rescue => e
|
|
130
|
+
@decrypt_errors << e
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
threads.each(&:join)
|
|
136
|
+
|
|
137
|
+
# Verify no errors and all decryptions succeeded
|
|
138
|
+
[@decrypt_errors.empty?, @decrypted_values.size]
|
|
139
|
+
#=> [true, 25]
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
## Rapid sequential manager access per thread
|
|
143
|
+
barrier = Concurrent::CyclicBarrier.new(20)
|
|
144
|
+
access_counts = Concurrent::Array.new
|
|
145
|
+
|
|
146
|
+
threads = 20.times.map do
|
|
147
|
+
Thread.new do
|
|
148
|
+
barrier.wait
|
|
149
|
+
count = 0
|
|
150
|
+
50.times do
|
|
151
|
+
Familia::Encryption.manager(algorithm: nil)
|
|
152
|
+
count += 1
|
|
153
|
+
end
|
|
154
|
+
access_counts << count
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
threads.each(&:join)
|
|
159
|
+
|
|
160
|
+
# Each thread completed 50 accesses
|
|
161
|
+
access_counts.all? { |c| c == 50 }
|
|
162
|
+
#=> true
|
|
163
|
+
|
|
164
|
+
# Cleanup
|
|
165
|
+
Familia.config.encryption_keys = nil
|
|
166
|
+
Familia.config.current_key_version = nil
|