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.
- 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/.talismanrc +5 -1
- data/CHANGELOG.rst +220 -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/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 +3 -2
- data/lib/familia/connection/operations.rb +2 -0
- data/lib/familia/connection/pipelined_core.rb +3 -3
- data/lib/familia/connection/transaction_core.rb +69 -2
- 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 +79 -52
- 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 +7 -10
- data/lib/familia/data_type/types/stringkey.rb +24 -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 +62 -7
- data/lib/familia/features/object_identifier.rb +49 -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 +97 -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 +8 -1
- data/lib/familia/horreum/definition.rb +16 -6
- data/lib/familia/horreum/management.rb +353 -52
- data/lib/familia/horreum/persistence.rb +179 -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 +3 -1
- 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 +61 -31
- 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/count_any_edge_cases_try.rb +486 -0
- data/try/features/count_any_methods_try.rb +197 -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 +305 -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 +140 -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 +606 -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 +9 -3
- data/try/integration/data_types/datatype_transactions_try.rb +17 -7
- 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 +7 -3
- 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 +39 -22
- 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 +6 -2
- 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/serialization_try.rb +386 -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 +6 -1
- 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 +69 -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,226 @@
|
|
|
1
|
+
# try/thread_safety/feature_registry_race_try.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
require_relative '../support/helpers/test_helpers'
|
|
6
|
+
|
|
7
|
+
# Thread safety tests for feature registry initialization
|
|
8
|
+
#
|
|
9
|
+
# Tests concurrent feature registration to ensure that lazy initialization
|
|
10
|
+
# of @features_available and @feature_definitions doesn't result in
|
|
11
|
+
# corruption or missing feature registrations.
|
|
12
|
+
#
|
|
13
|
+
# These tests verify:
|
|
14
|
+
# 1. Concurrent feature registration on same class
|
|
15
|
+
# 2. Feature dependency resolution during concurrent registration
|
|
16
|
+
# 3. Feature metadata consistency
|
|
17
|
+
# 4. Feature activation during concurrent access
|
|
18
|
+
|
|
19
|
+
## Concurrent feature registration on same class
|
|
20
|
+
module TestFeature1; end
|
|
21
|
+
module TestFeature2; end
|
|
22
|
+
module TestFeature3; end
|
|
23
|
+
|
|
24
|
+
class FeatureTestModel1 < Familia::Horreum
|
|
25
|
+
identifier_field :test_id
|
|
26
|
+
field :test_id
|
|
27
|
+
|
|
28
|
+
def init
|
|
29
|
+
@test_id ||= SecureRandom.hex(4)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
FeatureTestModel1.instance_variable_set(:@features_available, nil)
|
|
34
|
+
FeatureTestModel1.instance_variable_set(:@feature_definitions, nil)
|
|
35
|
+
|
|
36
|
+
barrier = Concurrent::CyclicBarrier.new(20)
|
|
37
|
+
features = Concurrent::Array.new
|
|
38
|
+
|
|
39
|
+
threads = 20.times.map do |i|
|
|
40
|
+
Thread.new do
|
|
41
|
+
barrier.wait
|
|
42
|
+
feature_module = Module.new
|
|
43
|
+
feature_name = "test_feature_#{i}".to_sym
|
|
44
|
+
FeatureTestModel1.add_feature(feature_module, feature_name)
|
|
45
|
+
features << feature_name
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
threads.each(&:join)
|
|
50
|
+
|
|
51
|
+
[features.size, FeatureTestModel1.features_available.is_a?(Hash)]
|
|
52
|
+
#=> [20, true]
|
|
53
|
+
|
|
54
|
+
## Feature activation during concurrent declaration
|
|
55
|
+
class FeatureTestModel2 < Familia::Horreum
|
|
56
|
+
identifier_field :test_id
|
|
57
|
+
field :test_id
|
|
58
|
+
|
|
59
|
+
def init
|
|
60
|
+
@test_id ||= SecureRandom.hex(4)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
barrier = Concurrent::CyclicBarrier.new(15)
|
|
65
|
+
activated = Concurrent::Array.new
|
|
66
|
+
|
|
67
|
+
threads = 15.times.map do |i|
|
|
68
|
+
Thread.new do
|
|
69
|
+
barrier.wait
|
|
70
|
+
feature_name = "activated_feature_#{i}".to_sym
|
|
71
|
+
feature_module = Module.new
|
|
72
|
+
FeatureTestModel2.add_feature(feature_module, feature_name)
|
|
73
|
+
FeatureTestModel2.feature(feature_name)
|
|
74
|
+
activated << feature_name
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
threads.each(&:join)
|
|
79
|
+
activated.size
|
|
80
|
+
#=> 15
|
|
81
|
+
|
|
82
|
+
## Concurrent feature registration with field groups
|
|
83
|
+
class FeatureFieldGroupModel < Familia::Horreum
|
|
84
|
+
identifier_field :test_id
|
|
85
|
+
field :test_id
|
|
86
|
+
|
|
87
|
+
def init
|
|
88
|
+
@test_id ||= SecureRandom.hex(4)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
barrier = Concurrent::CyclicBarrier.new(20)
|
|
93
|
+
feature_groups = Concurrent::Array.new
|
|
94
|
+
|
|
95
|
+
threads = 20.times.map do |i|
|
|
96
|
+
Thread.new do
|
|
97
|
+
barrier.wait
|
|
98
|
+
feature_module = Module.new
|
|
99
|
+
feature_name = "grouped_feature_#{i}".to_sym
|
|
100
|
+
field_group = "group_#{i}".to_sym
|
|
101
|
+
FeatureFieldGroupModel.add_feature(feature_module, feature_name, field_group: field_group)
|
|
102
|
+
feature_groups << field_group
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
threads.each(&:join)
|
|
107
|
+
feature_groups.size
|
|
108
|
+
#=> 20
|
|
109
|
+
|
|
110
|
+
## Feature dependency registration during concurrent access
|
|
111
|
+
class DependencyTestModel < Familia::Horreum
|
|
112
|
+
identifier_field :test_id
|
|
113
|
+
field :test_id
|
|
114
|
+
|
|
115
|
+
def init
|
|
116
|
+
@test_id ||= SecureRandom.hex(4)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
barrier = Concurrent::CyclicBarrier.new(25)
|
|
121
|
+
dependencies = Concurrent::Array.new
|
|
122
|
+
|
|
123
|
+
threads = 25.times.map do |i|
|
|
124
|
+
Thread.new do
|
|
125
|
+
barrier.wait
|
|
126
|
+
feature_module = Module.new
|
|
127
|
+
feature_name = "dependent_feature_#{i}".to_sym
|
|
128
|
+
depends_on = i > 0 ? ["dependent_feature_#{i - 1}".to_sym] : []
|
|
129
|
+
DependencyTestModel.add_feature(feature_module, feature_name, depends_on: depends_on)
|
|
130
|
+
dependencies << feature_name
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
threads.each(&:join)
|
|
135
|
+
dependencies.size
|
|
136
|
+
#=> 25
|
|
137
|
+
|
|
138
|
+
## Concurrent feature queries
|
|
139
|
+
class QueryTestModel < Familia::Horreum
|
|
140
|
+
identifier_field :test_id
|
|
141
|
+
field :test_id
|
|
142
|
+
|
|
143
|
+
def init
|
|
144
|
+
@test_id ||= SecureRandom.hex(4)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Pre-register some features
|
|
149
|
+
5.times do |i|
|
|
150
|
+
QueryTestModel.add_feature(Module.new, "query_feature_#{i}".to_sym)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
barrier = Concurrent::CyclicBarrier.new(30)
|
|
154
|
+
query_results = Concurrent::Array.new
|
|
155
|
+
|
|
156
|
+
threads = 30.times.map do
|
|
157
|
+
Thread.new do
|
|
158
|
+
barrier.wait
|
|
159
|
+
available = QueryTestModel.features_available
|
|
160
|
+
query_results << available.keys.size
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
threads.each(&:join)
|
|
165
|
+
query_results.size
|
|
166
|
+
#=> 30
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
## Feature metadata consistency during concurrent registration
|
|
170
|
+
class MetadataTestModel < Familia::Horreum
|
|
171
|
+
identifier_field :test_id
|
|
172
|
+
field :test_id
|
|
173
|
+
|
|
174
|
+
def init
|
|
175
|
+
@test_id ||= SecureRandom.hex(4)
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
barrier = Concurrent::CyclicBarrier.new(20)
|
|
180
|
+
metadata = Concurrent::Array.new
|
|
181
|
+
|
|
182
|
+
threads = 20.times.map do |i|
|
|
183
|
+
Thread.new do
|
|
184
|
+
barrier.wait
|
|
185
|
+
feature_module = Module.new do
|
|
186
|
+
def self.feature_name
|
|
187
|
+
"metadata_feature"
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
feature_name = "meta_feature_#{i}".to_sym
|
|
191
|
+
MetadataTestModel.add_feature(feature_module, feature_name)
|
|
192
|
+
metadata << [feature_name, MetadataTestModel.features_available[feature_name]]
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
threads.each(&:join)
|
|
197
|
+
metadata.size
|
|
198
|
+
#=> 20
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
## Feature definitions hash consistency
|
|
202
|
+
class DefinitionsTestModel < Familia::Horreum
|
|
203
|
+
identifier_field :test_id
|
|
204
|
+
field :test_id
|
|
205
|
+
|
|
206
|
+
def init
|
|
207
|
+
@test_id ||= SecureRandom.hex(4)
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
barrier = Concurrent::CyclicBarrier.new(30)
|
|
212
|
+
definitions = Concurrent::Array.new
|
|
213
|
+
|
|
214
|
+
threads = 30.times.map do |i|
|
|
215
|
+
Thread.new do
|
|
216
|
+
barrier.wait
|
|
217
|
+
feature_module = Module.new
|
|
218
|
+
feature_name = "def_feature_#{i}".to_sym
|
|
219
|
+
DefinitionsTestModel.add_feature(feature_module, feature_name)
|
|
220
|
+
definitions << DefinitionsTestModel.feature_definitions.keys.size
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
threads.each(&:join)
|
|
225
|
+
definitions.size
|
|
226
|
+
#=> 30
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# try/thread_safety/fiber_pipeline_isolation_try.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
require_relative '../support/helpers/test_helpers'
|
|
6
|
+
|
|
7
|
+
# Thread safety tests for fiber-local pipeline storage
|
|
8
|
+
#
|
|
9
|
+
# Tests that fiber-local pipeline storage properly isolates pipelines
|
|
10
|
+
# between different fibers and threads, ensuring no cross-contamination.
|
|
11
|
+
#
|
|
12
|
+
# These tests verify:
|
|
13
|
+
# 1. Concurrent pipelines in different fibers are isolated
|
|
14
|
+
# 2. Pipeline context doesn't leak between fibers
|
|
15
|
+
# 3. Nested pipelines work correctly across fibers
|
|
16
|
+
# 4. Pipeline cleanup happens properly per fiber
|
|
17
|
+
|
|
18
|
+
## Concurrent threads with isolated pipelines
|
|
19
|
+
results = Concurrent::Array.new
|
|
20
|
+
barrier = Concurrent::CyclicBarrier.new(10)
|
|
21
|
+
|
|
22
|
+
# Each thread has its own root fiber, so pipelines are isolated per-thread
|
|
23
|
+
threads = 10.times.map do |i|
|
|
24
|
+
Thread.new do
|
|
25
|
+
barrier.wait # Synchronize start for maximum contention
|
|
26
|
+
Familia.pipeline do
|
|
27
|
+
# Each thread's fiber should have isolated pipeline context
|
|
28
|
+
conn = Fiber[:familia_pipeline]
|
|
29
|
+
# Use direct Redis commands instead of save
|
|
30
|
+
conn.set("test:pipe:#{i}", "value_#{i}")
|
|
31
|
+
results << [i, conn.object_id, "pipe_#{i}"]
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
threads.each(&:join)
|
|
37
|
+
|
|
38
|
+
results
|
|
39
|
+
#==> _.size == 10
|
|
40
|
+
#==> _.map { |(i, _, _)| i }.sort == (0..9).to_a
|
|
41
|
+
#==> _.map { |(_, _, pipe_id)| pipe_id }.uniq.size == 10
|
|
42
|
+
|
|
43
|
+
## Pipeline isolation across multiple threads
|
|
44
|
+
barrier = Concurrent::CyclicBarrier.new(15)
|
|
45
|
+
fiber_results = Concurrent::Array.new
|
|
46
|
+
|
|
47
|
+
# Each thread automatically has its own root fiber
|
|
48
|
+
threads = 15.times.map do |i|
|
|
49
|
+
Thread.new do
|
|
50
|
+
barrier.wait
|
|
51
|
+
Familia.pipeline do
|
|
52
|
+
conn = Fiber[:familia_pipeline]
|
|
53
|
+
fiber_results << [Thread.current.object_id, conn.object_id]
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
threads.each(&:join)
|
|
59
|
+
|
|
60
|
+
fiber_results
|
|
61
|
+
#==> _.size == 15
|
|
62
|
+
#==> _.map { |(thread_id, _)| thread_id }.uniq.size >= 10
|
|
63
|
+
|
|
64
|
+
## Nested pipelines in same fiber maintain context
|
|
65
|
+
nested_results = Concurrent::Array.new
|
|
66
|
+
|
|
67
|
+
fiber = Fiber.new do
|
|
68
|
+
Familia.pipeline do
|
|
69
|
+
outer_conn = Fiber[:familia_pipeline]
|
|
70
|
+
nested_results << [:outer, outer_conn.object_id]
|
|
71
|
+
|
|
72
|
+
Familia.pipeline do
|
|
73
|
+
inner_conn = Fiber[:familia_pipeline]
|
|
74
|
+
nested_results << [:inner, inner_conn.object_id]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
after_inner = Fiber[:familia_pipeline]
|
|
78
|
+
nested_results << [:after_inner, after_inner.object_id]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
after_outer = Fiber[:familia_pipeline]
|
|
82
|
+
nested_results << [:after_outer, after_outer.nil?]
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
fiber.resume
|
|
86
|
+
|
|
87
|
+
nested_results
|
|
88
|
+
#==> _.size == 4
|
|
89
|
+
#==> _[0][1] == _[1][1] # Same connection for nested
|
|
90
|
+
#==> _[0][1] == _[2][1] # Same connection after inner
|
|
91
|
+
#==> _[3][1] == true # Cleaned up after outer
|
|
92
|
+
|
|
93
|
+
## Pipeline context doesn't leak between sequential fibers
|
|
94
|
+
sequential_results = Concurrent::Array.new
|
|
95
|
+
|
|
96
|
+
5.times do |i|
|
|
97
|
+
fiber = Fiber.new do
|
|
98
|
+
Familia.pipeline do
|
|
99
|
+
conn = Fiber[:familia_pipeline]
|
|
100
|
+
sequential_results << [i, conn.object_id]
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
fiber.resume
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
sequential_results
|
|
107
|
+
#==> _.size == 5
|
|
108
|
+
#==> _.map { |(i, _)| i } == [0, 1, 2, 3, 4]
|
|
109
|
+
|
|
110
|
+
## Concurrent fiber creation and pipeline execution
|
|
111
|
+
creation_barrier = Concurrent::CyclicBarrier.new(20)
|
|
112
|
+
execution_results = Concurrent::Array.new
|
|
113
|
+
|
|
114
|
+
threads = 20.times.map do |i|
|
|
115
|
+
Thread.new do
|
|
116
|
+
creation_barrier.wait
|
|
117
|
+
fiber = Fiber.new do
|
|
118
|
+
begin
|
|
119
|
+
Familia.pipeline do
|
|
120
|
+
conn = Fiber[:familia_pipeline]
|
|
121
|
+
# Use direct Redis commands instead of save
|
|
122
|
+
conn.set("test:concurrent_pipe:#{i}", "value_#{i}")
|
|
123
|
+
execution_results << "concurrent_#{i}"
|
|
124
|
+
end
|
|
125
|
+
rescue => e
|
|
126
|
+
execution_results << [:error, e.class.name]
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
fiber.resume
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
threads.each(&:join)
|
|
134
|
+
|
|
135
|
+
[execution_results.size, execution_results.count { |r| r.is_a?(String) && r.start_with?('concurrent_') }]
|
|
136
|
+
#=> [20, 20]
|
|
137
|
+
|
|
138
|
+
## Pipeline cleanup after exception
|
|
139
|
+
exception_results = Concurrent::Array.new
|
|
140
|
+
|
|
141
|
+
fiber = Fiber.new do
|
|
142
|
+
begin
|
|
143
|
+
Familia.pipeline do
|
|
144
|
+
exception_results << Fiber[:familia_pipeline].object_id
|
|
145
|
+
raise "Test exception"
|
|
146
|
+
end
|
|
147
|
+
rescue => e
|
|
148
|
+
exception_results << [:exception, e.message]
|
|
149
|
+
end
|
|
150
|
+
exception_results << [:after_exception, Fiber[:familia_pipeline].nil?]
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
fiber.resume
|
|
154
|
+
|
|
155
|
+
exception_results
|
|
156
|
+
#==> _.size == 3
|
|
157
|
+
#==> _[0].is_a?(Integer)
|
|
158
|
+
#==> _[1] == [:exception, "Test exception"]
|
|
159
|
+
#==> _[2] == [:after_exception, true]
|
|
160
|
+
|
|
161
|
+
## Fiber switching during pipeline maintains context
|
|
162
|
+
switch_results = Concurrent::Array.new
|
|
163
|
+
|
|
164
|
+
fiber1 = Fiber.new do
|
|
165
|
+
Familia.pipeline do
|
|
166
|
+
conn1 = Fiber[:familia_pipeline]
|
|
167
|
+
switch_results << [:fiber1_before_yield, conn1.object_id]
|
|
168
|
+
Fiber.yield
|
|
169
|
+
conn1_after = Fiber[:familia_pipeline]
|
|
170
|
+
switch_results << [:fiber1_after_yield, conn1_after.object_id]
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
fiber2 = Fiber.new do
|
|
175
|
+
Familia.pipeline do
|
|
176
|
+
conn2 = Fiber[:familia_pipeline]
|
|
177
|
+
switch_results << [:fiber2, conn2.object_id]
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
fiber1.resume
|
|
182
|
+
fiber2.resume
|
|
183
|
+
fiber1.resume
|
|
184
|
+
|
|
185
|
+
switch_results
|
|
186
|
+
#==> _.size == 3
|
|
187
|
+
#==> _[0][1] == _[2][1] # Same connection before/after yield
|
|
188
|
+
#==> _[0][1] != _[1][1] # Different connections per fiber
|
|
189
|
+
|
|
190
|
+
## Multiple pipelines per fiber (sequential)
|
|
191
|
+
multi_pipe_results = Concurrent::Array.new
|
|
192
|
+
|
|
193
|
+
fiber = Fiber.new do
|
|
194
|
+
3.times do |i|
|
|
195
|
+
Familia.pipeline do
|
|
196
|
+
conn = Fiber[:familia_pipeline]
|
|
197
|
+
multi_pipe_results << [i, conn.object_id]
|
|
198
|
+
end
|
|
199
|
+
multi_pipe_results << [:between, Fiber[:familia_pipeline].nil?]
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
fiber.resume
|
|
204
|
+
|
|
205
|
+
multi_pipe_results
|
|
206
|
+
#==> _.size == 6
|
|
207
|
+
#==> _.select { |(label, _)| label == :between }.all? { |(_, is_nil)| is_nil }
|
|
208
|
+
|
|
209
|
+
## Pipeline and transaction isolation in same fiber
|
|
210
|
+
mixed_results = Concurrent::Array.new
|
|
211
|
+
|
|
212
|
+
fiber = Fiber.new do
|
|
213
|
+
Familia.pipeline do
|
|
214
|
+
pipe_conn = Fiber[:familia_pipeline]
|
|
215
|
+
mixed_results << [:pipeline, pipe_conn.object_id]
|
|
216
|
+
|
|
217
|
+
Familia.transaction do
|
|
218
|
+
txn_conn = Fiber[:familia_transaction]
|
|
219
|
+
pipe_during_txn = Fiber[:familia_pipeline]
|
|
220
|
+
mixed_results << [:transaction, txn_conn.object_id]
|
|
221
|
+
mixed_results << [:pipeline_during_txn, pipe_during_txn.object_id]
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
pipe_after_txn = Fiber[:familia_pipeline]
|
|
225
|
+
mixed_results << [:pipeline_after_txn, pipe_after_txn.object_id]
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
fiber.resume
|
|
230
|
+
|
|
231
|
+
mixed_results
|
|
232
|
+
#==> _.size == 4
|
|
233
|
+
#==> _[0][1] == _[2][1] # Pipeline preserved during transaction
|
|
234
|
+
#==> _[0][1] == _[3][1] # Pipeline preserved after transaction
|
|
235
|
+
#==> _[0][1] != _[1][1] # Different connections for pipeline and transaction
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# try/thread_safety/fiber_transaction_isolation_try.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
require_relative '../support/helpers/test_helpers'
|
|
6
|
+
|
|
7
|
+
# Thread safety tests for fiber-local transaction storage
|
|
8
|
+
#
|
|
9
|
+
# Tests that fiber-local transaction storage properly isolates transactions
|
|
10
|
+
# between different fibers and threads, ensuring no cross-contamination.
|
|
11
|
+
#
|
|
12
|
+
# These tests verify:
|
|
13
|
+
# 1. Concurrent transactions in different fibers are isolated
|
|
14
|
+
# 2. Transaction context doesn't leak between fibers
|
|
15
|
+
# 3. Nested transactions work correctly across fibers
|
|
16
|
+
# 4. Transaction cleanup happens properly per fiber
|
|
17
|
+
|
|
18
|
+
## Concurrent threads with isolated transactions
|
|
19
|
+
results = Concurrent::Array.new
|
|
20
|
+
barrier = Concurrent::CyclicBarrier.new(10)
|
|
21
|
+
|
|
22
|
+
# Each thread has its own root fiber, so transactions are isolated per-thread
|
|
23
|
+
threads = 10.times.map do |i|
|
|
24
|
+
Thread.new do
|
|
25
|
+
barrier.wait # Synchronize start for maximum contention
|
|
26
|
+
Familia.transaction do
|
|
27
|
+
# Each thread's fiber should have isolated transaction context
|
|
28
|
+
conn = Fiber[:familia_transaction]
|
|
29
|
+
# Use direct Redis commands instead of save (which is not allowed in transactions)
|
|
30
|
+
conn.set("test:txn:#{i}", "value_#{i}")
|
|
31
|
+
results << [i, conn.object_id, "txn_#{i}"]
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
threads.each(&:join)
|
|
37
|
+
|
|
38
|
+
results
|
|
39
|
+
#==> _.size == 10
|
|
40
|
+
#==> _.map { |(i, _, _)| i }.sort == (0..9).to_a
|
|
41
|
+
#==> _.map { |(_, _, txn_id)| txn_id }.uniq.size == 10
|
|
42
|
+
|
|
43
|
+
## Transaction isolation across multiple threads
|
|
44
|
+
barrier = Concurrent::CyclicBarrier.new(15)
|
|
45
|
+
fiber_results = Concurrent::Array.new
|
|
46
|
+
|
|
47
|
+
# Each thread automatically has its own root fiber
|
|
48
|
+
threads = 15.times.map do |i|
|
|
49
|
+
Thread.new do
|
|
50
|
+
barrier.wait
|
|
51
|
+
Familia.transaction do
|
|
52
|
+
conn = Fiber[:familia_transaction]
|
|
53
|
+
fiber_results << [Thread.current.object_id, conn.object_id]
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
threads.each(&:join)
|
|
59
|
+
|
|
60
|
+
fiber_results
|
|
61
|
+
#==> _.size == 15
|
|
62
|
+
#==> _.map { |(thread_id, _)| thread_id }.uniq.size >= 10
|
|
63
|
+
|
|
64
|
+
## Nested transactions in same fiber maintain context
|
|
65
|
+
nested_results = Concurrent::Array.new
|
|
66
|
+
|
|
67
|
+
fiber = Fiber.new do
|
|
68
|
+
Familia.transaction do
|
|
69
|
+
outer_conn = Fiber[:familia_transaction]
|
|
70
|
+
nested_results << [:outer, outer_conn.object_id]
|
|
71
|
+
|
|
72
|
+
Familia.transaction do
|
|
73
|
+
inner_conn = Fiber[:familia_transaction]
|
|
74
|
+
nested_results << [:inner, inner_conn.object_id]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
after_inner = Fiber[:familia_transaction]
|
|
78
|
+
nested_results << [:after_inner, after_inner.object_id]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
after_outer = Fiber[:familia_transaction]
|
|
82
|
+
nested_results << [:after_outer, after_outer.nil?]
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
fiber.resume
|
|
86
|
+
|
|
87
|
+
nested_results
|
|
88
|
+
#==> _.size == 4
|
|
89
|
+
#==> _[0][1] == _[1][1] # Same connection for nested
|
|
90
|
+
#==> _[0][1] == _[2][1] # Same connection after inner
|
|
91
|
+
#==> _[3][1] == true # Cleaned up after outer
|
|
92
|
+
|
|
93
|
+
## Transaction context doesn't leak between sequential fibers
|
|
94
|
+
sequential_results = Concurrent::Array.new
|
|
95
|
+
|
|
96
|
+
5.times do |i|
|
|
97
|
+
fiber = Fiber.new do
|
|
98
|
+
Familia.transaction do
|
|
99
|
+
conn = Fiber[:familia_transaction]
|
|
100
|
+
sequential_results << [i, conn.object_id]
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
fiber.resume
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
sequential_results
|
|
107
|
+
#==> _.size == 5
|
|
108
|
+
#==> _.map { |(i, _)| i } == [0, 1, 2, 3, 4]
|
|
109
|
+
|
|
110
|
+
## Concurrent fiber creation and transaction execution
|
|
111
|
+
creation_barrier = Concurrent::CyclicBarrier.new(20)
|
|
112
|
+
execution_results = Concurrent::Array.new
|
|
113
|
+
|
|
114
|
+
threads = 20.times.map do |i|
|
|
115
|
+
Thread.new do
|
|
116
|
+
creation_barrier.wait
|
|
117
|
+
fiber = Fiber.new do
|
|
118
|
+
begin
|
|
119
|
+
Familia.transaction do
|
|
120
|
+
# Use direct Redis commands instead of save
|
|
121
|
+
conn = Fiber[:familia_transaction]
|
|
122
|
+
conn.set("test:concurrent:#{i}", "value_#{i}")
|
|
123
|
+
execution_results << "concurrent_#{i}"
|
|
124
|
+
end
|
|
125
|
+
rescue => e
|
|
126
|
+
execution_results << [:error, e.class.name]
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
fiber.resume
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
threads.each(&:join)
|
|
134
|
+
|
|
135
|
+
execution_results
|
|
136
|
+
#==> _.size == 20
|
|
137
|
+
#==> _.all? { |r| r.is_a?(String) && r.start_with?('concurrent_') }
|
|
138
|
+
#==> true
|
|
139
|
+
|
|
140
|
+
## Transaction cleanup after exception
|
|
141
|
+
exception_results = Concurrent::Array.new
|
|
142
|
+
|
|
143
|
+
fiber = Fiber.new do
|
|
144
|
+
begin
|
|
145
|
+
Familia.transaction do
|
|
146
|
+
exception_results << Fiber[:familia_transaction].object_id
|
|
147
|
+
raise "Test exception"
|
|
148
|
+
end
|
|
149
|
+
rescue => e
|
|
150
|
+
exception_results << [:exception, e.message]
|
|
151
|
+
end
|
|
152
|
+
exception_results << [:after_exception, Fiber[:familia_transaction].nil?]
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
fiber.resume
|
|
156
|
+
exception_results
|
|
157
|
+
#==> _.size == 3
|
|
158
|
+
#==> _[0].is_a?(Integer)
|
|
159
|
+
#==> _[1] == [:exception, "Test exception"]
|
|
160
|
+
#==> _[2] == [:after_exception, true]
|
|
161
|
+
|
|
162
|
+
## Fiber switching during transaction maintains context
|
|
163
|
+
switch_results = Concurrent::Array.new
|
|
164
|
+
|
|
165
|
+
fiber1 = Fiber.new do
|
|
166
|
+
Familia.transaction do
|
|
167
|
+
conn1 = Fiber[:familia_transaction]
|
|
168
|
+
switch_results << [:fiber1_before_yield, conn1.object_id]
|
|
169
|
+
Fiber.yield
|
|
170
|
+
conn1_after = Fiber[:familia_transaction]
|
|
171
|
+
switch_results << [:fiber1_after_yield, conn1_after.object_id]
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
fiber2 = Fiber.new do
|
|
176
|
+
Familia.transaction do
|
|
177
|
+
conn2 = Fiber[:familia_transaction]
|
|
178
|
+
switch_results << [:fiber2, conn2.object_id]
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
fiber1.resume
|
|
183
|
+
fiber2.resume
|
|
184
|
+
fiber1.resume
|
|
185
|
+
|
|
186
|
+
switch_results
|
|
187
|
+
#==> _.size == 3
|
|
188
|
+
#==> _[0][1] == _[2][1] # Same connection before/after yield
|
|
189
|
+
#==> _[0][1] != _[1][1] # Different connections per fiber
|
|
190
|
+
|
|
191
|
+
## Multiple transactions per fiber (sequential)
|
|
192
|
+
multi_txn_results = Concurrent::Array.new
|
|
193
|
+
|
|
194
|
+
fiber = Fiber.new do
|
|
195
|
+
3.times do |i|
|
|
196
|
+
Familia.transaction do
|
|
197
|
+
conn = Fiber[:familia_transaction]
|
|
198
|
+
multi_txn_results << [i, conn.object_id]
|
|
199
|
+
end
|
|
200
|
+
multi_txn_results << [:between, Fiber[:familia_transaction].nil?]
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
fiber.resume
|
|
205
|
+
|
|
206
|
+
multi_txn_results
|
|
207
|
+
#==> _.size == 6
|
|
208
|
+
#==> _.select { |(label, _)| label == :between }.all? { |(_, is_nil)| is_nil }
|