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,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
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
# lib/familia/thread_safety/monitor.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
require 'concurrent-ruby'
|
|
6
|
+
|
|
7
|
+
module Familia
|
|
8
|
+
module ThreadSafety
|
|
9
|
+
# Thread safety monitoring for production observability
|
|
10
|
+
#
|
|
11
|
+
# Tracks mutex contention, race conditions, and synchronization metrics
|
|
12
|
+
# to provide insights into thread safety behavior in production.
|
|
13
|
+
#
|
|
14
|
+
# @example Basic usage
|
|
15
|
+
# Familia::ThreadSafety::Monitor.start!
|
|
16
|
+
# # ... application runs ...
|
|
17
|
+
# report = Familia::ThreadSafety::Monitor.report
|
|
18
|
+
# puts report[:summary]
|
|
19
|
+
#
|
|
20
|
+
# @example Custom instrumentation
|
|
21
|
+
# Familia::ThreadSafety::Monitor.record_contention('connection_chain')
|
|
22
|
+
# Familia::ThreadSafety::Monitor.time_critical_section('field_registration') do
|
|
23
|
+
# # ... critical code ...
|
|
24
|
+
# end
|
|
25
|
+
class Monitor
|
|
26
|
+
class << self
|
|
27
|
+
def instance
|
|
28
|
+
@instance ||= new
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Delegate all methods to singleton instance
|
|
32
|
+
def method_missing(method, *args, &block)
|
|
33
|
+
if instance.respond_to?(method)
|
|
34
|
+
instance.send(method, *args, &block)
|
|
35
|
+
else
|
|
36
|
+
super
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def respond_to_missing?(method, include_private = false)
|
|
41
|
+
instance.respond_to?(method) || super
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
attr_reader :enabled, :started_at
|
|
46
|
+
|
|
47
|
+
def initialize
|
|
48
|
+
@enabled = false
|
|
49
|
+
@started_at = nil
|
|
50
|
+
@mutex_contentions = Concurrent::AtomicFixnum.new(0)
|
|
51
|
+
@race_detections = Concurrent::AtomicFixnum.new(0)
|
|
52
|
+
@critical_sections = Concurrent::AtomicFixnum.new(0)
|
|
53
|
+
@deadlock_checks = Concurrent::AtomicFixnum.new(0)
|
|
54
|
+
|
|
55
|
+
# Track contention points with counts
|
|
56
|
+
@contention_points = Concurrent::Map.new
|
|
57
|
+
|
|
58
|
+
# Track wait time aggregates for critical sections (removed @wait_times to prevent memory leak)
|
|
59
|
+
@wait_time_totals = Concurrent::Map.new
|
|
60
|
+
@wait_time_counts = Concurrent::Map.new
|
|
61
|
+
|
|
62
|
+
# Track thread-local state for nested monitoring
|
|
63
|
+
@thread_state = Concurrent::Map.new
|
|
64
|
+
|
|
65
|
+
# Track concurrent operation counts
|
|
66
|
+
@concurrent_operations = Concurrent::Map.new
|
|
67
|
+
|
|
68
|
+
# Performance metrics
|
|
69
|
+
@section_timings = Concurrent::Map.new
|
|
70
|
+
@section_counts = Concurrent::Map.new
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Start monitoring
|
|
74
|
+
def start!
|
|
75
|
+
@enabled = true
|
|
76
|
+
@started_at = Time.now
|
|
77
|
+
reset_metrics
|
|
78
|
+
Familia.info("[ThreadSafety] Monitoring started")
|
|
79
|
+
true
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Stop monitoring
|
|
83
|
+
def stop!
|
|
84
|
+
@enabled = false
|
|
85
|
+
duration = @started_at ? Time.now - @started_at : 0
|
|
86
|
+
Familia.info("[ThreadSafety] Monitoring stopped after #{duration.round(2)}s")
|
|
87
|
+
@started_at = nil
|
|
88
|
+
true
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Reset all metrics
|
|
92
|
+
def reset_metrics
|
|
93
|
+
@mutex_contentions.value = 0
|
|
94
|
+
@race_detections.value = 0
|
|
95
|
+
@critical_sections.value = 0
|
|
96
|
+
@deadlock_checks.value = 0
|
|
97
|
+
@contention_points.clear
|
|
98
|
+
# @wait_times.clear - removed to prevent memory leak
|
|
99
|
+
@wait_time_totals.clear
|
|
100
|
+
@wait_time_counts.clear
|
|
101
|
+
@thread_state.clear
|
|
102
|
+
@concurrent_operations.clear
|
|
103
|
+
@section_timings.clear
|
|
104
|
+
@section_counts.clear
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Record a mutex contention event
|
|
108
|
+
def record_contention(location, wait_time = nil)
|
|
109
|
+
return unless @enabled
|
|
110
|
+
|
|
111
|
+
@mutex_contentions.increment
|
|
112
|
+
@contention_points[location] = @contention_points.fetch(location, 0) + 1
|
|
113
|
+
|
|
114
|
+
if wait_time
|
|
115
|
+
record_wait_time(location, wait_time)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
Familia.trace(:THREAD_CONTENTION, nil, "Contention at #{location} (wait: #{wait_time&.round(4)}s)")
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Record wait time for a location
|
|
122
|
+
def record_wait_time(location, wait_time)
|
|
123
|
+
# Note: @wait_times was removed to prevent memory leak from unbounded array growth
|
|
124
|
+
# We only need the aggregated totals and counts for calculations
|
|
125
|
+
@wait_time_totals[location] = @wait_time_totals.fetch(location, 0.0) + wait_time
|
|
126
|
+
@wait_time_counts[location] = @wait_time_counts.fetch(location, 0) + 1
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Record a potential race condition detection
|
|
130
|
+
def record_race_condition(location, details = nil)
|
|
131
|
+
return unless @enabled
|
|
132
|
+
|
|
133
|
+
@race_detections.increment
|
|
134
|
+
msg = "Potential race condition at #{location}"
|
|
135
|
+
msg += ": #{details}" if details
|
|
136
|
+
Familia.warn("[ThreadSafety] #{msg}")
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Time a critical section with contention tracking
|
|
140
|
+
def time_critical_section(name)
|
|
141
|
+
return yield unless @enabled
|
|
142
|
+
|
|
143
|
+
thread_id = Thread.current.object_id
|
|
144
|
+
start_time = Familia.now_in_μs
|
|
145
|
+
|
|
146
|
+
# Check for concurrent execution
|
|
147
|
+
concurrent_count = @concurrent_operations[name] = @concurrent_operations.fetch(name, 0) + 1
|
|
148
|
+
if concurrent_count > 1
|
|
149
|
+
record_contention(name)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
@critical_sections.increment
|
|
153
|
+
|
|
154
|
+
begin
|
|
155
|
+
result = yield
|
|
156
|
+
ensure
|
|
157
|
+
end_time = Familia.now_in_μs
|
|
158
|
+
duration_μs = end_time - start_time
|
|
159
|
+
|
|
160
|
+
# Record timing in microseconds
|
|
161
|
+
@section_timings[name] = @section_timings.fetch(name, 0) + duration_μs
|
|
162
|
+
@section_counts[name] = @section_counts.fetch(name, 0) + 1
|
|
163
|
+
|
|
164
|
+
# Decrement concurrent count
|
|
165
|
+
@concurrent_operations[name] = @concurrent_operations.fetch(name, 1) - 1
|
|
166
|
+
|
|
167
|
+
if duration_μs > 100_000 # Log slow critical sections (> 100ms = 100,000μs)
|
|
168
|
+
Familia.warn("[ThreadSafety] Slow critical section '#{name}': #{(duration_μs / 1000.0).round(2)}ms")
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
result
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# NOTE: monitor_mutex method was removed as it was unused and had flawed
|
|
176
|
+
# exception handling that could lead to deadlocks. The InstrumentedMutex
|
|
177
|
+
# class should be used instead for mutex monitoring.
|
|
178
|
+
|
|
179
|
+
# Check for potential deadlocks
|
|
180
|
+
def check_deadlock
|
|
181
|
+
return unless @enabled
|
|
182
|
+
|
|
183
|
+
@deadlock_checks.increment
|
|
184
|
+
|
|
185
|
+
# This is a simple check - in production you might want more sophisticated detection
|
|
186
|
+
thread_count = Thread.list.count
|
|
187
|
+
if thread_count > 100
|
|
188
|
+
Familia.warn("[ThreadSafety] High thread count: #{thread_count}")
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Check for threads waiting on mutexes (simplified)
|
|
192
|
+
waiting_threads = Thread.list.select { |t| t.status == "sleep" }
|
|
193
|
+
if waiting_threads.size > thread_count * 0.8
|
|
194
|
+
Familia.warn("[ThreadSafety] Potential deadlock: #{waiting_threads.size}/#{thread_count} threads sleeping")
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Generate a comprehensive report
|
|
199
|
+
def report
|
|
200
|
+
return { enabled: false, message: "Monitoring not enabled" } unless @started_at
|
|
201
|
+
|
|
202
|
+
duration = Time.now - @started_at
|
|
203
|
+
|
|
204
|
+
# Calculate hot spots
|
|
205
|
+
hot_spots = []
|
|
206
|
+
@contention_points.each_pair do |location, count|
|
|
207
|
+
hot_spots << [location, count]
|
|
208
|
+
end
|
|
209
|
+
hot_spots = hot_spots
|
|
210
|
+
.sort_by { |_, count| -count }
|
|
211
|
+
.first(10)
|
|
212
|
+
.map { |location, count|
|
|
213
|
+
avg_wait_μs = if @wait_time_counts[location] && @wait_time_counts[location] > 0
|
|
214
|
+
(@wait_time_totals[location] / @wait_time_counts[location]).round(0)
|
|
215
|
+
else
|
|
216
|
+
0
|
|
217
|
+
end
|
|
218
|
+
{
|
|
219
|
+
location: location,
|
|
220
|
+
contentions: count,
|
|
221
|
+
avg_wait_μs: avg_wait_μs
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
# Calculate critical section performance
|
|
226
|
+
section_performance = []
|
|
227
|
+
@section_counts.each_pair do |name, count|
|
|
228
|
+
avg_time_μs = (@section_timings[name] / count).round(0)
|
|
229
|
+
section_performance << {
|
|
230
|
+
section: name,
|
|
231
|
+
calls: count,
|
|
232
|
+
avg_time_μs: avg_time_μs,
|
|
233
|
+
total_time_μs: @section_timings[name]
|
|
234
|
+
}
|
|
235
|
+
end
|
|
236
|
+
section_performance.sort_by! { |s| -s[:total_time_μs] }
|
|
237
|
+
|
|
238
|
+
{
|
|
239
|
+
summary: {
|
|
240
|
+
monitoring_duration_s: duration.round(2),
|
|
241
|
+
mutex_contentions: @mutex_contentions.value,
|
|
242
|
+
race_detections: @race_detections.value,
|
|
243
|
+
critical_sections: @critical_sections.value,
|
|
244
|
+
deadlock_checks: @deadlock_checks.value
|
|
245
|
+
},
|
|
246
|
+
hot_spots: hot_spots,
|
|
247
|
+
section_performance: section_performance,
|
|
248
|
+
health: calculate_health_score,
|
|
249
|
+
recommendations: generate_recommendations(hot_spots)
|
|
250
|
+
}
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# Calculate a health score (0-100)
|
|
254
|
+
def calculate_health_score
|
|
255
|
+
return 100 unless @started_at
|
|
256
|
+
|
|
257
|
+
duration = Time.now - @started_at
|
|
258
|
+
return 100 if duration < 60 # Need at least 1 minute of data
|
|
259
|
+
|
|
260
|
+
contentions_per_hour = (@mutex_contentions.value / duration) * 3600
|
|
261
|
+
races_per_hour = (@race_detections.value / duration) * 3600
|
|
262
|
+
|
|
263
|
+
score = 100
|
|
264
|
+
score -= [contentions_per_hour / 10.0, 30].min # -3 points per 100 contentions/hour, max -30
|
|
265
|
+
score -= [races_per_hour * 10, 50].min # -10 points per race/hour, max -50
|
|
266
|
+
|
|
267
|
+
[score, 0].max.round
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Generate recommendations based on metrics
|
|
271
|
+
def generate_recommendations(hot_spots)
|
|
272
|
+
recommendations = []
|
|
273
|
+
|
|
274
|
+
if @race_detections.value > 0
|
|
275
|
+
recommendations << {
|
|
276
|
+
severity: 'critical',
|
|
277
|
+
message: "#{@race_detections.value} potential race conditions detected - investigate immediately"
|
|
278
|
+
}
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
if hot_spots.any? { |h| h[:contentions] > 100 }
|
|
282
|
+
high_contention = hot_spots.select { |h| h[:contentions] > 100 }
|
|
283
|
+
locations = high_contention.map { |h| h[:location] }.join(', ')
|
|
284
|
+
recommendations << {
|
|
285
|
+
severity: 'warning',
|
|
286
|
+
message: "High contention detected at: #{locations}"
|
|
287
|
+
}
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
if hot_spots.any? { |h| h[:avg_wait_μs] > 100_000 } # > 100ms in microseconds
|
|
291
|
+
slow_spots = hot_spots.select { |h| h[:avg_wait_μs] > 100_000 }
|
|
292
|
+
recommendations << {
|
|
293
|
+
severity: 'warning',
|
|
294
|
+
message: "Long wait times at: #{slow_spots.map { |h| "#{h[:location]} (#{(h[:avg_wait_μs] / 1000.0).round(1)}ms)" }.join(', ')}"
|
|
295
|
+
}
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
if @deadlock_checks.value > 0 && Thread.list.count > 50
|
|
299
|
+
recommendations << {
|
|
300
|
+
severity: 'info',
|
|
301
|
+
message: "Consider connection pooling - high thread count detected"
|
|
302
|
+
}
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
recommendations
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
# Export metrics in a format suitable for APM tools
|
|
309
|
+
def export_metrics
|
|
310
|
+
{
|
|
311
|
+
'familia.thread_safety.mutex_contentions' => @mutex_contentions.value,
|
|
312
|
+
'familia.thread_safety.race_detections' => @race_detections.value,
|
|
313
|
+
'familia.thread_safety.critical_sections' => @critical_sections.value,
|
|
314
|
+
'familia.thread_safety.deadlock_checks' => @deadlock_checks.value,
|
|
315
|
+
'familia.thread_safety.health_score' => calculate_health_score
|
|
316
|
+
}
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
# Hook for APM integration
|
|
320
|
+
def apm_transaction(name, &block)
|
|
321
|
+
return yield unless @enabled
|
|
322
|
+
|
|
323
|
+
# This is where you'd integrate with NewRelic, DataDog, etc.
|
|
324
|
+
time_critical_section(name, &block)
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
end
|
data/lib/familia/utils.rb
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# lib/familia/utils.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
module Familia
|
|
4
6
|
# Family-related utility methods
|
|
@@ -42,6 +44,17 @@ module Familia
|
|
|
42
44
|
current_time.utc.to_f
|
|
43
45
|
end
|
|
44
46
|
|
|
47
|
+
# Returns the current time in microseconds.
|
|
48
|
+
# This is used to measure the duration of Database commands.
|
|
49
|
+
#
|
|
50
|
+
# Alias: now_in_microseconds
|
|
51
|
+
#
|
|
52
|
+
# @return [Integer] The current time in microseconds.
|
|
53
|
+
def now_in_μs
|
|
54
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
|
|
55
|
+
end
|
|
56
|
+
alias now_in_microseconds now_in_μs
|
|
57
|
+
|
|
45
58
|
# A quantized timestamp
|
|
46
59
|
#
|
|
47
60
|
# @param quantum [Integer] The time quantum in seconds (default: 10 minutes).
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# lib/familia/verifiable_identifier.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
require 'openssl'
|
|
4
6
|
require_relative 'secure_identifier'
|
|
@@ -33,7 +35,7 @@ module Familia
|
|
|
33
35
|
# $ openssl rand -hex 32
|
|
34
36
|
# > cafef00dcafef00dcafef00dcafef00dcafef00dcafef00d
|
|
35
37
|
#
|
|
36
|
-
# 2.
|
|
38
|
+
# 2. Set it as an environment variable in your production environment:
|
|
37
39
|
# export VERIFIABLE_ID_HMAC_SECRET="cafef00dcafef00dcafef00dcafef00dcafef00dcafef00d"
|
|
38
40
|
#
|
|
39
41
|
SECRET_KEY = ENV.fetch('VERIFIABLE_ID_HMAC_SECRET', 'cafef00dcafef00dcafef00dcafef00dcafef00dcafef00d')
|
data/lib/familia/version.rb
CHANGED
data/lib/familia.rb
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
# lib/familia.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
require 'oj'
|
|
4
6
|
require 'redis'
|
|
5
7
|
require 'uri/valkey'
|
|
6
8
|
require 'connection_pool'
|
|
9
|
+
require 'concurrent-ruby'
|
|
7
10
|
|
|
8
11
|
# OJ configuration is handled internally by Familia::JsonSerializer
|
|
9
12
|
|
|
@@ -11,6 +14,8 @@ require_relative 'multi_result'
|
|
|
11
14
|
require_relative 'familia/refinements'
|
|
12
15
|
require_relative 'familia/errors'
|
|
13
16
|
require_relative 'familia/version'
|
|
17
|
+
require_relative 'familia/thread_safety/monitor'
|
|
18
|
+
require_relative 'familia/thread_safety/instrumented_mutex'
|
|
14
19
|
|
|
15
20
|
# Familia - A family warehouse for Valkey/Redis
|
|
16
21
|
#
|
|
@@ -39,9 +44,30 @@ module Familia
|
|
|
39
44
|
using Refinements::StylizeWords
|
|
40
45
|
|
|
41
46
|
class << self
|
|
42
|
-
|
|
47
|
+
attr_writer :debug
|
|
43
48
|
attr_reader :members
|
|
44
49
|
|
|
50
|
+
# Thread safety monitoring controls
|
|
51
|
+
def thread_safety_monitor
|
|
52
|
+
ThreadSafety::Monitor.instance
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def start_monitoring!
|
|
56
|
+
thread_safety_monitor.start!
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def stop_monitoring!
|
|
60
|
+
thread_safety_monitor.stop!
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def thread_safety_report
|
|
64
|
+
thread_safety_monitor.report
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def thread_safety_metrics
|
|
68
|
+
thread_safety_monitor.export_metrics
|
|
69
|
+
end
|
|
70
|
+
|
|
45
71
|
def included(member)
|
|
46
72
|
raise Problem, "#{member} should subclass Familia::Horreum"
|
|
47
73
|
end
|
|
@@ -87,7 +113,7 @@ module Familia
|
|
|
87
113
|
# @param klass [Class] The class to remove from members
|
|
88
114
|
# @return [Class, nil] The removed class or nil if not found
|
|
89
115
|
def unload_member(klass)
|
|
90
|
-
Familia.
|
|
116
|
+
Familia.debug "[unload_member] Removing #{klass} from members"
|
|
91
117
|
@members.delete(klass)
|
|
92
118
|
end
|
|
93
119
|
|
|
@@ -97,7 +123,7 @@ module Familia
|
|
|
97
123
|
# @return [Array<Class>] The removed anonymous classes
|
|
98
124
|
def clear_anonymous_members
|
|
99
125
|
anonymous_classes = @members.select { |m| m.name.nil? }
|
|
100
|
-
Familia.
|
|
126
|
+
Familia.debug "[clear_anonymous_members] Removing #{anonymous_classes.size} anonymous classes"
|
|
101
127
|
@members.reject! { |m| m.name.nil? }
|
|
102
128
|
anonymous_classes
|
|
103
129
|
end
|
|
@@ -128,7 +154,7 @@ module Familia
|
|
|
128
154
|
# Familia.member_by_config_name(:nonexistent) # => nil
|
|
129
155
|
#
|
|
130
156
|
def member_by_config_name(config_name)
|
|
131
|
-
Familia.
|
|
157
|
+
Familia.debug "[member_by_config_name] #{members.map(&:config_name)} #{config_name}"
|
|
132
158
|
|
|
133
159
|
members.find { |m| m.config_name.to_s.eql?(config_name.to_s) }
|
|
134
160
|
end
|
|
@@ -149,6 +175,7 @@ module Familia
|
|
|
149
175
|
extend Utils
|
|
150
176
|
end
|
|
151
177
|
|
|
178
|
+
require_relative 'familia/instrumentation'
|
|
152
179
|
require_relative 'familia/base'
|
|
153
180
|
require_relative 'familia/features'
|
|
154
181
|
require_relative 'familia/data_type'
|