digitalkin 1.0.0.dev8__tar.gz → 1.0.0.dev9__tar.gz
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.
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/PKG-INFO +1 -1
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/pyproject.toml +1 -1
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/__version__.py +1 -1
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/task_manager/redis/redis_client.py +2 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/task_manager/redis/redis_signal.py +76 -27
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/task_manager/task_executor.py +1 -1
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/grpc_servers/gateway_servicer.py +32 -13
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/grpc_servers/module_server.py +38 -13
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/grpc_servers/module_servicer.py +22 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/settings/redis.py +4 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin.egg-info/PKG-INFO +1 -1
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/LICENSE +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/README.md +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/base_server/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/base_server/mock/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/base_server/mock/mock_pb2.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/base_server/mock/mock_pb2_grpc.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/base_server/server_async_insecure.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/base_server/server_async_secure.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/base_server/server_sync_insecure.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/base_server/server_sync_secure.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/bench_module/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/bench_module/echo_module.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/bench_module/models/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/bench_module/models/input.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/bench_module/models/output.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/bench_module/models/secret.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/bench_module/models/setup.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/bench_module/server.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/bench_module/triggers/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/bench_module/triggers/message_trigger.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/modules/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/modules/archetype_with_tools_module.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/modules/cpu_intensive_module.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/modules/dynamic_setup_module.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/modules/minimal_llm_module.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/modules/text_transform_module.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/monitoring/digitalkin_observability/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/monitoring/digitalkin_observability/http_server.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/monitoring/digitalkin_observability/interceptors.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/monitoring/digitalkin_observability/metrics.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/monitoring/digitalkin_observability/prometheus.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/monitoring/tests/test_metrics.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/redis_demo/client.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/redis_demo/echo_module.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/redis_demo/models/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/redis_demo/models/input.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/redis_demo/models/output.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/redis_demo/models/secret.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/redis_demo/models/setup.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/redis_demo/server.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/redis_demo/triggers/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/redis_demo/triggers/message_trigger.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/services/filesystem_module.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/services/storage_module.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/setup.cfg +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/community/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/community/agno/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/community/agno/agno_adapter.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/community/agno/agui_tools.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/community/agno/hitl.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/community/agno/models.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/common/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/common/factories.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/exceptions.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/job_manager/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/job_manager/base_job_manager.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/job_manager/single_job_manager.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/profiling/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/profiling/step_timer.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/profiling/task_profiler.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/resilience/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/resilience/bulkhead.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/resilience/task_supervisor.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/task_manager/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/task_manager/base_task_manager.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/task_manager/local_task_manager.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/task_manager/module_runner.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/task_manager/redis/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/task_manager/redis/instrumented.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/task_manager/redis/proto_streams.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/task_manager/redis/redis_checkpoint.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/task_manager/redis/redis_idempotency.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/task_manager/redis/redis_state.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/task_manager/redis/redis_streams.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/task_manager/redis/shadow.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/task_manager/remote_task_manager.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/task_manager/task_session.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/exceptions.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/grpc_servers/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/grpc_servers/_base_server.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/grpc_servers/exceptions.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/grpc_servers/interceptors/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/grpc_servers/interceptors/circuit_breaker_interceptor.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/grpc_servers/m2m_call_registry.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/grpc_servers/stream_registry.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/grpc_servers/stream_session.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/grpc_servers/utils/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/grpc_servers/utils/circuit_breaker.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/grpc_servers/utils/grpc_client_wrapper.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/grpc_servers/utils/grpc_error_handler.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/grpc_servers/utils/utility_schema_extender.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/grpc_servers/utils/validators.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/logger.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/mixins/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/mixins/agui_mixin.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/mixins/base_mixin.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/mixins/cost_mixin.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/mixins/file_history_mixin.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/mixins/filesystem_mixin.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/mixins/logger_mixin.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/mixins/storage_mixin.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/core/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/core/job_manager_models.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/core/redis.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/core/task_monitor.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/events/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/events/agent_events.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/grpc_servers/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/grpc_servers/circuit_breaker.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/grpc_servers/m2m.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/grpc_servers/models.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/grpc_servers/stream_error_codes.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/grpc_servers/types.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/module/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/module/ag_ui.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/module/base_types.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/module/module.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/module/module_context.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/module/module_types.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/module/request_metadata.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/module/select_schema.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/module/setup_types.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/module/tool_cache.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/module/tool_reference.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/module/utility.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/services/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/services/cost.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/services/registry.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/services/services.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/services/storage.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/settings/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/settings/consumer.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/settings/gateway.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/settings/grpc_client.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/settings/log.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/settings/module.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/settings/profiling.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/settings/queue.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/settings/resilience.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/settings/server/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/settings/server/channel.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/settings/server/grpc.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/settings/server/server.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/settings/server/servicer.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/settings/task_manager.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/settings/utils/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/settings/utils/channel.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/utils/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/utils/dynamic_schema.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/modules/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/modules/_base_module.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/modules/archetype_module.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/modules/tool_module.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/modules/trigger_handler.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/modules/triggers/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/modules/triggers/healthcheck_ping_trigger.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/modules/triggers/healthcheck_services_trigger.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/modules/triggers/healthcheck_status_trigger.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/py.typed +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/base_strategy.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/communication/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/communication/communication_strategy.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/communication/default_communication.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/communication/exceptions.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/communication/grpc_communication.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/cost/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/cost/cost_strategy.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/cost/default_cost.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/cost/exceptions.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/cost/grpc_cost.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/filesystem/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/filesystem/default_filesystem.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/filesystem/exceptions.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/filesystem/filesystem_strategy.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/filesystem/grpc_filesystem.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/identity/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/identity/default_identity.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/identity/identity_strategy.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/registry/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/registry/default_registry.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/registry/exceptions.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/registry/grpc_registry.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/registry/registry_models.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/registry/registry_strategy.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/services_config.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/services_models.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/setup/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/setup/default_setup.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/setup/exceptions.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/setup/grpc_setup.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/setup/setup_strategy.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/storage/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/storage/default_storage.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/storage/exceptions.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/storage/grpc_storage.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/storage/storage_strategy.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/task_manager/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/task_manager/default_task_manager.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/task_manager/exceptions.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/task_manager/redis_task_manager.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/task_manager/task_manager_strategy.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/user_profile/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/user_profile/default_user_profile.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/user_profile/exceptions.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/user_profile/grpc_user_profile.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/user_profile/user_profile_strategy.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/utils/__init__.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/utils/arg_parser.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/utils/conditional_schema.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/utils/development_mode_action.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/utils/dynamic_schema.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/utils/exceptions.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/utils/llm_ready_schema.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/utils/package_discover.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/utils/proto_utils.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/utils/schema_splitter.py +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin.egg-info/SOURCES.txt +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin.egg-info/dependency_links.txt +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin.egg-info/requires.txt +0 -0
- {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin.egg-info/top_level.txt +0 -0
|
@@ -51,11 +51,13 @@ class RedisClient: # noqa: PLR0904
|
|
|
51
51
|
self.url,
|
|
52
52
|
max_connections=default_size,
|
|
53
53
|
decode_responses=False,
|
|
54
|
+
health_check_interval=pool.health_check_interval,
|
|
54
55
|
)
|
|
55
56
|
self._blocking_client = aioredis.Redis.from_url(
|
|
56
57
|
self.url,
|
|
57
58
|
max_connections=blocking_size,
|
|
58
59
|
decode_responses=False,
|
|
60
|
+
health_check_interval=pool.health_check_interval,
|
|
59
61
|
)
|
|
60
62
|
|
|
61
63
|
from digitalkin.grpc_servers.utils.validators import GatewayValidator
|
|
@@ -7,6 +7,7 @@ import contextlib
|
|
|
7
7
|
import json
|
|
8
8
|
import random
|
|
9
9
|
import time
|
|
10
|
+
import uuid
|
|
10
11
|
from typing import TYPE_CHECKING, Any, ClassVar
|
|
11
12
|
|
|
12
13
|
from digitalkin.core.resilience.task_supervisor import log_unhandled
|
|
@@ -15,12 +16,20 @@ from digitalkin.logger import logger
|
|
|
15
16
|
from digitalkin.models.settings.redis import get_redis_settings
|
|
16
17
|
|
|
17
18
|
if TYPE_CHECKING:
|
|
19
|
+
from collections.abc import Awaitable, Callable
|
|
20
|
+
|
|
18
21
|
from digitalkin.core.task_manager.task_session import TaskSession
|
|
19
22
|
|
|
23
|
+
CacheInvalidator = Callable[[str, str], Awaitable[None]]
|
|
24
|
+
|
|
20
25
|
|
|
21
26
|
class SharedRedisListener:
|
|
22
27
|
"""One PubSub connection per Redis URL; direct-dispatches signals to tasks."""
|
|
23
28
|
|
|
29
|
+
PROCESS_ID: ClassVar[str] = uuid.uuid4().hex
|
|
30
|
+
"""Per-process UUID generated at class definition; identifies this listener on
|
|
31
|
+
``signal_ch:_global_`` broadcasts. ``os.getpid()`` collides in Docker (always 1)."""
|
|
32
|
+
|
|
24
33
|
_instances: ClassVar[dict[str, SharedRedisListener]] = {}
|
|
25
34
|
|
|
26
35
|
@classmethod
|
|
@@ -74,6 +83,7 @@ class SharedRedisListener:
|
|
|
74
83
|
self._pubsub: Any = None
|
|
75
84
|
self._listen_task: asyncio.Task[None] | None = None
|
|
76
85
|
self._stop_event = asyncio.Event()
|
|
86
|
+
self._start_lock = asyncio.Lock()
|
|
77
87
|
self._counters: dict[str, int] = {
|
|
78
88
|
"received": 0,
|
|
79
89
|
"deduped": 0,
|
|
@@ -81,58 +91,70 @@ class SharedRedisListener:
|
|
|
81
91
|
"dropped": 0,
|
|
82
92
|
"restarts": 0,
|
|
83
93
|
"subscribed": 0,
|
|
94
|
+
"invalidated": 0,
|
|
84
95
|
}
|
|
85
96
|
self._last_counters_log = time.monotonic()
|
|
97
|
+
self._cache_invalidator: CacheInvalidator | None = None
|
|
98
|
+
|
|
99
|
+
def set_cache_invalidator(self, handler: CacheInvalidator) -> None:
|
|
100
|
+
"""Register the ``(action_name, setup_id)`` handler invoked for ``invalidate_*`` signals."""
|
|
101
|
+
self._cache_invalidator = handler
|
|
102
|
+
|
|
103
|
+
async def start(self) -> None:
|
|
104
|
+
"""Open PubSub, PSUBSCRIBE ``signal_ch:*``, and start the listen loop. Idempotent under concurrent callers."""
|
|
105
|
+
async with self._start_lock:
|
|
106
|
+
if self._pubsub is not None and self._listen_task is not None and not self._listen_task.done():
|
|
107
|
+
return
|
|
108
|
+
psub_t0 = time.perf_counter_ns()
|
|
109
|
+
self._pubsub = self._redis_client.pubsub()
|
|
110
|
+
await self._pubsub.psubscribe("signal_ch:*")
|
|
111
|
+
psub_ms = (time.perf_counter_ns() - psub_t0) / 1e6
|
|
112
|
+
logger.info(
|
|
113
|
+
"[lat-audit] signal_psubscribe: psubscribe_ms=%.2f pattern=signal_ch:* phase=boot origin=%s",
|
|
114
|
+
psub_ms, SharedRedisListener.PROCESS_ID,
|
|
115
|
+
)
|
|
116
|
+
self._stop_event = asyncio.Event()
|
|
117
|
+
self._listen_task = asyncio.create_task(self._listen_loop(), name="shared_redis_listener")
|
|
118
|
+
self._listen_task.add_done_callback(log_unhandled)
|
|
86
119
|
|
|
87
|
-
|
|
120
|
+
def register(
|
|
88
121
|
self,
|
|
89
122
|
task_id: str,
|
|
90
123
|
session: TaskSession,
|
|
91
124
|
task: asyncio.Task[None],
|
|
92
125
|
) -> None:
|
|
93
|
-
"""
|
|
126
|
+
"""Store session + task refs; sub-millisecond, never awaits.
|
|
94
127
|
|
|
95
128
|
Raises:
|
|
96
|
-
RuntimeError: If max registered tasks is exceeded.
|
|
129
|
+
RuntimeError: If max registered tasks is exceeded or ``start()`` was never called.
|
|
97
130
|
"""
|
|
131
|
+
if self._listen_task is None or self._listen_task.done():
|
|
132
|
+
msg = "SharedRedisListener.register called before start()"
|
|
133
|
+
raise RuntimeError(msg)
|
|
98
134
|
sig = get_redis_settings().signal
|
|
99
135
|
if len(self._task_refs) >= sig.max_tasks:
|
|
100
136
|
msg = f"SharedRedisListener: max tasks ({sig.max_tasks}) exceeded"
|
|
101
137
|
raise RuntimeError(msg)
|
|
102
138
|
|
|
103
|
-
|
|
104
|
-
if self._pubsub is None:
|
|
105
|
-
self._pubsub = self._redis_client.pubsub()
|
|
106
|
-
await self._pubsub.subscribe(f"signal_ch:{task_id}")
|
|
107
|
-
sub_ms = (time.perf_counter_ns() - sub_t0) / 1e6
|
|
108
|
-
|
|
139
|
+
reg_t0 = time.perf_counter_ns()
|
|
109
140
|
self._task_sessions[task_id] = session
|
|
110
141
|
self._task_refs[task_id] = task
|
|
111
142
|
task.add_done_callback(lambda _: self.unregister(task_id))
|
|
112
143
|
|
|
113
|
-
started_loop = False
|
|
114
|
-
if self._listen_task is None or self._listen_task.done():
|
|
115
|
-
self._stop_event = asyncio.Event()
|
|
116
|
-
self._listen_task = asyncio.create_task(self._listen_loop(), name="shared_redis_listener")
|
|
117
|
-
self._listen_task.add_done_callback(log_unhandled)
|
|
118
|
-
started_loop = True
|
|
119
|
-
|
|
120
144
|
self._counters["subscribed"] += 1
|
|
121
145
|
logger.info(
|
|
122
|
-
"[lat-audit] signal_subscribe:
|
|
123
|
-
|
|
124
|
-
started_loop,
|
|
146
|
+
"[lat-audit] signal_subscribe: register_ms=%.2f active_subs=%d task_id=%s origin=%s",
|
|
147
|
+
(time.perf_counter_ns() - reg_t0) / 1e6,
|
|
125
148
|
len(self._task_refs),
|
|
126
149
|
task_id,
|
|
150
|
+
SharedRedisListener.PROCESS_ID,
|
|
127
151
|
)
|
|
128
152
|
|
|
129
153
|
def unregister(self, task_id: str) -> None:
|
|
130
|
-
"""Drop the task_id;
|
|
154
|
+
"""Drop the task_id. Loop lifetime is process-wide; ``close()`` is the only stop site."""
|
|
131
155
|
self._task_refs.pop(task_id, None)
|
|
132
156
|
self._task_sessions.pop(task_id, None)
|
|
133
157
|
self._last_seen.pop(task_id, None)
|
|
134
|
-
if not self._task_refs:
|
|
135
|
-
self._stop_event.set()
|
|
136
158
|
|
|
137
159
|
def dispatch_signal(self, task_id: str, data: dict[str, Any], raw_json: str) -> bool:
|
|
138
160
|
"""Route a signal: ``cancel``/``stop`` → side channel + ``task.cancel()``; other actions → audit-only.
|
|
@@ -151,6 +173,24 @@ class SharedRedisListener:
|
|
|
151
173
|
e2e_ms = (time.time_ns() - pub_ns) / 1e6 if pub_ns else 0.0
|
|
152
174
|
self._counters["received"] += 1
|
|
153
175
|
|
|
176
|
+
if action.startswith("invalidate_"):
|
|
177
|
+
origin = data.get("origin")
|
|
178
|
+
if origin is not None and origin == SharedRedisListener.PROCESS_ID:
|
|
179
|
+
return True
|
|
180
|
+
setup_id = data.get("setup_id", "")
|
|
181
|
+
self._counters["invalidated"] += 1
|
|
182
|
+
logger.info(
|
|
183
|
+
"[lat-audit] signal_invalidate: e2e_ms=%.2f action=%s setup_id=%s",
|
|
184
|
+
e2e_ms, action, setup_id,
|
|
185
|
+
)
|
|
186
|
+
if self._cache_invalidator is not None:
|
|
187
|
+
inv_task = asyncio.create_task(
|
|
188
|
+
self._cache_invalidator(action.upper(), setup_id),
|
|
189
|
+
name=f"invalidate_{action}",
|
|
190
|
+
)
|
|
191
|
+
inv_task.add_done_callback(log_unhandled)
|
|
192
|
+
return True
|
|
193
|
+
|
|
154
194
|
logger.info(
|
|
155
195
|
"[lat-audit] signal_dispatch: e2e_ms=%.2f dispatch_ms=%.2f action=%s task_id=%s",
|
|
156
196
|
e2e_ms,
|
|
@@ -184,7 +224,7 @@ class SharedRedisListener:
|
|
|
184
224
|
Returns:
|
|
185
225
|
The triple, or ``None`` if the message is not a usable ``signal_ch:`` payload.
|
|
186
226
|
"""
|
|
187
|
-
if msg["type"]
|
|
227
|
+
if msg["type"] not in {"message", "pmessage"}:
|
|
188
228
|
return None
|
|
189
229
|
channel = msg["channel"].decode() if isinstance(msg["channel"], bytes) else msg["channel"]
|
|
190
230
|
if not channel.startswith("signal_ch:"):
|
|
@@ -204,8 +244,14 @@ class SharedRedisListener:
|
|
|
204
244
|
while not self._stop_event.is_set():
|
|
205
245
|
try:
|
|
206
246
|
if self._pubsub is None:
|
|
207
|
-
|
|
208
|
-
|
|
247
|
+
self._pubsub = self._redis_client.pubsub()
|
|
248
|
+
psub_t0 = time.perf_counter_ns()
|
|
249
|
+
await self._pubsub.psubscribe("signal_ch:*")
|
|
250
|
+
psub_ms = (time.perf_counter_ns() - psub_t0) / 1e6
|
|
251
|
+
logger.info(
|
|
252
|
+
"[lat-audit] signal_psubscribe: psubscribe_ms=%.2f pattern=signal_ch:* phase=loop",
|
|
253
|
+
psub_ms,
|
|
254
|
+
)
|
|
209
255
|
msg = await self._pubsub.get_message(ignore_subscribe_messages=True, timeout=0.5)
|
|
210
256
|
if msg is not None:
|
|
211
257
|
parsed = self._parse_message(msg)
|
|
@@ -225,6 +271,7 @@ class SharedRedisListener:
|
|
|
225
271
|
break
|
|
226
272
|
except Exception:
|
|
227
273
|
self._counters["restarts"] += 1
|
|
274
|
+
self._pubsub = None
|
|
228
275
|
logger.exception("SharedRedisListener iteration error, retrying in %.1fs", backoff)
|
|
229
276
|
await asyncio.sleep(backoff)
|
|
230
277
|
backoff = min(backoff * 2, 10.0)
|
|
@@ -234,7 +281,8 @@ class SharedRedisListener:
|
|
|
234
281
|
c = self._counters
|
|
235
282
|
logger.info(
|
|
236
283
|
"[lat-audit] signal_counters: received=%d deduped=%d evicted=%d "
|
|
237
|
-
"dropped=%d listener_restarts=%d active_subs=%d subscribed_total=%d"
|
|
284
|
+
"dropped=%d listener_restarts=%d active_subs=%d subscribed_total=%d "
|
|
285
|
+
"invalidated=%d",
|
|
238
286
|
c["received"],
|
|
239
287
|
c["deduped"],
|
|
240
288
|
c["evicted"],
|
|
@@ -242,6 +290,7 @@ class SharedRedisListener:
|
|
|
242
290
|
c["restarts"],
|
|
243
291
|
len(self._task_refs),
|
|
244
292
|
c["subscribed"],
|
|
293
|
+
c["invalidated"],
|
|
245
294
|
)
|
|
246
295
|
self._last_counters_log = now
|
|
247
296
|
self._listen_task = None
|
|
@@ -258,7 +307,7 @@ class SharedRedisListener:
|
|
|
258
307
|
self._last_seen.clear()
|
|
259
308
|
if self._pubsub is not None:
|
|
260
309
|
with contextlib.suppress(Exception):
|
|
261
|
-
await self._pubsub.
|
|
310
|
+
await self._pubsub.punsubscribe("signal_ch:*")
|
|
262
311
|
with contextlib.suppress(Exception):
|
|
263
312
|
await self._pubsub.aclose()
|
|
264
313
|
self._pubsub = None
|
{digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/task_manager/task_executor.py
RENAMED
|
@@ -126,7 +126,7 @@ class TaskExecutor:
|
|
|
126
126
|
)
|
|
127
127
|
else:
|
|
128
128
|
try:
|
|
129
|
-
|
|
129
|
+
listener.register(task_id, session, task)
|
|
130
130
|
except Exception:
|
|
131
131
|
logger.warning(
|
|
132
132
|
"Signal registration failed — signals disabled for task_id=%s",
|
{digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/grpc_servers/gateway_servicer.py
RENAMED
|
@@ -17,6 +17,7 @@ from redis.exceptions import RedisError
|
|
|
17
17
|
|
|
18
18
|
from digitalkin.core.profiling.step_timer import StepTimer
|
|
19
19
|
from digitalkin.core.task_manager.redis.proto_streams import ProtoStreamReader
|
|
20
|
+
from digitalkin.core.task_manager.redis.redis_signal import SharedRedisListener
|
|
20
21
|
from digitalkin.grpc_servers.m2m_call_registry import M2MCallRegistry
|
|
21
22
|
from digitalkin.grpc_servers.stream_registry import StreamRegistry
|
|
22
23
|
from digitalkin.grpc_servers.stream_session import StreamSession
|
|
@@ -130,8 +131,17 @@ class GatewayServicer:
|
|
|
130
131
|
return task
|
|
131
132
|
|
|
132
133
|
async def start(self) -> None:
|
|
133
|
-
"""Start the M2M call-registry TTL sweeper."""
|
|
134
|
+
"""Start the M2M call-registry TTL sweeper and PSUBSCRIBE the signal listener."""
|
|
134
135
|
await self._m2m.start()
|
|
136
|
+
listener = SharedRedisListener.singleton_or_none()
|
|
137
|
+
if listener is not None:
|
|
138
|
+
try:
|
|
139
|
+
await listener.start()
|
|
140
|
+
except Exception:
|
|
141
|
+
logger.warning(
|
|
142
|
+
"SharedRedisListener.start() failed at boot — first-task PSUBSCRIBE will retry lazily",
|
|
143
|
+
exc_info=True,
|
|
144
|
+
)
|
|
135
145
|
|
|
136
146
|
async def stop(self) -> None:
|
|
137
147
|
"""Shut down registries and cancel the M2M sweeper."""
|
|
@@ -433,26 +443,35 @@ class GatewayServicer:
|
|
|
433
443
|
|
|
434
444
|
try:
|
|
435
445
|
if action_name.startswith("INVALIDATE_"):
|
|
446
|
+
setup_id_for_invalidate = task_id
|
|
436
447
|
if self._cache_handler is not None:
|
|
437
|
-
await self._cache_handler(action_name)
|
|
448
|
+
await self._cache_handler(action_name, setup_id_for_invalidate)
|
|
438
449
|
timer.mark("cache_handler")
|
|
439
450
|
last_mark = "cache_handler"
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
451
|
+
payload = json.dumps({
|
|
452
|
+
"action": action_name.lower(),
|
|
453
|
+
"setup_id": setup_id_for_invalidate,
|
|
454
|
+
"published_at_ns": time.time_ns(),
|
|
455
|
+
})
|
|
456
|
+
try:
|
|
457
|
+
await self._redis_client.publish("signal_ch:_global_", payload)
|
|
458
|
+
timer.mark("global_publish")
|
|
459
|
+
last_mark = "global_publish"
|
|
460
|
+
except RedisError:
|
|
461
|
+
logger.warning(
|
|
462
|
+
"[gateway] INVALIDATE fan-out publish failed — local-only invalidation applied",
|
|
445
463
|
extra=log_extra,
|
|
464
|
+
exc_info=True,
|
|
446
465
|
)
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
timer.elapsed_now_ms(),
|
|
466
|
+
logger.info(
|
|
467
|
+
"[lat-audit] SendSignal: %s path=cache total=%.2fms action=%s setup_id=%s",
|
|
468
|
+
timer.format_steps(),
|
|
469
|
+
timer.total_ms(),
|
|
452
470
|
action_name,
|
|
471
|
+
setup_id_for_invalidate,
|
|
453
472
|
extra=log_extra,
|
|
454
473
|
)
|
|
455
|
-
return gateway_pb2.ClientSignalResponse(success=
|
|
474
|
+
return gateway_pb2.ClientSignalResponse(success=True, task_id=setup_id_for_invalidate)
|
|
456
475
|
|
|
457
476
|
if GatewayValidator.validate_id(task_id, "task_id") is not None:
|
|
458
477
|
logger.warning(
|
{digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/grpc_servers/module_server.py
RENAMED
|
@@ -9,6 +9,7 @@ from agentic_mesh_protocol.module.v1 import module_service_pb2, module_service_p
|
|
|
9
9
|
|
|
10
10
|
from digitalkin.core.task_manager.module_runner import ModuleRunner
|
|
11
11
|
from digitalkin.core.task_manager.redis import RedisClient
|
|
12
|
+
from digitalkin.core.task_manager.redis.redis_signal import SharedRedisListener
|
|
12
13
|
from digitalkin.grpc_servers._base_server import BaseServer
|
|
13
14
|
from digitalkin.grpc_servers.gateway_servicer import GatewayServicer
|
|
14
15
|
from digitalkin.grpc_servers.module_servicer import ModuleServicer
|
|
@@ -129,11 +130,20 @@ class ModuleServer(BaseServer):
|
|
|
129
130
|
|
|
130
131
|
logger.info("GatewayServicer + ModuleRunner registered (Redis: %s)", redis_url)
|
|
131
132
|
|
|
132
|
-
|
|
133
|
-
|
|
133
|
+
listener = SharedRedisListener.singleton_or_none()
|
|
134
|
+
if listener is not None:
|
|
135
|
+
listener.set_cache_invalidator(self._handle_cache_invalidation)
|
|
136
|
+
|
|
137
|
+
async def _handle_cache_invalidation(self, action: str, setup_id: str = "") -> None:
|
|
138
|
+
"""Dispatch cache invalidation by action name.
|
|
139
|
+
|
|
140
|
+
``INVALIDATE_SETUP`` and ``INVALIDATE_TOOLS`` require a ``setup_id`` —
|
|
141
|
+
without one they log a warning and skip. ``INVALIDATE_ALL`` is the only
|
|
142
|
+
full-wipe path.
|
|
134
143
|
|
|
135
144
|
Args:
|
|
136
|
-
action: SignalAction enum name (e.g.
|
|
145
|
+
action: SignalAction enum name (e.g. ``INVALIDATE_SETUP``).
|
|
146
|
+
setup_id: Setup identifier for scoped invalidation; ignored by full-wipe actions.
|
|
137
147
|
"""
|
|
138
148
|
handlers: dict[str, Any] = {
|
|
139
149
|
"INVALIDATE_ALL": self._invalidate_all,
|
|
@@ -144,24 +154,39 @@ class ModuleServer(BaseServer):
|
|
|
144
154
|
"INVALIDATE_SHARED": self._invalidate_shared,
|
|
145
155
|
}
|
|
146
156
|
handler = handlers.get(action)
|
|
147
|
-
if handler is
|
|
157
|
+
if handler is None:
|
|
158
|
+
logger.warning("Unknown invalidation action: %s", action)
|
|
159
|
+
return
|
|
160
|
+
if action in {"INVALIDATE_SETUP", "INVALIDATE_TOOLS"}:
|
|
161
|
+
await handler(setup_id)
|
|
162
|
+
else:
|
|
148
163
|
await handler()
|
|
149
|
-
|
|
164
|
+
logger.info("Cache invalidated: %s setup_id=%s", action, setup_id or "<all>")
|
|
150
165
|
|
|
151
166
|
async def _invalidate_all(self) -> None:
|
|
152
|
-
|
|
153
|
-
|
|
167
|
+
if self.module_servicer is not None:
|
|
168
|
+
self.module_servicer.invalidate_setup_cache()
|
|
169
|
+
self.module_servicer.invalidate_tool_cache()
|
|
154
170
|
await self._invalidate_shared()
|
|
155
171
|
await self._invalidate_models()
|
|
156
172
|
await self._invalidate_channels()
|
|
157
173
|
|
|
158
|
-
async def _invalidate_setup(self) -> None:
|
|
159
|
-
if self.module_servicer is
|
|
160
|
-
|
|
174
|
+
async def _invalidate_setup(self, setup_id: str = "") -> None:
|
|
175
|
+
if self.module_servicer is None:
|
|
176
|
+
return
|
|
177
|
+
if not setup_id:
|
|
178
|
+
logger.warning("INVALIDATE_SETUP received without setup_id — skipping (scoped-only policy)")
|
|
179
|
+
return
|
|
180
|
+
self.module_servicer._setup_cache.pop(setup_id, None) # noqa: SLF001
|
|
181
|
+
self.module_servicer._setup_inflight.pop(setup_id, None) # noqa: SLF001
|
|
161
182
|
|
|
162
|
-
async def _invalidate_tools(self) -> None:
|
|
163
|
-
if self.module_servicer is
|
|
164
|
-
|
|
183
|
+
async def _invalidate_tools(self, setup_id: str = "") -> None:
|
|
184
|
+
if self.module_servicer is None:
|
|
185
|
+
return
|
|
186
|
+
if not setup_id:
|
|
187
|
+
logger.warning("INVALIDATE_TOOLS received without setup_id — skipping (scoped-only policy)")
|
|
188
|
+
return
|
|
189
|
+
self.module_servicer._tool_cache_by_setup.pop(setup_id, None) # noqa: SLF001
|
|
165
190
|
|
|
166
191
|
async def _invalidate_shared(self) -> None:
|
|
167
192
|
self.module_class.clear_shared()
|
{digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/grpc_servers/module_servicer.py
RENAMED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Module servicer implementation for DigitalKin."""
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
+
import json
|
|
4
5
|
import os
|
|
5
6
|
import time
|
|
6
7
|
from argparse import ArgumentParser, Namespace
|
|
@@ -17,6 +18,7 @@ from google.protobuf import json_format, struct_pb2
|
|
|
17
18
|
|
|
18
19
|
from digitalkin.core.job_manager.base_job_manager import BaseJobManager
|
|
19
20
|
from digitalkin.core.job_manager.single_job_manager import SingleJobManager
|
|
21
|
+
from digitalkin.core.task_manager.redis.redis_signal import SharedRedisListener
|
|
20
22
|
from digitalkin.grpc_servers.exceptions import ServicerError
|
|
21
23
|
from digitalkin.logger import logger
|
|
22
24
|
from digitalkin.models.module.module import ModuleCodeModel
|
|
@@ -387,6 +389,26 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
|
|
|
387
389
|
),
|
|
388
390
|
)
|
|
389
391
|
self._tool_cache_by_setup.pop(setup_version.setup_id, None)
|
|
392
|
+
|
|
393
|
+
publish_ns = time.time_ns()
|
|
394
|
+
for action in ("invalidate_setup", "invalidate_tools"):
|
|
395
|
+
payload = json.dumps({
|
|
396
|
+
"action": action,
|
|
397
|
+
"setup_id": setup_version.setup_id,
|
|
398
|
+
"published_at_ns": publish_ns,
|
|
399
|
+
"origin": SharedRedisListener.PROCESS_ID,
|
|
400
|
+
})
|
|
401
|
+
try:
|
|
402
|
+
await self._redis_client.publish("signal_ch:_global_", payload)
|
|
403
|
+
except Exception:
|
|
404
|
+
logger.warning(
|
|
405
|
+
"[gateway] cache-invalidate fan-out publish failed for action=%s "
|
|
406
|
+
"setup_id=%s — peers may keep stale cache until TTL",
|
|
407
|
+
action,
|
|
408
|
+
setup_version.setup_id,
|
|
409
|
+
exc_info=True,
|
|
410
|
+
)
|
|
411
|
+
|
|
390
412
|
setup_version.content = json_format.ParseDict( # type: ignore[misc]
|
|
391
413
|
updated_setup_data,
|
|
392
414
|
struct_pb2.Struct(),
|
|
@@ -16,6 +16,10 @@ class RedisPoolSettings(BaseSettings):
|
|
|
16
16
|
pool_size_default: int = Field(default=0, description="Non-blocking pool size (0 = pool_size // 2)")
|
|
17
17
|
pool_size_blocking: int = Field(default=0, description="Blocking pool size for XREAD (0 = pool_size // 2)")
|
|
18
18
|
health_check_timeout: float = Field(default=5.0, description="Max seconds to wait for a PING during health check.")
|
|
19
|
+
health_check_interval: int = Field(
|
|
20
|
+
default=15,
|
|
21
|
+
description="Seconds between connection-level PINGs; 0 disables. Catches silently-dead sockets.",
|
|
22
|
+
)
|
|
19
23
|
|
|
20
24
|
def get_default_pool_size(self) -> int:
|
|
21
25
|
"""Non-blocking pool size, defaults to half of total.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/base_server/server_async_insecure.py
RENAMED
|
File without changes
|
|
File without changes
|
{digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/base_server/server_sync_insecure.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/bench_module/triggers/message_trigger.py
RENAMED
|
File without changes
|
|
File without changes
|
{digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/modules/archetype_with_tools_module.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/redis_demo/triggers/message_trigger.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/community/agno/agno_adapter.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/job_manager/base_job_manager.py
RENAMED
|
File without changes
|
|
File without changes
|