digitalkin 0.3.3.dev5__tar.gz → 0.3.3.dev7__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-0.3.3.dev5 → digitalkin-0.3.3.dev7}/PKG-INFO +1 -1
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/pyproject.toml +1 -1
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/__version__.py +1 -1
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/core/job_manager/base_job_manager.py +6 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/core/job_manager/taskiq_broker.py +61 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/core/job_manager/taskiq_job_manager.py +207 -94
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/core/task_manager/task_session.py +2 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/grpc_servers/module_server.py +10 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin.egg-info/PKG-INFO +1 -1
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/LICENSE +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/README.md +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/examples/base_server/__init__.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/examples/base_server/mock/__init__.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/examples/base_server/mock/mock_pb2.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/examples/base_server/mock/mock_pb2_grpc.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/examples/base_server/server_async_insecure.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/examples/base_server/server_async_secure.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/examples/base_server/server_sync_insecure.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/examples/base_server/server_sync_secure.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/examples/modules/__init__.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/examples/modules/archetype_with_tools_module.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/examples/modules/cpu_intensive_module.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/examples/modules/dynamic_setup_module.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/examples/modules/minimal_llm_module.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/examples/modules/text_transform_module.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/examples/monitoring/digitalkin_observability/__init__.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/examples/monitoring/digitalkin_observability/http_server.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/examples/monitoring/digitalkin_observability/interceptors.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/examples/monitoring/digitalkin_observability/metrics.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/examples/monitoring/digitalkin_observability/prometheus.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/examples/monitoring/tests/test_metrics.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/examples/services/filesystem_module.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/examples/services/storage_module.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/setup.cfg +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/__init__.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/core/__init__.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/core/common/__init__.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/core/common/factories.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/core/job_manager/__init__.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/core/job_manager/single_job_manager.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/core/profiling/__init__.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/core/profiling/asyncio_monitor.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/core/profiling/task_profiler.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/core/task_manager/__init__.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/core/task_manager/base_task_manager.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/core/task_manager/local_task_manager.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/core/task_manager/remote_task_manager.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/core/task_manager/task_executor.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/grpc_servers/__init__.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/grpc_servers/_base_server.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/grpc_servers/module_servicer.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/grpc_servers/utils/__init__.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/grpc_servers/utils/exceptions.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/grpc_servers/utils/grpc_client_wrapper.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/grpc_servers/utils/grpc_error_handler.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/grpc_servers/utils/utility_schema_extender.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/logger.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/mixins/__init__.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/mixins/base_mixin.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/mixins/callback_mixin.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/mixins/chat_history_mixin.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/mixins/cost_mixin.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/mixins/file_history_mixin.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/mixins/filesystem_mixin.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/mixins/logger_mixin.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/mixins/storage_mixin.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/models/__init__.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/models/core/__init__.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/models/core/job_manager_models.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/models/core/task_monitor.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/models/grpc_servers/__init__.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/models/grpc_servers/models.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/models/grpc_servers/types.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/models/module/__init__.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/models/module/base_types.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/models/module/module.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/models/module/module_context.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/models/module/module_types.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/models/module/request_metadata.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/models/module/select_schema.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/models/module/setup_types.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/models/module/tool_cache.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/models/module/tool_reference.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/models/module/utility.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/models/services/__init__.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/models/services/cost.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/models/services/registry.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/models/services/storage.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/modules/__init__.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/modules/_base_module.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/modules/archetype_module.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/modules/tool_module.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/modules/trigger_handler.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/modules/triggers/__init__.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/modules/triggers/healthcheck_ping_trigger.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/modules/triggers/healthcheck_services_trigger.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/modules/triggers/healthcheck_status_trigger.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/py.typed +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/__init__.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/agent/__init__.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/agent/agent_strategy.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/agent/default_agent.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/base_strategy.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/communication/__init__.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/communication/communication_strategy.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/communication/default_communication.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/communication/grpc_communication.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/cost/__init__.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/cost/cost_strategy.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/cost/default_cost.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/cost/grpc_cost.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/filesystem/__init__.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/filesystem/default_filesystem.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/filesystem/filesystem_strategy.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/filesystem/grpc_filesystem.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/identity/__init__.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/identity/default_identity.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/identity/identity_strategy.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/registry/__init__.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/registry/default_registry.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/registry/exceptions.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/registry/grpc_registry.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/registry/registry_models.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/registry/registry_strategy.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/services_config.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/services_models.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/setup/__init__.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/setup/default_setup.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/setup/grpc_setup.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/setup/setup_strategy.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/snapshot/__init__.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/snapshot/default_snapshot.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/snapshot/snapshot_strategy.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/storage/__init__.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/storage/default_storage.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/storage/grpc_storage.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/storage/storage_strategy.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/task_manager/__init__.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/task_manager/default_task_manager.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/task_manager/grpc_task_manager.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/task_manager/task_manager_strategy.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/user_profile/__init__.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/user_profile/default_user_profile.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/user_profile/grpc_user_profile.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/services/user_profile/user_profile_strategy.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/utils/__init__.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/utils/arg_parser.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/utils/conditional_schema.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/utils/development_mode_action.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/utils/dynamic_schema.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/utils/llm_ready_schema.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/utils/package_discover.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/utils/proto_utils.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/utils/schema_splitter.py +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin.egg-info/SOURCES.txt +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin.egg-info/dependency_links.txt +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin.egg-info/requires.txt +0 -0
- {digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin.egg-info/top_level.txt +0 -0
{digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/core/job_manager/base_job_manager.py
RENAMED
|
@@ -130,6 +130,12 @@ class BaseJobManager(abc.ABC, Generic[InputModelT, OutputModelT, SetupModelT]):
|
|
|
130
130
|
required for the job manager to function.
|
|
131
131
|
"""
|
|
132
132
|
|
|
133
|
+
async def stop(self) -> None:
|
|
134
|
+
"""Stop the job manager and clean up resources.
|
|
135
|
+
|
|
136
|
+
Default no-op. Subclasses with external connections override.
|
|
137
|
+
"""
|
|
138
|
+
|
|
133
139
|
@staticmethod
|
|
134
140
|
async def job_specific_callback(
|
|
135
141
|
callback: Callable[[str, DataModel | ModuleCodeModel], Coroutine[Any, Any, None]],
|
{digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/core/job_manager/taskiq_broker.py
RENAMED
|
@@ -11,8 +11,10 @@ from rstream import Producer
|
|
|
11
11
|
from rstream.exceptions import PreconditionFailed
|
|
12
12
|
from taskiq import Context, TaskiqDepends, TaskiqMessage
|
|
13
13
|
from taskiq.abc.formatter import TaskiqFormatter
|
|
14
|
+
from taskiq.abc.middleware import TaskiqMiddleware
|
|
14
15
|
from taskiq.compat import model_validate
|
|
15
16
|
from taskiq.message import BrokerMessage
|
|
17
|
+
from taskiq.result import TaskiqResult
|
|
16
18
|
from taskiq_aio_pika import AioPikaBroker
|
|
17
19
|
|
|
18
20
|
from digitalkin.core.common import ModuleFactory
|
|
@@ -175,6 +177,11 @@ class TaskiqBrokerConfig:
|
|
|
175
177
|
startup=[TaskiqBrokerConfig.init_rstream],
|
|
176
178
|
)
|
|
177
179
|
broker.formatter = PickleFormatter()
|
|
180
|
+
redis_url = os.environ.get("DIGITALKIN_TASKIQ_RESULT_BACKEND_URL")
|
|
181
|
+
if redis_url:
|
|
182
|
+
from taskiq_redis import RedisAsyncResultBackend
|
|
183
|
+
|
|
184
|
+
broker.with_result_backend(RedisAsyncResultBackend(redis_url))
|
|
178
185
|
return broker
|
|
179
186
|
|
|
180
187
|
@staticmethod
|
|
@@ -224,9 +231,54 @@ class TaskiqBrokerConfig:
|
|
|
224
231
|
await RSTREAM_PRODUCER.send(stream=TaskiqBrokerConfig.STREAM, message=body)
|
|
225
232
|
|
|
226
233
|
|
|
234
|
+
class TaskiqLifecycleMiddleware(TaskiqMiddleware):
|
|
235
|
+
"""Lifecycle middleware for structured logging and safety-net EndOfStreamOutput."""
|
|
236
|
+
|
|
237
|
+
async def pre_execute(self, message: TaskiqMessage) -> TaskiqMessage: # noqa: PLR6301
|
|
238
|
+
"""Log task start.
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
The unmodified message.
|
|
242
|
+
"""
|
|
243
|
+
logger.info("Taskiq task starting: %s (task_name=%s)", message.task_id, message.task_name)
|
|
244
|
+
return message
|
|
245
|
+
|
|
246
|
+
async def post_execute(self, message: TaskiqMessage, result: TaskiqResult) -> None: # noqa: PLR6301
|
|
247
|
+
"""Log task completion."""
|
|
248
|
+
log_fn = logger.info if not result.is_err else logger.error
|
|
249
|
+
log_fn(
|
|
250
|
+
"Taskiq task finished: %s (task_name=%s, is_err=%s, exec_time=%.3fs)",
|
|
251
|
+
message.task_id,
|
|
252
|
+
message.task_name,
|
|
253
|
+
result.is_err,
|
|
254
|
+
result.execution_time,
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
async def on_error( # noqa: PLR6301
|
|
258
|
+
self,
|
|
259
|
+
message: TaskiqMessage,
|
|
260
|
+
result: TaskiqResult, # noqa: ARG002
|
|
261
|
+
exception: BaseException,
|
|
262
|
+
) -> None:
|
|
263
|
+
"""Safety net: send EndOfStreamOutput if worker task failed to."""
|
|
264
|
+
logger.error("Taskiq task error: %s (task_name=%s, error=%s)", message.task_id, message.task_name, exception)
|
|
265
|
+
try:
|
|
266
|
+
await TaskiqBrokerConfig.send_message_to_stream(
|
|
267
|
+
message.task_id,
|
|
268
|
+
ModuleCodeModel(code="WorkerCrash", short_description="Middleware safety net", message=str(exception)),
|
|
269
|
+
)
|
|
270
|
+
await TaskiqBrokerConfig.send_message_to_stream(
|
|
271
|
+
message.task_id,
|
|
272
|
+
DataModel(root=EndOfStreamOutput()),
|
|
273
|
+
)
|
|
274
|
+
except Exception:
|
|
275
|
+
logger.exception("Middleware safety net failed for %s", message.task_id)
|
|
276
|
+
|
|
277
|
+
|
|
227
278
|
# Module-level globals required by Taskiq framework (decorator needs broker at import time)
|
|
228
279
|
RSTREAM_PRODUCER = TaskiqBrokerConfig.define_producer()
|
|
229
280
|
TASKIQ_BROKER = TaskiqBrokerConfig.define_broker()
|
|
281
|
+
TASKIQ_BROKER.add_middlewares(TaskiqLifecycleMiddleware())
|
|
230
282
|
|
|
231
283
|
|
|
232
284
|
@TASKIQ_BROKER.task(task_name="__discarded__")
|
|
@@ -367,6 +419,7 @@ async def run_config_module(
|
|
|
367
419
|
services_mode: ServicesMode,
|
|
368
420
|
config_setup_data: dict,
|
|
369
421
|
request_metadata: dict[str, str] | None = None,
|
|
422
|
+
registry_config: dict[str, Any] | None = None,
|
|
370
423
|
context: Context = TaskiqDepends(),
|
|
371
424
|
) -> None:
|
|
372
425
|
"""TaskIQ task allowing a module to compute in the background asynchronously.
|
|
@@ -379,9 +432,17 @@ async def run_config_module(
|
|
|
379
432
|
services_mode: ServicesMode,
|
|
380
433
|
config_setup_data: dict,
|
|
381
434
|
request_metadata: gRPC request metadata (headers) to forward to the module.
|
|
435
|
+
registry_config: Registry config (client_config) forwarded from the main process.
|
|
382
436
|
context: Allow TaskIQ context access
|
|
383
437
|
"""
|
|
384
438
|
logger.info("Starting config module with services_mode: %s", services_mode)
|
|
439
|
+
|
|
440
|
+
# Restore registry config lost during pickle (worker re-imports class without runtime mutations)
|
|
441
|
+
if registry_config is not None:
|
|
442
|
+
if "services_config_params" not in module_class.__dict__:
|
|
443
|
+
module_class.services_config_params = dict(module_class.services_config_params)
|
|
444
|
+
module_class.services_config_params["registry"] = registry_config
|
|
445
|
+
|
|
385
446
|
services_config = ServicesConfig(
|
|
386
447
|
services_config_strategies=module_class.services_config_strategies,
|
|
387
448
|
services_config_params=module_class.services_config_params,
|
|
@@ -9,6 +9,7 @@ except ImportError:
|
|
|
9
9
|
|
|
10
10
|
import asyncio
|
|
11
11
|
import contextlib
|
|
12
|
+
import datetime
|
|
12
13
|
import json
|
|
13
14
|
import os
|
|
14
15
|
from collections.abc import AsyncGenerator, AsyncIterator
|
|
@@ -37,6 +38,9 @@ class TaskiqJobManager(BaseJobManager[InputModelT, OutputModelT, SetupModelT]):
|
|
|
37
38
|
"""Taskiq job manager for running modules in Taskiq tasks."""
|
|
38
39
|
|
|
39
40
|
services_mode: ServicesMode
|
|
41
|
+
stream_consumer: Consumer
|
|
42
|
+
stream_consumer_task: asyncio.Task[None]
|
|
43
|
+
_reaper_task: asyncio.Task[None]
|
|
40
44
|
|
|
41
45
|
@staticmethod
|
|
42
46
|
async def _on_consumer_closed(reason: Any) -> None:
|
|
@@ -86,8 +90,95 @@ class TaskiqJobManager(BaseJobManager[InputModelT, OutputModelT, SetupModelT]):
|
|
|
86
90
|
job_id = data.get("job_id")
|
|
87
91
|
if not job_id:
|
|
88
92
|
return
|
|
93
|
+
output_data = data.get("output_data")
|
|
89
94
|
if queue := self.job_queues.get(job_id):
|
|
90
|
-
await queue.put(
|
|
95
|
+
await queue.put(output_data)
|
|
96
|
+
|
|
97
|
+
# Bridge session status from RStream terminal markers
|
|
98
|
+
session = self.tasks_sessions.get(job_id)
|
|
99
|
+
if session is None or not isinstance(output_data, dict):
|
|
100
|
+
return
|
|
101
|
+
if "code" in output_data:
|
|
102
|
+
if session.status not in {"cancelled", "failed"}:
|
|
103
|
+
session.status = "failed"
|
|
104
|
+
logger.info("Job %s marked failed from RStream error (code=%s)", job_id, output_data.get("code"))
|
|
105
|
+
elif isinstance(output_data.get("root"), dict) and output_data["root"].get("protocol") == "end_of_stream":
|
|
106
|
+
if session.status not in {"cancelled", "failed"}:
|
|
107
|
+
session.status = "completed"
|
|
108
|
+
logger.info("Job %s marked completed from RStream end_of_stream", job_id)
|
|
109
|
+
session.close_stream()
|
|
110
|
+
|
|
111
|
+
async def _run_consumer_with_restart(self) -> None:
|
|
112
|
+
"""Run the RStream consumer with automatic restart on failure.
|
|
113
|
+
|
|
114
|
+
Raises:
|
|
115
|
+
CancelledError: If the task is cancelled.
|
|
116
|
+
"""
|
|
117
|
+
max_retries = int(os.environ.get("DIGITALKIN_RSTREAM_MAX_RETRIES", "10"))
|
|
118
|
+
base_delay = 1.0
|
|
119
|
+
max_delay = 60.0
|
|
120
|
+
attempt = 0
|
|
121
|
+
|
|
122
|
+
while True:
|
|
123
|
+
try:
|
|
124
|
+
await self.stream_consumer.run()
|
|
125
|
+
break # Normal exit (consumer closed gracefully)
|
|
126
|
+
except asyncio.CancelledError:
|
|
127
|
+
raise
|
|
128
|
+
except Exception:
|
|
129
|
+
attempt += 1
|
|
130
|
+
if attempt > max_retries:
|
|
131
|
+
logger.exception("Stream consumer failed after %d retries, giving up", max_retries)
|
|
132
|
+
for session in list(self.tasks_sessions.values()):
|
|
133
|
+
if session.status == "pending":
|
|
134
|
+
session.status = "failed"
|
|
135
|
+
session.close_stream()
|
|
136
|
+
break
|
|
137
|
+
delay = min(base_delay * (2 ** (attempt - 1)), max_delay)
|
|
138
|
+
logger.exception(
|
|
139
|
+
"Stream consumer failed (attempt %d/%d), restarting in %.1fs", attempt, max_retries, delay
|
|
140
|
+
)
|
|
141
|
+
await asyncio.sleep(delay)
|
|
142
|
+
# Reconnect
|
|
143
|
+
self.stream_consumer = self._define_consumer()
|
|
144
|
+
await self.stream_consumer.create_stream(
|
|
145
|
+
TaskiqBrokerConfig.STREAM,
|
|
146
|
+
exists_ok=True,
|
|
147
|
+
arguments={"max-length-bytes": TaskiqBrokerConfig.STREAM_RETENTION},
|
|
148
|
+
)
|
|
149
|
+
await self.stream_consumer.start()
|
|
150
|
+
await self.stream_consumer.subscribe(
|
|
151
|
+
stream=TaskiqBrokerConfig.STREAM,
|
|
152
|
+
subscriber_name=f"""subscriber_{os.environ.get("SERVER_NAME", "module_servicer")}""",
|
|
153
|
+
callback=self._on_message, # type: ignore[arg-type]
|
|
154
|
+
offset_specification=ConsumerOffsetSpecification(OffsetType.LAST),
|
|
155
|
+
initial_credit=int(os.environ.get("DIGITALKIN_RSTREAM_INITIAL_CREDIT", "50")),
|
|
156
|
+
)
|
|
157
|
+
logger.info("Stream consumer reconnected (attempt %d)", attempt)
|
|
158
|
+
|
|
159
|
+
async def _reap_orphan_sessions(self) -> None:
|
|
160
|
+
"""Mark sessions stuck in pending beyond timeout as failed.
|
|
161
|
+
|
|
162
|
+
Handles hard worker crashes where no EndOfStreamOutput arrives.
|
|
163
|
+
"""
|
|
164
|
+
orphan_timeout = float(os.environ.get("DIGITALKIN_ORPHAN_SESSION_TIMEOUT", "600.0"))
|
|
165
|
+
check_interval = float(os.environ.get("DIGITALKIN_ORPHAN_CHECK_INTERVAL", "60.0"))
|
|
166
|
+
|
|
167
|
+
while True:
|
|
168
|
+
try:
|
|
169
|
+
await asyncio.sleep(check_interval)
|
|
170
|
+
except asyncio.CancelledError:
|
|
171
|
+
return
|
|
172
|
+
now = datetime.datetime.now(datetime.timezone.utc)
|
|
173
|
+
for task_id, session in list(self.tasks_sessions.items()):
|
|
174
|
+
if session.status != "pending":
|
|
175
|
+
continue
|
|
176
|
+
elapsed = (now - session.created_at).total_seconds()
|
|
177
|
+
if elapsed > orphan_timeout:
|
|
178
|
+
logger.warning("Orphan session: %s (pending %.0fs)", task_id, elapsed)
|
|
179
|
+
session.status = "failed"
|
|
180
|
+
session.close_stream()
|
|
181
|
+
await self._task_manager._cleanup_task(task_id, session.mission_id) # noqa: SLF001
|
|
91
182
|
|
|
92
183
|
async def start(self) -> None:
|
|
93
184
|
"""Start the TaskiqJobManager (no-op for external connections)."""
|
|
@@ -116,37 +207,41 @@ class TaskiqJobManager(BaseJobManager[InputModelT, OutputModelT, SetupModelT]):
|
|
|
116
207
|
initial_credit=int(os.environ.get("DIGITALKIN_RSTREAM_INITIAL_CREDIT", "50")),
|
|
117
208
|
)
|
|
118
209
|
|
|
119
|
-
# Wrap the consumer task with error handling
|
|
120
|
-
async def run_consumer_with_error_handling() -> None:
|
|
121
|
-
try:
|
|
122
|
-
await self.stream_consumer.run()
|
|
123
|
-
except asyncio.CancelledError:
|
|
124
|
-
logger.debug("Stream consumer task cancelled")
|
|
125
|
-
raise
|
|
126
|
-
except Exception:
|
|
127
|
-
logger.exception("Stream consumer task failed")
|
|
128
|
-
raise
|
|
129
|
-
|
|
130
210
|
self.stream_consumer_task = asyncio.create_task(
|
|
131
|
-
|
|
211
|
+
self._run_consumer_with_restart(),
|
|
132
212
|
name="stream_consumer_task",
|
|
133
213
|
)
|
|
134
214
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
215
|
+
self._reaper_task = asyncio.create_task(self._reap_orphan_sessions(), name="orphan_session_reaper")
|
|
216
|
+
|
|
217
|
+
async def stop(self) -> None:
|
|
218
|
+
"""Stop the TaskiqJobManager, cancel workers, and clean up all resources."""
|
|
219
|
+
# 1. Cancel reaper
|
|
220
|
+
self._reaper_task.cancel()
|
|
221
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
222
|
+
await self._reaper_task
|
|
223
|
+
|
|
224
|
+
# 2. Cancel all running modules (sends cancel signals to workers)
|
|
225
|
+
await self.stop_all_modules()
|
|
226
|
+
|
|
227
|
+
# 3. Clean remaining sessions (releases semaphore slots)
|
|
228
|
+
for task_id in list(self.tasks_sessions.keys()):
|
|
229
|
+
session = self.tasks_sessions.get(task_id)
|
|
230
|
+
if session is not None:
|
|
231
|
+
await self._task_manager._cleanup_task(task_id, session.mission_id) # noqa: SLF001
|
|
232
|
+
|
|
233
|
+
# 4. Close RStream consumer
|
|
138
234
|
await self.stream_consumer.close()
|
|
139
|
-
# Cancel the background task
|
|
140
235
|
self.stream_consumer_task.cancel()
|
|
141
236
|
with contextlib.suppress(asyncio.CancelledError):
|
|
142
237
|
await self.stream_consumer_task
|
|
143
238
|
|
|
144
|
-
#
|
|
239
|
+
# 5. Clear job queues
|
|
145
240
|
queue_count = len(self.job_queues)
|
|
146
241
|
self.job_queues.clear()
|
|
147
|
-
logger.info("TaskiqJobManager:
|
|
242
|
+
logger.info("TaskiqJobManager stopped: cleared %d queues", queue_count)
|
|
148
243
|
|
|
149
|
-
#
|
|
244
|
+
# 6. Close producer and broker
|
|
150
245
|
await TaskiqBrokerConfig.cleanup_global_resources()
|
|
151
246
|
|
|
152
247
|
def __init__(
|
|
@@ -192,11 +287,11 @@ class TaskiqJobManager(BaseJobManager[InputModelT, OutputModelT, SetupModelT]):
|
|
|
192
287
|
Raises:
|
|
193
288
|
asyncio.TimeoutError: If waiting for the setup response times out.
|
|
194
289
|
"""
|
|
195
|
-
|
|
196
|
-
|
|
290
|
+
if job_id not in self.job_queues:
|
|
291
|
+
self.job_queues[job_id] = QueueFactory.create_bounded_queue(maxsize=self.max_queue_size)
|
|
292
|
+
queue = self.job_queues[job_id]
|
|
197
293
|
|
|
198
294
|
try:
|
|
199
|
-
# Add timeout to prevent indefinite blocking
|
|
200
295
|
item = await asyncio.wait_for(queue.get(), timeout=self._config_setup_timeout)
|
|
201
296
|
except asyncio.TimeoutError:
|
|
202
297
|
logger.error(
|
|
@@ -207,8 +302,9 @@ class TaskiqJobManager(BaseJobManager[InputModelT, OutputModelT, SetupModelT]):
|
|
|
207
302
|
queue.task_done()
|
|
208
303
|
return item
|
|
209
304
|
finally:
|
|
210
|
-
logger.info("generate_config_setup_module_response: job_id=%s: %s", job_id, self.job_queues[job_id].empty())
|
|
211
305
|
self.job_queues.pop(job_id, None)
|
|
306
|
+
if (session := self.tasks_sessions.get(job_id)) is not None:
|
|
307
|
+
await self._task_manager._cleanup_task(job_id, session.mission_id) # noqa: SLF001
|
|
212
308
|
|
|
213
309
|
async def create_config_setup_instance_job(
|
|
214
310
|
self,
|
|
@@ -245,6 +341,8 @@ class TaskiqJobManager(BaseJobManager[InputModelT, OutputModelT, SetupModelT]):
|
|
|
245
341
|
raise TypeError(msg)
|
|
246
342
|
|
|
247
343
|
# Submit task to Taskiq
|
|
344
|
+
registry_config = self.module_class.services_config_params.get("registry")
|
|
345
|
+
|
|
248
346
|
running_task: AsyncTaskiqTask[Any] = await task.kiq(
|
|
249
347
|
mission_id,
|
|
250
348
|
setup_id,
|
|
@@ -253,37 +351,46 @@ class TaskiqJobManager(BaseJobManager[InputModelT, OutputModelT, SetupModelT]):
|
|
|
253
351
|
self.services_mode,
|
|
254
352
|
config_setup_data.model_dump(mode="json"), # SetupModelT generic bound to BaseModel # type: ignore
|
|
255
353
|
request_metadata,
|
|
354
|
+
registry_config,
|
|
256
355
|
)
|
|
257
356
|
|
|
258
357
|
job_id = running_task.task_id
|
|
259
358
|
|
|
260
|
-
#
|
|
261
|
-
|
|
262
|
-
job_id,
|
|
263
|
-
mission_id=mission_id,
|
|
264
|
-
setup_id=setup_id,
|
|
265
|
-
setup_version_id=setup_version_id,
|
|
266
|
-
request_metadata=request_metadata,
|
|
267
|
-
)
|
|
359
|
+
# Pre-create queue to avoid message drop race
|
|
360
|
+
self.job_queues[job_id] = QueueFactory.create_bounded_queue(maxsize=self.max_queue_size)
|
|
268
361
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
362
|
+
try:
|
|
363
|
+
# Create module instance for metadata
|
|
364
|
+
module = self.module_class(
|
|
365
|
+
job_id,
|
|
366
|
+
mission_id=mission_id,
|
|
367
|
+
setup_id=setup_id,
|
|
368
|
+
setup_version_id=setup_version_id,
|
|
369
|
+
request_metadata=request_metadata,
|
|
370
|
+
)
|
|
272
371
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
372
|
+
# Register task in TaskManager (remote mode)
|
|
373
|
+
async def _dummy_coro() -> None:
|
|
374
|
+
"""Dummy coroutine - actual execution happens in worker."""
|
|
375
|
+
|
|
376
|
+
await self.create_task(
|
|
377
|
+
job_id,
|
|
378
|
+
mission_id,
|
|
379
|
+
module,
|
|
380
|
+
_dummy_coro(),
|
|
381
|
+
)
|
|
382
|
+
except Exception:
|
|
383
|
+
self.job_queues.pop(job_id, None)
|
|
384
|
+
raise
|
|
279
385
|
|
|
280
|
-
logger.info("Registered config task: %s
|
|
281
|
-
|
|
282
|
-
|
|
386
|
+
logger.info("Registered config task: %s", job_id)
|
|
387
|
+
if os.environ.get("DIGITALKIN_TASKIQ_RESULT_BACKEND_URL"):
|
|
388
|
+
result = await running_task.wait_result(timeout=10)
|
|
389
|
+
logger.debug("Job %s config result: %s", job_id, result)
|
|
283
390
|
return job_id
|
|
284
391
|
|
|
285
392
|
@asynccontextmanager
|
|
286
|
-
async def generate_stream_consumer(self, job_id: str) -> AsyncIterator[AsyncGenerator[dict[str, Any], None]]:
|
|
393
|
+
async def generate_stream_consumer(self, job_id: str) -> AsyncIterator[AsyncGenerator[dict[str, Any], None]]: # noqa: C901, PLR0915
|
|
287
394
|
"""Generate a stream consumer for the RStream stream.
|
|
288
395
|
|
|
289
396
|
Args:
|
|
@@ -292,10 +399,11 @@ class TaskiqJobManager(BaseJobManager[InputModelT, OutputModelT, SetupModelT]):
|
|
|
292
399
|
Yields:
|
|
293
400
|
messages: The stream messages from the associated module.
|
|
294
401
|
"""
|
|
295
|
-
|
|
296
|
-
|
|
402
|
+
if job_id not in self.job_queues:
|
|
403
|
+
self.job_queues[job_id] = QueueFactory.create_bounded_queue(maxsize=self.max_queue_size)
|
|
404
|
+
queue = self.job_queues[job_id]
|
|
297
405
|
|
|
298
|
-
async def _stream() -> AsyncGenerator[dict[str, Any], Any]:
|
|
406
|
+
async def _stream() -> AsyncGenerator[dict[str, Any], Any]: # noqa: C901
|
|
299
407
|
"""Generate the stream with batch-drain optimization.
|
|
300
408
|
|
|
301
409
|
Yields:
|
|
@@ -349,16 +457,22 @@ class TaskiqJobManager(BaseJobManager[InputModelT, OutputModelT, SetupModelT]):
|
|
|
349
457
|
logger.info("Job %s no longer registered, ending stream", job_id)
|
|
350
458
|
break
|
|
351
459
|
|
|
352
|
-
|
|
460
|
+
session = self.tasks_sessions[job_id]
|
|
461
|
+
if session.stream_closed:
|
|
462
|
+
logger.info("Job %s stream closed, draining queue and ending stream", job_id)
|
|
463
|
+
while not queue.empty():
|
|
464
|
+
item = queue.get_nowait()
|
|
465
|
+
queue.task_done()
|
|
466
|
+
yield item
|
|
467
|
+
break
|
|
353
468
|
|
|
354
|
-
|
|
469
|
+
status = await self.get_module_status(job_id)
|
|
470
|
+
if status in {"cancelled", "failed", "completed"}:
|
|
355
471
|
logger.info("Job %s has terminal status %s, draining queue and ending stream", job_id, status)
|
|
356
|
-
|
|
357
472
|
while not queue.empty():
|
|
358
473
|
item = queue.get_nowait()
|
|
359
474
|
queue.task_done()
|
|
360
475
|
yield item
|
|
361
|
-
|
|
362
476
|
break
|
|
363
477
|
|
|
364
478
|
try:
|
|
@@ -414,29 +528,37 @@ class TaskiqJobManager(BaseJobManager[InputModelT, OutputModelT, SetupModelT]):
|
|
|
414
528
|
)
|
|
415
529
|
job_id = running_task.task_id
|
|
416
530
|
|
|
417
|
-
#
|
|
418
|
-
|
|
419
|
-
job_id,
|
|
420
|
-
mission_id=mission_id,
|
|
421
|
-
setup_id=setup_id,
|
|
422
|
-
setup_version_id=setup_version_id,
|
|
423
|
-
request_metadata=request_metadata,
|
|
424
|
-
)
|
|
531
|
+
# Pre-create queue to avoid message drop race
|
|
532
|
+
self.job_queues[job_id] = QueueFactory.create_bounded_queue(maxsize=self.max_queue_size)
|
|
425
533
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
534
|
+
try:
|
|
535
|
+
# Create module instance for metadata
|
|
536
|
+
module = self.module_class(
|
|
537
|
+
job_id,
|
|
538
|
+
mission_id=mission_id,
|
|
539
|
+
setup_id=setup_id,
|
|
540
|
+
setup_version_id=setup_version_id,
|
|
541
|
+
request_metadata=request_metadata,
|
|
542
|
+
)
|
|
429
543
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
544
|
+
# Register task in TaskManager (remote mode)
|
|
545
|
+
async def _dummy_coro() -> None:
|
|
546
|
+
"""Dummy coroutine - actual execution happens in worker."""
|
|
547
|
+
|
|
548
|
+
await self.create_task(
|
|
549
|
+
job_id,
|
|
550
|
+
mission_id,
|
|
551
|
+
module,
|
|
552
|
+
_dummy_coro(),
|
|
553
|
+
)
|
|
554
|
+
except Exception:
|
|
555
|
+
self.job_queues.pop(job_id, None)
|
|
556
|
+
raise
|
|
436
557
|
|
|
437
|
-
logger.info("Registered remote task: %s
|
|
438
|
-
|
|
439
|
-
|
|
558
|
+
logger.info("Registered remote task: %s", job_id)
|
|
559
|
+
if os.environ.get("DIGITALKIN_TASKIQ_RESULT_BACKEND_URL"):
|
|
560
|
+
result = await running_task.wait_result(timeout=10)
|
|
561
|
+
logger.debug("Job %s result: %s", job_id, result)
|
|
440
562
|
return job_id
|
|
441
563
|
|
|
442
564
|
async def get_module_status(self, job_id: str) -> str:
|
|
@@ -455,37 +577,28 @@ class TaskiqJobManager(BaseJobManager[InputModelT, OutputModelT, SetupModelT]):
|
|
|
455
577
|
return session.status
|
|
456
578
|
|
|
457
579
|
async def wait_for_completion(self, job_id: str, max_wait: float = 600.0) -> None:
|
|
458
|
-
"""Wait for a task to complete
|
|
580
|
+
"""Wait for a task to complete via stream-closed event.
|
|
459
581
|
|
|
460
|
-
|
|
461
|
-
|
|
582
|
+
Relies on ``_on_message`` setting ``_stream_closed`` when
|
|
583
|
+
``end_of_stream`` arrives from RStream. Falls back to ``max_wait``
|
|
584
|
+
timeout for crash scenarios.
|
|
462
585
|
|
|
463
586
|
Args:
|
|
464
587
|
job_id: The unique identifier of the job to wait for.
|
|
465
588
|
max_wait: Maximum time in seconds to wait before giving up.
|
|
466
589
|
|
|
467
590
|
Raises:
|
|
468
|
-
KeyError: If the job_id is not found in tasks_sessions.
|
|
469
591
|
asyncio.TimeoutError: If max_wait is exceeded.
|
|
470
592
|
"""
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
if session is None or session.status in terminal_states:
|
|
481
|
-
logger.debug("Job %s reached terminal state: %s", job_id, session.status if session else "removed")
|
|
482
|
-
break
|
|
483
|
-
if elapsed >= max_wait:
|
|
484
|
-
logger.error("Job %s: max wait time (%.1fs) exceeded, giving up", job_id, max_wait)
|
|
485
|
-
raise asyncio.TimeoutError
|
|
486
|
-
await asyncio.sleep(poll_interval)
|
|
487
|
-
elapsed += poll_interval
|
|
488
|
-
poll_interval = min(poll_interval * 2, 0.5)
|
|
593
|
+
session = self.tasks_sessions.get(job_id)
|
|
594
|
+
if session is None:
|
|
595
|
+
return
|
|
596
|
+
try:
|
|
597
|
+
await asyncio.wait_for(session._stream_closed.wait(), timeout=max_wait) # noqa: SLF001
|
|
598
|
+
except asyncio.TimeoutError:
|
|
599
|
+
logger.error("Job %s: max wait time (%.1fs) exceeded", job_id, max_wait)
|
|
600
|
+
raise
|
|
601
|
+
logger.debug("Job %s: stream closed, completion detected (status=%s)", job_id, session.status)
|
|
489
602
|
|
|
490
603
|
async def stop_module(self, job_id: str) -> bool:
|
|
491
604
|
"""Stop a running module using TaskManager.
|
{digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/core/task_manager/task_session.py
RENAMED
|
@@ -31,6 +31,7 @@ class TaskSession:
|
|
|
31
31
|
task_id: str
|
|
32
32
|
mission_id: str
|
|
33
33
|
|
|
34
|
+
created_at: datetime.datetime
|
|
34
35
|
started_at: datetime.datetime | None
|
|
35
36
|
completed_at: datetime.datetime | None
|
|
36
37
|
|
|
@@ -73,6 +74,7 @@ class TaskSession:
|
|
|
73
74
|
self.task_id = task_id
|
|
74
75
|
self.mission_id = mission_id
|
|
75
76
|
|
|
77
|
+
self.created_at = datetime.datetime.now(datetime.timezone.utc)
|
|
76
78
|
self.started_at = None
|
|
77
79
|
self.completed_at = None
|
|
78
80
|
|
{digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/src/digitalkin/grpc_servers/module_server.py
RENAMED
|
@@ -155,6 +155,16 @@ class ModuleServer(BaseServer):
|
|
|
155
155
|
except Exception:
|
|
156
156
|
logger.exception("Failed to shutdown module servicer resources")
|
|
157
157
|
|
|
158
|
+
try:
|
|
159
|
+
await self.module_servicer.job_manager.stop_all_modules()
|
|
160
|
+
except Exception:
|
|
161
|
+
logger.exception("Failed to stop all modules during shutdown")
|
|
162
|
+
|
|
163
|
+
try:
|
|
164
|
+
await self.module_servicer.job_manager.stop()
|
|
165
|
+
except Exception:
|
|
166
|
+
logger.exception("Failed to stop job manager during shutdown")
|
|
167
|
+
|
|
158
168
|
# Close server-level registry channel
|
|
159
169
|
if isinstance(self.registry, GrpcRegistry):
|
|
160
170
|
try:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/examples/base_server/server_async_insecure.py
RENAMED
|
File without changes
|
|
File without changes
|
{digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/examples/base_server/server_sync_insecure.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{digitalkin-0.3.3.dev5 → digitalkin-0.3.3.dev7}/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
|