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,139 @@
|
|
|
1
|
+
# try/unit/middleware/database_command_counter_methods_try.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
# Test DatabaseCommandCounter non-command methods and utilities
|
|
6
|
+
#
|
|
7
|
+
# This test file covers utility methods, configuration methods,
|
|
8
|
+
# and state management methods that don't involve command execution.
|
|
9
|
+
#
|
|
10
|
+
# Covers:
|
|
11
|
+
# - Counter methods (count, reset, increment, count_commands)
|
|
12
|
+
# - Configuration (skip_commands)
|
|
13
|
+
# - Utility methods (skip_command?)
|
|
14
|
+
# - Thread safety of atomic counter
|
|
15
|
+
|
|
16
|
+
require_relative '../../support/helpers/test_helpers'
|
|
17
|
+
require 'concurrent-ruby'
|
|
18
|
+
|
|
19
|
+
@original_count = DatabaseCommandCounter.count
|
|
20
|
+
|
|
21
|
+
# Clear initial state
|
|
22
|
+
DatabaseCommandCounter.reset
|
|
23
|
+
|
|
24
|
+
## count returns the current command count
|
|
25
|
+
DatabaseCommandCounter.reset
|
|
26
|
+
DatabaseCommandCounter.count
|
|
27
|
+
#=> 0
|
|
28
|
+
|
|
29
|
+
## reset sets count to zero
|
|
30
|
+
DatabaseCommandCounter.increment
|
|
31
|
+
DatabaseCommandCounter.increment
|
|
32
|
+
DatabaseCommandCounter.reset
|
|
33
|
+
DatabaseCommandCounter.count
|
|
34
|
+
#=> 0
|
|
35
|
+
|
|
36
|
+
## reset returns zero
|
|
37
|
+
result = DatabaseCommandCounter.reset
|
|
38
|
+
result
|
|
39
|
+
#=> 0
|
|
40
|
+
|
|
41
|
+
## increment increases the count by 1
|
|
42
|
+
DatabaseCommandCounter.reset
|
|
43
|
+
DatabaseCommandCounter.increment
|
|
44
|
+
DatabaseCommandCounter.count
|
|
45
|
+
#=> 1
|
|
46
|
+
|
|
47
|
+
## increment returns the new count
|
|
48
|
+
DatabaseCommandCounter.reset
|
|
49
|
+
result = DatabaseCommandCounter.increment
|
|
50
|
+
result
|
|
51
|
+
#=> 1
|
|
52
|
+
|
|
53
|
+
## increment can be called multiple times
|
|
54
|
+
DatabaseCommandCounter.reset
|
|
55
|
+
3.times { DatabaseCommandCounter.increment }
|
|
56
|
+
DatabaseCommandCounter.count
|
|
57
|
+
#=> 3
|
|
58
|
+
|
|
59
|
+
## skip_commands returns a Set with default skipped commands
|
|
60
|
+
DatabaseCommandCounter.skip_commands.class
|
|
61
|
+
#=> Set
|
|
62
|
+
|
|
63
|
+
## skip_commands includes SELECT by default
|
|
64
|
+
DatabaseCommandCounter.skip_commands.include?("SELECT")
|
|
65
|
+
#=> true
|
|
66
|
+
|
|
67
|
+
## skip_commands is frozen for immutability
|
|
68
|
+
DatabaseCommandCounter.skip_commands.frozen?
|
|
69
|
+
#=> true
|
|
70
|
+
|
|
71
|
+
## skip_command? returns true for commands in skip_commands
|
|
72
|
+
DatabaseCommandCounter.skip_command?(["SELECT", "0"])
|
|
73
|
+
#=> true
|
|
74
|
+
|
|
75
|
+
## skip_command? returns false for commands not in skip_commands
|
|
76
|
+
DatabaseCommandCounter.skip_command?(["SET", "key", "value"])
|
|
77
|
+
#=> false
|
|
78
|
+
|
|
79
|
+
## skip_command? handles command array properly
|
|
80
|
+
DatabaseCommandCounter.skip_command?(["GET", "key"])
|
|
81
|
+
#=> false
|
|
82
|
+
|
|
83
|
+
## skip_command? is case insensitive
|
|
84
|
+
DatabaseCommandCounter.skip_command?(["select", "0"])
|
|
85
|
+
#=> true
|
|
86
|
+
|
|
87
|
+
## count_commands captures count difference in block
|
|
88
|
+
DatabaseCommandCounter.reset
|
|
89
|
+
initial_count = DatabaseCommandCounter.count
|
|
90
|
+
commands_executed = DatabaseCommandCounter.count_commands do
|
|
91
|
+
5.times { DatabaseCommandCounter.increment }
|
|
92
|
+
end
|
|
93
|
+
commands_executed
|
|
94
|
+
#=> 5
|
|
95
|
+
|
|
96
|
+
## count_commands works with empty block
|
|
97
|
+
DatabaseCommandCounter.reset
|
|
98
|
+
commands_executed = DatabaseCommandCounter.count_commands do
|
|
99
|
+
# No commands
|
|
100
|
+
end
|
|
101
|
+
commands_executed
|
|
102
|
+
#=> 0
|
|
103
|
+
|
|
104
|
+
## count_commands captures count difference with existing count
|
|
105
|
+
DatabaseCommandCounter.reset
|
|
106
|
+
5.times { DatabaseCommandCounter.increment } # Existing commands
|
|
107
|
+
commands_executed = DatabaseCommandCounter.count_commands do
|
|
108
|
+
3.times { DatabaseCommandCounter.increment } # New commands in block
|
|
109
|
+
end
|
|
110
|
+
commands_executed
|
|
111
|
+
#=> 3
|
|
112
|
+
|
|
113
|
+
## Thread safety: increment is thread-safe with concurrent access
|
|
114
|
+
DatabaseCommandCounter.reset
|
|
115
|
+
threads = 10.times.map do |i|
|
|
116
|
+
Thread.new do
|
|
117
|
+
10.times { DatabaseCommandCounter.increment }
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
threads.each(&:join)
|
|
122
|
+
DatabaseCommandCounter.count
|
|
123
|
+
#=> 100
|
|
124
|
+
|
|
125
|
+
## Thread safety: count_commands is thread-safe
|
|
126
|
+
DatabaseCommandCounter.reset
|
|
127
|
+
result = DatabaseCommandCounter.count_commands do
|
|
128
|
+
threads = 5.times.map do
|
|
129
|
+
Thread.new do
|
|
130
|
+
10.times { DatabaseCommandCounter.increment }
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
threads.each(&:join)
|
|
134
|
+
end
|
|
135
|
+
result
|
|
136
|
+
#=> 50
|
|
137
|
+
|
|
138
|
+
# Restore original state (if needed)
|
|
139
|
+
DatabaseCommandCounter.reset
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# try/unit/middleware/database_logger_methods_try.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
# Test DatabaseLogger non-command methods and utilities
|
|
6
|
+
#
|
|
7
|
+
# This test file covers utility methods, configuration methods,
|
|
8
|
+
# and state management methods that don't involve command execution.
|
|
9
|
+
#
|
|
10
|
+
# Covers:
|
|
11
|
+
# - Configuration getters/setters (logger, max_commands, structured_logging, sample_rate)
|
|
12
|
+
# - Command storage methods (clear_commands, append_command, capture_commands)
|
|
13
|
+
# - Utility methods (index, now_in_μs, should_log?)
|
|
14
|
+
# - State introspection (commands, process_start)
|
|
15
|
+
# - Thread safety of command storage
|
|
16
|
+
|
|
17
|
+
require_relative '../../support/helpers/test_helpers'
|
|
18
|
+
require 'logger'
|
|
19
|
+
require 'stringio'
|
|
20
|
+
require 'concurrent-ruby'
|
|
21
|
+
|
|
22
|
+
@original_logger = DatabaseLogger.logger
|
|
23
|
+
@original_max_commands = DatabaseLogger.max_commands
|
|
24
|
+
@original_structured_logging = DatabaseLogger.structured_logging
|
|
25
|
+
@original_sample_rate = DatabaseLogger.sample_rate
|
|
26
|
+
|
|
27
|
+
# Clear initial state
|
|
28
|
+
DatabaseLogger.clear_commands
|
|
29
|
+
DatabaseLogger.logger = nil
|
|
30
|
+
DatabaseLogger.max_commands = 10_000
|
|
31
|
+
DatabaseLogger.structured_logging = false
|
|
32
|
+
DatabaseLogger.sample_rate = nil
|
|
33
|
+
|
|
34
|
+
## logger getter returns the current logger instance
|
|
35
|
+
test_logger = Logger.new(StringIO.new)
|
|
36
|
+
DatabaseLogger.logger = test_logger
|
|
37
|
+
DatabaseLogger.logger == test_logger
|
|
38
|
+
#=> true
|
|
39
|
+
|
|
40
|
+
## logger can be set to nil
|
|
41
|
+
DatabaseLogger.logger = nil
|
|
42
|
+
DatabaseLogger.logger
|
|
43
|
+
#=> nil
|
|
44
|
+
|
|
45
|
+
## max_commands getter returns the current max commands value
|
|
46
|
+
DatabaseLogger.max_commands = 5_000
|
|
47
|
+
DatabaseLogger.max_commands
|
|
48
|
+
#=> 5000
|
|
49
|
+
|
|
50
|
+
## max_commands can be set to different values
|
|
51
|
+
DatabaseLogger.max_commands = 1_000
|
|
52
|
+
DatabaseLogger.max_commands
|
|
53
|
+
#=> 1000
|
|
54
|
+
|
|
55
|
+
## structured_logging getter returns the current structured logging mode
|
|
56
|
+
DatabaseLogger.structured_logging = true
|
|
57
|
+
DatabaseLogger.structured_logging
|
|
58
|
+
#=> true
|
|
59
|
+
|
|
60
|
+
## structured_logging can be toggled
|
|
61
|
+
DatabaseLogger.structured_logging = false
|
|
62
|
+
DatabaseLogger.structured_logging
|
|
63
|
+
#=> false
|
|
64
|
+
|
|
65
|
+
## sample_rate getter returns the current sample rate
|
|
66
|
+
DatabaseLogger.sample_rate = 0.5
|
|
67
|
+
DatabaseLogger.sample_rate
|
|
68
|
+
#=> 0.5
|
|
69
|
+
|
|
70
|
+
## sample_rate can be set to nil
|
|
71
|
+
DatabaseLogger.sample_rate = nil
|
|
72
|
+
DatabaseLogger.sample_rate
|
|
73
|
+
#=> nil
|
|
74
|
+
|
|
75
|
+
## commands getter returns the captured commands array
|
|
76
|
+
DatabaseLogger.clear_commands
|
|
77
|
+
commands = DatabaseLogger.commands
|
|
78
|
+
commands.class
|
|
79
|
+
#=> Concurrent::Array
|
|
80
|
+
|
|
81
|
+
## commands array is initially empty after clear
|
|
82
|
+
DatabaseLogger.clear_commands
|
|
83
|
+
DatabaseLogger.commands.empty?
|
|
84
|
+
#=> true
|
|
85
|
+
|
|
86
|
+
## process_start returns a float timestamp
|
|
87
|
+
DatabaseLogger.process_start.class
|
|
88
|
+
#=> Float
|
|
89
|
+
|
|
90
|
+
## process_start is frozen
|
|
91
|
+
DatabaseLogger.process_start.frozen?
|
|
92
|
+
#=> true
|
|
93
|
+
|
|
94
|
+
## clear_commands returns nil and empties the commands array
|
|
95
|
+
DatabaseLogger.instance_variable_get(:@commands) << "test"
|
|
96
|
+
result = DatabaseLogger.clear_commands
|
|
97
|
+
[result, DatabaseLogger.commands.empty?]
|
|
98
|
+
#=> [nil, true]
|
|
99
|
+
|
|
100
|
+
## index returns the current count of commands
|
|
101
|
+
DatabaseLogger.clear_commands
|
|
102
|
+
DatabaseLogger.index
|
|
103
|
+
#=> 0
|
|
104
|
+
|
|
105
|
+
## index increases as commands are added
|
|
106
|
+
DatabaseLogger.clear_commands
|
|
107
|
+
msg = DatabaseLogger::CommandMessage.new("TEST", 100, 0.001)
|
|
108
|
+
DatabaseLogger.append_command(msg)
|
|
109
|
+
DatabaseLogger.index
|
|
110
|
+
#=> 1
|
|
111
|
+
|
|
112
|
+
## append_command adds a message to the commands array
|
|
113
|
+
DatabaseLogger.clear_commands
|
|
114
|
+
msg = DatabaseLogger::CommandMessage.new("SET key value", 500, 0.002)
|
|
115
|
+
result = DatabaseLogger.append_command(msg)
|
|
116
|
+
[DatabaseLogger.commands.size, result.last == msg]
|
|
117
|
+
#=> [1, true]
|
|
118
|
+
|
|
119
|
+
## append_command respects max_commands limit by shifting oldest
|
|
120
|
+
DatabaseLogger.max_commands = 3
|
|
121
|
+
DatabaseLogger.clear_commands
|
|
122
|
+
msg1 = DatabaseLogger::CommandMessage.new("CMD1", 100, 0.001)
|
|
123
|
+
msg2 = DatabaseLogger::CommandMessage.new("CMD2", 200, 0.002)
|
|
124
|
+
msg3 = DatabaseLogger::CommandMessage.new("CMD3", 300, 0.003)
|
|
125
|
+
msg4 = DatabaseLogger::CommandMessage.new("CMD4", 400, 0.004)
|
|
126
|
+
|
|
127
|
+
DatabaseLogger.append_command(msg1)
|
|
128
|
+
DatabaseLogger.append_command(msg2)
|
|
129
|
+
DatabaseLogger.append_command(msg3)
|
|
130
|
+
DatabaseLogger.append_command(msg4)
|
|
131
|
+
|
|
132
|
+
[DatabaseLogger.commands.size, DatabaseLogger.commands.first == msg2]
|
|
133
|
+
#=> [3, true]
|
|
134
|
+
|
|
135
|
+
## now_in_μs returns current time in microseconds
|
|
136
|
+
time1 = DatabaseLogger.now_in_μs
|
|
137
|
+
sleep(0.001)
|
|
138
|
+
time2 = DatabaseLogger.now_in_μs
|
|
139
|
+
time2 > time1
|
|
140
|
+
#=> true
|
|
141
|
+
|
|
142
|
+
## now_in_microseconds is an alias for now_in_μs
|
|
143
|
+
DatabaseLogger.method(:now_in_microseconds) == DatabaseLogger.method(:now_in_μs)
|
|
144
|
+
#=> true
|
|
145
|
+
|
|
146
|
+
## capture_commands yields and returns captured commands
|
|
147
|
+
DatabaseLogger.clear_commands
|
|
148
|
+
commands = DatabaseLogger.capture_commands do
|
|
149
|
+
msg = DatabaseLogger::CommandMessage.new("GET key", 150, 0.001)
|
|
150
|
+
DatabaseLogger.append_command(msg)
|
|
151
|
+
msg2 = DatabaseLogger::CommandMessage.new("SET key2 value", 200, 0.002)
|
|
152
|
+
DatabaseLogger.append_command(msg2)
|
|
153
|
+
end
|
|
154
|
+
commands.size
|
|
155
|
+
#=> 2
|
|
156
|
+
|
|
157
|
+
## capture_commands clears commands before capturing
|
|
158
|
+
DatabaseLogger.clear_commands
|
|
159
|
+
msg_before = DatabaseLogger::CommandMessage.new("BEFORE", 100, 0.001)
|
|
160
|
+
DatabaseLogger.append_command(msg_before)
|
|
161
|
+
|
|
162
|
+
commands = DatabaseLogger.capture_commands do
|
|
163
|
+
msg = DatabaseLogger::CommandMessage.new("DURING", 150, 0.002)
|
|
164
|
+
DatabaseLogger.append_command(msg)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
[commands.size, commands.first.command]
|
|
168
|
+
#=> [1, "DURING"]
|
|
169
|
+
|
|
170
|
+
## capture_commands returns array snapshot, not live reference
|
|
171
|
+
DatabaseLogger.clear_commands
|
|
172
|
+
commands = DatabaseLogger.capture_commands do
|
|
173
|
+
msg = DatabaseLogger::CommandMessage.new("TEST", 100, 0.001)
|
|
174
|
+
DatabaseLogger.append_command(msg)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Add another command after capture
|
|
178
|
+
msg2 = DatabaseLogger::CommandMessage.new("AFTER", 200, 0.002)
|
|
179
|
+
DatabaseLogger.append_command(msg2)
|
|
180
|
+
|
|
181
|
+
[commands.size, DatabaseLogger.commands.size]
|
|
182
|
+
#=> [1, 2]
|
|
183
|
+
|
|
184
|
+
## should_log? returns true when sample_rate is nil
|
|
185
|
+
DatabaseLogger.sample_rate = nil
|
|
186
|
+
DatabaseLogger.logger = Logger.new(StringIO.new)
|
|
187
|
+
DatabaseLogger.should_log?
|
|
188
|
+
#=> true
|
|
189
|
+
|
|
190
|
+
## should_log? returns false when logger is nil regardless of sample_rate
|
|
191
|
+
DatabaseLogger.sample_rate = 1.0
|
|
192
|
+
DatabaseLogger.logger = nil
|
|
193
|
+
DatabaseLogger.should_log?
|
|
194
|
+
#=> false
|
|
195
|
+
|
|
196
|
+
## should_log? uses atomic counter for thread-safe sampling
|
|
197
|
+
DatabaseLogger.sample_rate = 0.5 # 50% sampling
|
|
198
|
+
DatabaseLogger.logger = Logger.new(StringIO.new)
|
|
199
|
+
DatabaseLogger.instance_variable_set(:@sample_counter, Concurrent::AtomicFixnum.new(0))
|
|
200
|
+
|
|
201
|
+
# Test deterministic sampling pattern
|
|
202
|
+
results = 10.times.map { DatabaseLogger.should_log? }
|
|
203
|
+
results.count(true)
|
|
204
|
+
#=> 5
|
|
205
|
+
|
|
206
|
+
## should_log? sampling is deterministic and consistent
|
|
207
|
+
DatabaseLogger.sample_rate = 0.25 # 25% sampling (every 4th)
|
|
208
|
+
DatabaseLogger.instance_variable_set(:@sample_counter, Concurrent::AtomicFixnum.new(0))
|
|
209
|
+
|
|
210
|
+
results = 12.times.map { DatabaseLogger.should_log? }
|
|
211
|
+
results.count(true)
|
|
212
|
+
#=> 3
|
|
213
|
+
|
|
214
|
+
## CommandMessage can be created with command, duration, timeline
|
|
215
|
+
msg = DatabaseLogger::CommandMessage.new("SET key value", 1500, 0.123456)
|
|
216
|
+
[msg.command, msg.μs, msg.timeline]
|
|
217
|
+
#=> ["SET key value", 1500, 0.123456]
|
|
218
|
+
|
|
219
|
+
## CommandMessage.inspect formats nicely
|
|
220
|
+
msg = DatabaseLogger::CommandMessage.new("GET key", 2500, 1.234567)
|
|
221
|
+
msg.inspect
|
|
222
|
+
#=> "1.234567 2500μs > GET key"
|
|
223
|
+
|
|
224
|
+
## CommandMessage.to_a returns deconstructed array
|
|
225
|
+
msg = DatabaseLogger::CommandMessage.new("DEL key1 key2", 750, 2.345678)
|
|
226
|
+
msg.to_a
|
|
227
|
+
#=> ["DEL key1 key2", 750, 2.345678]
|
|
228
|
+
|
|
229
|
+
## Thread safety: append_command is thread-safe with concurrent access
|
|
230
|
+
# Reset max_commands to allow all 100 commands
|
|
231
|
+
DatabaseLogger.max_commands = 1000
|
|
232
|
+
DatabaseLogger.clear_commands
|
|
233
|
+
threads = 10.times.map do |i|
|
|
234
|
+
Thread.new do
|
|
235
|
+
10.times do |j|
|
|
236
|
+
msg = DatabaseLogger::CommandMessage.new("THREAD#{i}_CMD#{j}", 100, 0.001)
|
|
237
|
+
DatabaseLogger.append_command(msg)
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
threads.each(&:join)
|
|
243
|
+
DatabaseLogger.commands.size
|
|
244
|
+
#=> 100
|
|
245
|
+
|
|
246
|
+
# Restore original state
|
|
247
|
+
DatabaseLogger.logger = @original_logger
|
|
248
|
+
DatabaseLogger.max_commands = @original_max_commands
|
|
249
|
+
DatabaseLogger.structured_logging = @original_structured_logging
|
|
250
|
+
DatabaseLogger.sample_rate = @original_sample_rate
|
|
251
|
+
DatabaseLogger.clear_commands
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# try/unit/thread_safety_monitor_try.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
require_relative '../support/helpers/test_helpers'
|
|
6
|
+
|
|
7
|
+
# Thread safety monitoring tests
|
|
8
|
+
#
|
|
9
|
+
# Tests basic monitoring functionality without complex concurrency
|
|
10
|
+
|
|
11
|
+
# setup
|
|
12
|
+
Familia.stop_monitoring! if Familia.thread_safety_monitor.enabled
|
|
13
|
+
Familia.thread_safety_monitor.reset_metrics
|
|
14
|
+
|
|
15
|
+
## Monitor starts and stops correctly
|
|
16
|
+
Familia.start_monitoring!
|
|
17
|
+
started = Familia.thread_safety_monitor.enabled
|
|
18
|
+
Familia.stop_monitoring!
|
|
19
|
+
stopped = !Familia.thread_safety_monitor.enabled
|
|
20
|
+
started && stopped
|
|
21
|
+
#=> true
|
|
22
|
+
|
|
23
|
+
## Monitor tracks contentions
|
|
24
|
+
Familia.start_monitoring!
|
|
25
|
+
Familia.thread_safety_monitor.record_contention('test_location')
|
|
26
|
+
Familia.thread_safety_monitor.record_contention('test_location')
|
|
27
|
+
report = Familia.thread_safety_report
|
|
28
|
+
report[:summary][:mutex_contentions]
|
|
29
|
+
#=> 2
|
|
30
|
+
|
|
31
|
+
## Monitor tracks different locations
|
|
32
|
+
Familia.thread_safety_monitor.reset_metrics
|
|
33
|
+
Familia.thread_safety_monitor.record_contention('location_a')
|
|
34
|
+
Familia.thread_safety_monitor.record_contention('location_b')
|
|
35
|
+
Familia.thread_safety_monitor.record_contention('location_a')
|
|
36
|
+
report = Familia.thread_safety_report
|
|
37
|
+
report[:hot_spots].size
|
|
38
|
+
#=> 2
|
|
39
|
+
|
|
40
|
+
## Monitor tracks race conditions
|
|
41
|
+
Familia.thread_safety_monitor.reset_metrics
|
|
42
|
+
Familia.thread_safety_monitor.record_race_condition('test', 'details')
|
|
43
|
+
report = Familia.thread_safety_report
|
|
44
|
+
report[:summary][:race_detections]
|
|
45
|
+
#=> 1
|
|
46
|
+
|
|
47
|
+
## Monitor exports metrics
|
|
48
|
+
Familia.thread_safety_monitor.reset_metrics
|
|
49
|
+
Familia.thread_safety_monitor.record_contention('test')
|
|
50
|
+
metrics = Familia.thread_safety_metrics
|
|
51
|
+
metrics['familia.thread_safety.mutex_contentions']
|
|
52
|
+
#=> 1
|
|
53
|
+
|
|
54
|
+
## Health score starts at maximum
|
|
55
|
+
Familia.thread_safety_monitor.reset_metrics
|
|
56
|
+
report = Familia.thread_safety_report
|
|
57
|
+
report[:health]
|
|
58
|
+
#=> 100
|
|
59
|
+
|
|
60
|
+
## InstrumentedMutex is used for connection chain
|
|
61
|
+
conn_mutex = Familia.instance_variable_get(:@connection_chain_mutex)
|
|
62
|
+
conn_mutex.is_a?(Familia::ThreadSafety::InstrumentedMutex)
|
|
63
|
+
#=> true
|
|
64
|
+
|
|
65
|
+
## InstrumentedMutex tracks basic operations
|
|
66
|
+
mutex = Familia::ThreadSafety::InstrumentedMutex.new('test')
|
|
67
|
+
mutex.synchronize { 'work' }
|
|
68
|
+
stats = mutex.stats
|
|
69
|
+
stats[:lock_count]
|
|
70
|
+
#=> 1
|
|
71
|
+
|
|
72
|
+
## Monitor time_critical_section works
|
|
73
|
+
Familia.thread_safety_monitor.reset_metrics
|
|
74
|
+
result = Familia.thread_safety_monitor.time_critical_section('test') { 37 }
|
|
75
|
+
result
|
|
76
|
+
#=> 37
|
|
77
|
+
|
|
78
|
+
## Critical sections are tracked
|
|
79
|
+
report = Familia.thread_safety_report
|
|
80
|
+
report[:summary][:critical_sections]
|
|
81
|
+
#=> 1
|
|
82
|
+
|
|
83
|
+
## Monitor uses microsecond timing for precision
|
|
84
|
+
Familia.thread_safety_monitor.reset_metrics
|
|
85
|
+
start_μs = Familia.now_in_μs
|
|
86
|
+
Familia.thread_safety_monitor.time_critical_section('timing_test') do
|
|
87
|
+
sleep 0.005 # 5ms sleep
|
|
88
|
+
end
|
|
89
|
+
end_μs = Familia.now_in_μs
|
|
90
|
+
duration_μs = end_μs - start_μs
|
|
91
|
+
# Should be at least 5000 microseconds (5ms)
|
|
92
|
+
duration_μs >= 5000
|
|
93
|
+
#=> true
|
|
94
|
+
|
|
95
|
+
## Wait time tracking uses microsecond precision
|
|
96
|
+
mutex = Familia::ThreadSafety::InstrumentedMutex.new('timing_mutex')
|
|
97
|
+
Familia.thread_safety_monitor.reset_metrics
|
|
98
|
+
|
|
99
|
+
# Create intentional contention with timing
|
|
100
|
+
t1_ready = false
|
|
101
|
+
t2_ready = false
|
|
102
|
+
|
|
103
|
+
t1 = Thread.new do
|
|
104
|
+
mutex.synchronize do
|
|
105
|
+
t1_ready = true
|
|
106
|
+
sleep 0.01 while !t2_ready # Wait for t2 to be waiting
|
|
107
|
+
sleep 0.005 # Hold lock for 5ms
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
t2 = Thread.new do
|
|
112
|
+
sleep 0.001 while !t1_ready # Wait for t1 to acquire lock
|
|
113
|
+
t2_ready = true
|
|
114
|
+
mutex.synchronize { 'got lock' }
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
t1.join
|
|
118
|
+
t2.join
|
|
119
|
+
|
|
120
|
+
stats = mutex.stats
|
|
121
|
+
# Should show contention occurred due to intentional delay
|
|
122
|
+
stats[:contention_count] > 0
|
|
123
|
+
#=> true
|
|
124
|
+
|
|
125
|
+
## Monitor preserves microsecond precision
|
|
126
|
+
Familia.thread_safety_monitor.reset_metrics
|
|
127
|
+
Familia.thread_safety_monitor.record_contention('precision_test', 1500) # 1500μs = 1.5ms
|
|
128
|
+
|
|
129
|
+
report = Familia.thread_safety_report
|
|
130
|
+
hot_spot = report[:hot_spots].first
|
|
131
|
+
# Should preserve microsecond precision, not convert to seconds
|
|
132
|
+
hot_spot[:avg_wait_μs]
|
|
133
|
+
#=> 1500
|
|
134
|
+
|
|
135
|
+
## Critical section timing uses microseconds
|
|
136
|
+
Familia.thread_safety_monitor.reset_metrics
|
|
137
|
+
result = Familia.thread_safety_monitor.time_critical_section('precision_timing') do
|
|
138
|
+
sleep 0.003 # 3ms
|
|
139
|
+
37
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
report = Familia.thread_safety_report
|
|
143
|
+
section_perf = report[:section_performance].first
|
|
144
|
+
# Total time should be in microseconds (≥3000μs for 3ms sleep)
|
|
145
|
+
section_perf[:total_time_μs] >= 3000
|
|
146
|
+
#=> true
|
|
147
|
+
|
|
148
|
+
# teardown
|
|
149
|
+
Familia.stop_monitoring!
|