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,156 @@
|
|
|
1
|
+
# lib/familia/instrumentation.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
require 'concurrent-ruby'
|
|
6
|
+
|
|
7
|
+
module Familia
|
|
8
|
+
# Provides instrumentation hooks for observability into Familia operations.
|
|
9
|
+
#
|
|
10
|
+
# This module allows applications to register callbacks for various events
|
|
11
|
+
# in Familia's lifecycle, enabling audit trails, performance monitoring,
|
|
12
|
+
# and operational observability.
|
|
13
|
+
#
|
|
14
|
+
# @example Basic usage
|
|
15
|
+
# Familia.on_command do |cmd, duration, context|
|
|
16
|
+
# puts "Redis command: #{cmd} (#{duration}μs)"
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# @example Audit trail for secrets service
|
|
20
|
+
# Familia.on_lifecycle do |event, instance, context|
|
|
21
|
+
# case event
|
|
22
|
+
# when :save
|
|
23
|
+
# AuditLog.create!(
|
|
24
|
+
# event: 'secret_saved',
|
|
25
|
+
# secret_id: instance.identifier,
|
|
26
|
+
# user_id: RequestContext.current_user_id
|
|
27
|
+
# )
|
|
28
|
+
# end
|
|
29
|
+
# end
|
|
30
|
+
#
|
|
31
|
+
module Instrumentation
|
|
32
|
+
@hooks = {
|
|
33
|
+
command: Concurrent::Array.new,
|
|
34
|
+
pipeline: Concurrent::Array.new,
|
|
35
|
+
lifecycle: Concurrent::Array.new,
|
|
36
|
+
error: Concurrent::Array.new
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
class << self
|
|
40
|
+
# Register a callback for Redis command execution.
|
|
41
|
+
#
|
|
42
|
+
# @yield [cmd, duration, context] Callback block
|
|
43
|
+
# @yieldparam cmd [String] The Redis command name (e.g., "SET", "ZADD")
|
|
44
|
+
# @yieldparam duration [Integer] Command execution duration in microseconds
|
|
45
|
+
# @yieldparam context [Hash] Additional context including:
|
|
46
|
+
# - :full_command [Array] Complete command with arguments
|
|
47
|
+
# - :db [Integer] Database number
|
|
48
|
+
# - :connection_id [String] Connection identifier
|
|
49
|
+
#
|
|
50
|
+
# @example
|
|
51
|
+
# Familia.on_command do |cmd, duration, ctx|
|
|
52
|
+
# StatsD.timing("familia.command.#{cmd.downcase}", duration / 1000.0)
|
|
53
|
+
# end
|
|
54
|
+
#
|
|
55
|
+
def on_command(&block)
|
|
56
|
+
@hooks[:command] << block
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Register a callback for pipelined Redis operations.
|
|
60
|
+
#
|
|
61
|
+
# @yield [command_count, duration, context] Callback block
|
|
62
|
+
# @yieldparam command_count [Integer] Number of commands in the pipeline
|
|
63
|
+
# @yieldparam duration [Integer] Pipeline execution duration in microseconds
|
|
64
|
+
# @yieldparam context [Hash] Additional context
|
|
65
|
+
#
|
|
66
|
+
# @example
|
|
67
|
+
# Familia.on_pipeline do |count, duration, ctx|
|
|
68
|
+
# StatsD.timing("familia.pipeline", duration / 1000.0)
|
|
69
|
+
# StatsD.gauge("familia.pipeline.commands", count)
|
|
70
|
+
# end
|
|
71
|
+
#
|
|
72
|
+
def on_pipeline(&block)
|
|
73
|
+
@hooks[:pipeline] << block
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Register a callback for Horreum lifecycle events.
|
|
77
|
+
#
|
|
78
|
+
# @yield [event, instance, context] Callback block
|
|
79
|
+
# @yieldparam event [Symbol] Lifecycle event (:initialize, :save, :destroy)
|
|
80
|
+
# @yieldparam instance [Familia::Horreum] The object instance
|
|
81
|
+
# @yieldparam context [Hash] Additional context including:
|
|
82
|
+
# - :duration [Integer] Operation duration in microseconds (for initialize/save)
|
|
83
|
+
# - :update_expiration [Boolean] Whether TTL was updated (for save)
|
|
84
|
+
#
|
|
85
|
+
# @example
|
|
86
|
+
# Familia.on_lifecycle do |event, instance, ctx|
|
|
87
|
+
# case event
|
|
88
|
+
# when :destroy
|
|
89
|
+
# Rails.logger.info("Destroyed #{instance.class}:#{instance.identifier}")
|
|
90
|
+
# end
|
|
91
|
+
# end
|
|
92
|
+
#
|
|
93
|
+
def on_lifecycle(&block)
|
|
94
|
+
@hooks[:lifecycle] << block
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Register a callback for error conditions.
|
|
98
|
+
#
|
|
99
|
+
# @yield [error, context] Callback block
|
|
100
|
+
# @yieldparam error [Exception] The error that occurred
|
|
101
|
+
# @yieldparam context [Hash] Additional context including:
|
|
102
|
+
# - :operation [Symbol] Operation that failed (:serialization, etc.)
|
|
103
|
+
# - :field [Symbol] Field name (for serialization errors)
|
|
104
|
+
# - :object_class [String] Class name of the object
|
|
105
|
+
#
|
|
106
|
+
# @example
|
|
107
|
+
# Familia.on_error do |error, ctx|
|
|
108
|
+
# Sentry.capture_exception(error, extra: ctx)
|
|
109
|
+
# end
|
|
110
|
+
#
|
|
111
|
+
def on_error(&block)
|
|
112
|
+
@hooks[:error] << block
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Notify all registered command hooks.
|
|
116
|
+
# @api private
|
|
117
|
+
def notify_command(cmd, duration, context = {})
|
|
118
|
+
@hooks[:command].each do |hook|
|
|
119
|
+
hook.call(cmd, duration, context)
|
|
120
|
+
rescue => e
|
|
121
|
+
Familia.error("Instrumentation hook failed", error: e.message, hook_type: :command)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Notify all registered pipeline hooks.
|
|
126
|
+
# @api private
|
|
127
|
+
def notify_pipeline(command_count, duration, context = {})
|
|
128
|
+
@hooks[:pipeline].each do |hook|
|
|
129
|
+
hook.call(command_count, duration, context)
|
|
130
|
+
rescue => e
|
|
131
|
+
Familia.error("Instrumentation hook failed", error: e.message, hook_type: :pipeline)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Notify all registered lifecycle hooks.
|
|
136
|
+
# @api private
|
|
137
|
+
def notify_lifecycle(event, instance, context = {})
|
|
138
|
+
@hooks[:lifecycle].each do |hook|
|
|
139
|
+
hook.call(event, instance, context)
|
|
140
|
+
rescue => e
|
|
141
|
+
Familia.error("Instrumentation hook failed", error: e.message, hook_type: :lifecycle)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Notify all registered error hooks.
|
|
146
|
+
# @api private
|
|
147
|
+
def notify_error(error, context = {})
|
|
148
|
+
@hooks[:error].each do |hook|
|
|
149
|
+
hook.call(error, context)
|
|
150
|
+
rescue => e
|
|
151
|
+
# Don't recurse on hook failures - just silently skip
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
data/lib/familia/logging.rb
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# lib/familia/logging.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
require 'pathname'
|
|
4
6
|
require 'logger'
|
|
@@ -117,7 +119,7 @@ module Familia
|
|
|
117
119
|
'ERROR' => 'E',
|
|
118
120
|
'FATAL' => 'F',
|
|
119
121
|
'UNKNOWN' => 'U',
|
|
120
|
-
'ANY' => 'T'
|
|
122
|
+
'ANY' => 'T', # ANY is Logger's label for severity < 0, treat as TRACE
|
|
121
123
|
}.freeze
|
|
122
124
|
|
|
123
125
|
# Format a log message with severity, timestamp, and context.
|
|
@@ -173,8 +175,16 @@ module Familia
|
|
|
173
175
|
# Familia.trace :LOAD, redis_client, "user:123", "from cache"
|
|
174
176
|
#
|
|
175
177
|
module Logging
|
|
178
|
+
# Thread-safe mutex initialization when module is extended
|
|
179
|
+
def self.extended(base)
|
|
180
|
+
base.instance_variable_set(:@logger_mutex, Mutex.new)
|
|
181
|
+
end
|
|
182
|
+
|
|
176
183
|
# Get the logger instance, initializing with defaults if not yet set
|
|
177
184
|
#
|
|
185
|
+
# Thread-safe lazy initialization using double-checked locking to ensure
|
|
186
|
+
# only a single logger instance is created even under concurrent logging calls.
|
|
187
|
+
#
|
|
178
188
|
# @return [FamiliaLogger] the logger instance
|
|
179
189
|
#
|
|
180
190
|
# @example Set a custom logger
|
|
@@ -184,10 +194,18 @@ module Familia
|
|
|
184
194
|
# Familia.logger.info "Connection established"
|
|
185
195
|
#
|
|
186
196
|
def logger
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
197
|
+
# Fast path: return existing logger if already initialized
|
|
198
|
+
return @logger if @logger
|
|
199
|
+
|
|
200
|
+
# Slow path: thread-safe initialization
|
|
201
|
+
@logger_mutex.synchronize do
|
|
202
|
+
@logger ||= FamiliaLogger.new($stderr).tap do |log|
|
|
203
|
+
log.progname = name
|
|
204
|
+
log.formatter = LogFormatter.new
|
|
205
|
+
end
|
|
190
206
|
end
|
|
207
|
+
|
|
208
|
+
@logger
|
|
191
209
|
end
|
|
192
210
|
|
|
193
211
|
# Set a custom logger instance.
|
|
@@ -195,6 +213,9 @@ module Familia
|
|
|
195
213
|
# Allows replacing the default FamiliaLogger with any Logger-compatible
|
|
196
214
|
# object. Useful for integrating with application logging frameworks.
|
|
197
215
|
#
|
|
216
|
+
# Automatically synchronizes the logger to DatabaseLogger if it's loaded,
|
|
217
|
+
# ensuring consistent logging across Familia's middleware stack.
|
|
218
|
+
#
|
|
198
219
|
# @param new_logger [Logger] The logger to use
|
|
199
220
|
# @return [Logger] The logger that was set
|
|
200
221
|
#
|
|
@@ -207,20 +228,14 @@ module Familia
|
|
|
207
228
|
# end
|
|
208
229
|
#
|
|
209
230
|
def logger=(new_logger)
|
|
210
|
-
@
|
|
231
|
+
@logger_mutex.synchronize do
|
|
232
|
+
@logger = new_logger
|
|
233
|
+
# Auto-sync to DatabaseLogger if loaded (inside mutex for atomicity)
|
|
234
|
+
DatabaseLogger.logger = new_logger if defined?(DatabaseLogger)
|
|
235
|
+
end
|
|
236
|
+
@logger
|
|
211
237
|
end
|
|
212
238
|
|
|
213
|
-
# Log an informational message.
|
|
214
|
-
#
|
|
215
|
-
# @param msg [String] The message to log
|
|
216
|
-
# @return [true]
|
|
217
|
-
#
|
|
218
|
-
# @example
|
|
219
|
-
# Familia.info "Redis connection established"
|
|
220
|
-
#
|
|
221
|
-
def info(msg)
|
|
222
|
-
logger.info(msg)
|
|
223
|
-
end
|
|
224
239
|
|
|
225
240
|
# Log a warning message.
|
|
226
241
|
#
|
|
@@ -234,34 +249,58 @@ module Familia
|
|
|
234
249
|
logger.warn(msg)
|
|
235
250
|
end
|
|
236
251
|
|
|
237
|
-
# Log a debug message
|
|
252
|
+
# Log a debug message with optional structured context.
|
|
238
253
|
#
|
|
239
|
-
#
|
|
240
|
-
#
|
|
254
|
+
# Only outputs when FAMILIA_DEBUG environment variable is enabled.
|
|
255
|
+
# Supports both simple string messages and structured logging with
|
|
256
|
+
# keyword context for operational observability.
|
|
241
257
|
#
|
|
242
|
-
# @param
|
|
258
|
+
# @param message [String, nil] The message to log
|
|
259
|
+
# @param context [Hash] Structured context (key-value pairs)
|
|
243
260
|
# @return [true, nil] Returns true if logged, nil if debug disabled
|
|
244
261
|
#
|
|
245
|
-
# @example
|
|
246
|
-
# Familia.
|
|
247
|
-
#
|
|
262
|
+
# @example Simple message
|
|
263
|
+
# Familia.debug "Cache lookup for user:123"
|
|
264
|
+
#
|
|
265
|
+
# @example Structured context
|
|
266
|
+
# Familia.debug "Horreum saved", class: "User", identifier: "user_123", duration: 1234
|
|
267
|
+
# # => "Horreum saved class=User identifier=user_123 duration=1234"
|
|
248
268
|
#
|
|
249
|
-
def
|
|
250
|
-
|
|
269
|
+
def debug(message = nil, **context)
|
|
270
|
+
return unless Familia.debug?
|
|
271
|
+
logger.debug(format_log(message, context))
|
|
251
272
|
end
|
|
252
273
|
|
|
253
|
-
# Log an
|
|
274
|
+
# Log an informational message with optional structured context.
|
|
254
275
|
#
|
|
255
|
-
#
|
|
276
|
+
# @param message [String, nil] The message to log
|
|
277
|
+
# @param context [Hash] Structured context (key-value pairs)
|
|
278
|
+
# @return [true]
|
|
256
279
|
#
|
|
257
|
-
# @
|
|
280
|
+
# @example Simple message
|
|
281
|
+
# Familia.info "Connection pool initialized"
|
|
282
|
+
#
|
|
283
|
+
# @example Structured context
|
|
284
|
+
# Familia.info "Pipeline executed", commands: 5, duration: 2340
|
|
285
|
+
#
|
|
286
|
+
def info(message = nil, **context)
|
|
287
|
+
logger.info(format_log(message, context))
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# Log an error message with optional structured context.
|
|
291
|
+
#
|
|
292
|
+
# @param message [String, nil] The message to log
|
|
293
|
+
# @param context [Hash] Structured context (key-value pairs)
|
|
258
294
|
# @return [true]
|
|
259
295
|
#
|
|
260
|
-
# @example
|
|
261
|
-
# Familia.
|
|
296
|
+
# @example Simple message
|
|
297
|
+
# Familia.error "Failed to deserialize value"
|
|
298
|
+
#
|
|
299
|
+
# @example Structured context
|
|
300
|
+
# Familia.error "Serialization failed", field: :email, error: e.message, class: "User"
|
|
262
301
|
#
|
|
263
|
-
def
|
|
264
|
-
logger.error(
|
|
302
|
+
def error(message = nil, **context)
|
|
303
|
+
logger.error(format_log(message, context))
|
|
265
304
|
end
|
|
266
305
|
|
|
267
306
|
# Logs a structured trace message for debugging Familia operations.
|
|
@@ -293,6 +332,27 @@ module Familia
|
|
|
293
332
|
|
|
294
333
|
private
|
|
295
334
|
|
|
335
|
+
# Format a log message with optional structured context.
|
|
336
|
+
#
|
|
337
|
+
# Combines a message string with key-value context into a single
|
|
338
|
+
# log line. Empty context returns the message unchanged.
|
|
339
|
+
#
|
|
340
|
+
# @param message [String, nil] The message to log
|
|
341
|
+
# @param context [Hash] Structured context (key-value pairs)
|
|
342
|
+
# @return [String] Formatted log message
|
|
343
|
+
# @api private
|
|
344
|
+
#
|
|
345
|
+
# @example
|
|
346
|
+
# format_log("User saved", id: 123, duration: 1.5)
|
|
347
|
+
# # => "User saved id=123 duration=1.5"
|
|
348
|
+
#
|
|
349
|
+
def format_log(message, context)
|
|
350
|
+
return message if context.empty?
|
|
351
|
+
parts = [message]
|
|
352
|
+
parts << context.map { |k, v| "#{k}=#{v}" }.join(' ')
|
|
353
|
+
parts.compact.join(' ')
|
|
354
|
+
end
|
|
355
|
+
|
|
296
356
|
# Check if trace logging is enabled via FAMILIA_TRACE environment variable.
|
|
297
357
|
#
|
|
298
358
|
# Trace logging is enabled when FAMILIA_TRACE is set to '1', 'true',
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# lib/familia/refinements/stylize_words.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
module Familia
|
|
4
6
|
module Refinements
|
|
@@ -16,20 +18,6 @@ module Familia
|
|
|
16
18
|
.downcase
|
|
17
19
|
end
|
|
18
20
|
|
|
19
|
-
# Convert from plural to singular form using basic English rules
|
|
20
|
-
def singularize
|
|
21
|
-
word = to_s
|
|
22
|
-
if word.end_with?('ies')
|
|
23
|
-
"#{word[0..-4]}y"
|
|
24
|
-
elsif word.end_with?('es') && word.length > 3
|
|
25
|
-
word[0..-3]
|
|
26
|
-
elsif word.end_with?('s') && word.length > 1
|
|
27
|
-
word[0..-2]
|
|
28
|
-
else
|
|
29
|
-
word
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
|
|
33
21
|
# Convert to camelCase
|
|
34
22
|
def camelize
|
|
35
23
|
_ize(:lower)
|
data/lib/familia/refinements.rb
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# lib/familia/secure_identifier.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
require 'securerandom'
|
|
4
6
|
|
|
@@ -118,6 +120,10 @@ module Familia
|
|
|
118
120
|
|
|
119
121
|
# @private
|
|
120
122
|
#
|
|
123
|
+
# Thread-safe lazy initialization using Concurrent::Map to ensure
|
|
124
|
+
# atomic cache population and consistent calculations across all
|
|
125
|
+
# concurrent ID generation requests.
|
|
126
|
+
#
|
|
121
127
|
# @param bits [Integer] The number of bits of entropy.
|
|
122
128
|
# @param base [Integer] The numeric base (2-36).
|
|
123
129
|
# @return [Integer] The minimum string length required.
|
|
@@ -130,8 +136,10 @@ module Familia
|
|
|
130
136
|
}.freeze
|
|
131
137
|
return hex_lengths[bits] if base == 16 && hex_lengths.key?(bits)
|
|
132
138
|
|
|
133
|
-
@min_length_for_bits_cache ||=
|
|
134
|
-
@min_length_for_bits_cache[
|
|
139
|
+
@min_length_for_bits_cache ||= Concurrent::Map.new
|
|
140
|
+
@min_length_for_bits_cache.fetch_or_store([bits, base]) do
|
|
141
|
+
(bits * Math.log(2) / Math.log(base)).ceil
|
|
142
|
+
end
|
|
135
143
|
end
|
|
136
144
|
end
|
|
137
145
|
end
|
data/lib/familia/settings.rb
CHANGED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# lib/familia/thread_safety/instrumented_mutex.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
require_relative 'monitor'
|
|
6
|
+
|
|
7
|
+
module Familia
|
|
8
|
+
module ThreadSafety
|
|
9
|
+
# A Mutex wrapper that automatically reports contention metrics
|
|
10
|
+
#
|
|
11
|
+
# This class wraps Ruby's standard Mutex to provide automatic
|
|
12
|
+
# instrumentation of lock contention and wait times.
|
|
13
|
+
#
|
|
14
|
+
# @example Basic usage
|
|
15
|
+
# mutex = Familia::ThreadSafety::InstrumentedMutex.new('connection_chain')
|
|
16
|
+
# mutex.synchronize { # critical section }
|
|
17
|
+
#
|
|
18
|
+
# @example With monitoring
|
|
19
|
+
# Familia::ThreadSafety::Monitor.start!
|
|
20
|
+
# mutex = Familia::ThreadSafety::InstrumentedMutex.new('field_registration')
|
|
21
|
+
# mutex.synchronize { # automatically tracked }
|
|
22
|
+
class InstrumentedMutex
|
|
23
|
+
attr_reader :name, :mutex
|
|
24
|
+
|
|
25
|
+
# Create a new instrumented mutex
|
|
26
|
+
#
|
|
27
|
+
# @param name [String, Symbol] Identifier for this mutex in monitoring
|
|
28
|
+
# @param monitor [Monitor, nil] Monitor instance to use (defaults to singleton)
|
|
29
|
+
def initialize(name, monitor = nil)
|
|
30
|
+
@name = name.to_s
|
|
31
|
+
@mutex = ::Mutex.new
|
|
32
|
+
@monitor = monitor || Monitor.instance
|
|
33
|
+
@lock_count = Concurrent::AtomicFixnum.new(0)
|
|
34
|
+
@contention_count = Concurrent::AtomicFixnum.new(0)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Synchronize with automatic monitoring
|
|
38
|
+
#
|
|
39
|
+
# @yield Block to execute while holding the lock
|
|
40
|
+
# @return Result of the block
|
|
41
|
+
def synchronize
|
|
42
|
+
return yield unless @monitor.enabled
|
|
43
|
+
|
|
44
|
+
acquired = false
|
|
45
|
+
wait_start = Familia.now_in_μs
|
|
46
|
+
|
|
47
|
+
# Try non-blocking acquisition first to detect contention
|
|
48
|
+
if @mutex.try_lock
|
|
49
|
+
acquired = true
|
|
50
|
+
wait_time = 0
|
|
51
|
+
@lock_count.increment
|
|
52
|
+
else
|
|
53
|
+
# Contention detected
|
|
54
|
+
@contention_count.increment
|
|
55
|
+
@monitor.record_contention(@name)
|
|
56
|
+
|
|
57
|
+
# Now do blocking acquisition
|
|
58
|
+
@mutex.lock
|
|
59
|
+
acquired = true
|
|
60
|
+
wait_end = Familia.now_in_μs
|
|
61
|
+
wait_time_μs = wait_end - wait_start
|
|
62
|
+
|
|
63
|
+
@lock_count.increment
|
|
64
|
+
@monitor.record_wait_time(@name, wait_time_μs)
|
|
65
|
+
|
|
66
|
+
if wait_time_μs > 10_000 # Log if waited more than 10ms (10,000μs)
|
|
67
|
+
Familia.trace(:MUTEX_WAIT, nil, "Waited #{(wait_time_μs / 1000.0).round(2)}ms for #{@name}")
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
yield
|
|
72
|
+
ensure
|
|
73
|
+
@mutex.unlock if acquired
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Acquire the lock (with monitoring)
|
|
77
|
+
def lock
|
|
78
|
+
return @mutex.lock unless @monitor.enabled
|
|
79
|
+
|
|
80
|
+
wait_start = Familia.now_in_μs
|
|
81
|
+
|
|
82
|
+
if @mutex.try_lock
|
|
83
|
+
@lock_count.increment
|
|
84
|
+
return true
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Contention detected
|
|
88
|
+
@contention_count.increment
|
|
89
|
+
@monitor.record_contention(@name)
|
|
90
|
+
|
|
91
|
+
result = @mutex.lock
|
|
92
|
+
wait_end = Familia.now_in_μs
|
|
93
|
+
wait_time_μs = wait_end - wait_start
|
|
94
|
+
|
|
95
|
+
@lock_count.increment
|
|
96
|
+
@monitor.record_wait_time(@name, wait_time_μs)
|
|
97
|
+
|
|
98
|
+
result
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Try to acquire the lock without blocking
|
|
102
|
+
def try_lock
|
|
103
|
+
result = @mutex.try_lock
|
|
104
|
+
@lock_count.increment if result
|
|
105
|
+
result
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Release the lock
|
|
109
|
+
def unlock
|
|
110
|
+
@mutex.unlock
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Check if locked by current thread
|
|
114
|
+
def locked?
|
|
115
|
+
@mutex.locked?
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Check if owned by current thread
|
|
119
|
+
def owned?
|
|
120
|
+
@mutex.owned?
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Sleep and release the lock temporarily
|
|
124
|
+
def sleep(timeout = nil)
|
|
125
|
+
@mutex.sleep(timeout)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Get statistics for this mutex
|
|
129
|
+
def stats
|
|
130
|
+
{
|
|
131
|
+
name: @name,
|
|
132
|
+
lock_count: @lock_count.value,
|
|
133
|
+
contention_count: @contention_count.value,
|
|
134
|
+
contention_rate: contention_rate
|
|
135
|
+
}
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Calculate contention rate (0.0 to 1.0)
|
|
139
|
+
def contention_rate
|
|
140
|
+
total = @lock_count.value
|
|
141
|
+
return 0.0 if total == 0
|
|
142
|
+
|
|
143
|
+
@contention_count.value.to_f / total
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Create a double-checked locking helper
|
|
147
|
+
#
|
|
148
|
+
# @param check [Proc] Condition to check
|
|
149
|
+
# @param init [Proc] Initialization to perform if check fails
|
|
150
|
+
# @return Result of check or init
|
|
151
|
+
def double_checked_locking(check, init)
|
|
152
|
+
# Fast path - check without lock
|
|
153
|
+
value = check.call
|
|
154
|
+
return value if value
|
|
155
|
+
|
|
156
|
+
# Slow path - check again with lock
|
|
157
|
+
synchronize do
|
|
158
|
+
value = check.call
|
|
159
|
+
return value if value
|
|
160
|
+
|
|
161
|
+
init.call
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|