jettask 0.2.19__py3-none-any.whl → 0.2.20__py3-none-any.whl
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.
- jettask/__init__.py +10 -3
- jettask/cli.py +314 -228
- jettask/config/__init__.py +9 -1
- jettask/config/config.py +245 -0
- jettask/config/env_loader.py +381 -0
- jettask/config/lua_scripts.py +158 -0
- jettask/config/nacos_config.py +132 -5
- jettask/core/__init__.py +1 -1
- jettask/core/app.py +1573 -666
- jettask/core/app_importer.py +33 -16
- jettask/core/container.py +532 -0
- jettask/core/task.py +1 -4
- jettask/core/unified_manager_base.py +2 -2
- jettask/executor/__init__.py +38 -0
- jettask/executor/core.py +625 -0
- jettask/executor/executor.py +338 -0
- jettask/executor/orchestrator.py +290 -0
- jettask/executor/process_entry.py +638 -0
- jettask/executor/task_executor.py +317 -0
- jettask/messaging/__init__.py +68 -0
- jettask/messaging/event_pool.py +2188 -0
- jettask/messaging/reader.py +519 -0
- jettask/messaging/registry.py +266 -0
- jettask/messaging/scanner.py +369 -0
- jettask/messaging/sender.py +312 -0
- jettask/persistence/__init__.py +118 -0
- jettask/persistence/backlog_monitor.py +567 -0
- jettask/{backend/data_access.py → persistence/base.py} +58 -57
- jettask/persistence/consumer.py +315 -0
- jettask/{core → persistence}/db_manager.py +23 -22
- jettask/persistence/maintenance.py +81 -0
- jettask/persistence/message_consumer.py +259 -0
- jettask/{backend/namespace_data_access.py → persistence/namespace.py} +66 -98
- jettask/persistence/offline_recovery.py +196 -0
- jettask/persistence/queue_discovery.py +215 -0
- jettask/persistence/task_persistence.py +218 -0
- jettask/persistence/task_updater.py +583 -0
- jettask/scheduler/__init__.py +2 -2
- jettask/scheduler/loader.py +6 -5
- jettask/scheduler/run_scheduler.py +1 -1
- jettask/scheduler/scheduler.py +7 -7
- jettask/scheduler/{unified_scheduler_manager.py → scheduler_coordinator.py} +18 -13
- jettask/task/__init__.py +16 -0
- jettask/{router.py → task/router.py} +26 -8
- jettask/task/task_center/__init__.py +9 -0
- jettask/task/task_executor.py +318 -0
- jettask/task/task_registry.py +291 -0
- jettask/test_connection_monitor.py +73 -0
- jettask/utils/__init__.py +31 -1
- jettask/{monitor/run_backlog_collector.py → utils/backlog_collector.py} +1 -1
- jettask/utils/db_connector.py +1629 -0
- jettask/{db_init.py → utils/db_init.py} +1 -1
- jettask/utils/rate_limit/__init__.py +30 -0
- jettask/utils/rate_limit/concurrency_limiter.py +665 -0
- jettask/utils/rate_limit/config.py +145 -0
- jettask/utils/rate_limit/limiter.py +41 -0
- jettask/utils/rate_limit/manager.py +269 -0
- jettask/utils/rate_limit/qps_limiter.py +154 -0
- jettask/utils/rate_limit/task_limiter.py +384 -0
- jettask/utils/serializer.py +3 -0
- jettask/{monitor/stream_backlog_monitor.py → utils/stream_backlog.py} +14 -6
- jettask/utils/time_sync.py +173 -0
- jettask/webui/__init__.py +27 -0
- jettask/{api/v1 → webui/api}/alerts.py +1 -1
- jettask/{api/v1 → webui/api}/analytics.py +2 -2
- jettask/{api/v1 → webui/api}/namespaces.py +1 -1
- jettask/{api/v1 → webui/api}/overview.py +1 -1
- jettask/{api/v1 → webui/api}/queues.py +3 -3
- jettask/{api/v1 → webui/api}/scheduled.py +1 -1
- jettask/{api/v1 → webui/api}/settings.py +1 -1
- jettask/{api.py → webui/app.py} +253 -145
- jettask/webui/namespace_manager/__init__.py +10 -0
- jettask/{multi_namespace_consumer.py → webui/namespace_manager/multi.py} +69 -22
- jettask/{unified_consumer_manager.py → webui/namespace_manager/unified.py} +1 -1
- jettask/{run.py → webui/run.py} +2 -2
- jettask/{services → webui/services}/__init__.py +1 -3
- jettask/{services → webui/services}/overview_service.py +34 -16
- jettask/{services → webui/services}/queue_service.py +1 -1
- jettask/{backend → webui/services}/queue_stats_v2.py +1 -1
- jettask/{services → webui/services}/settings_service.py +1 -1
- jettask/worker/__init__.py +53 -0
- jettask/worker/lifecycle.py +1507 -0
- jettask/worker/manager.py +583 -0
- jettask/{core/offline_worker_recovery.py → worker/recovery.py} +268 -175
- {jettask-0.2.19.dist-info → jettask-0.2.20.dist-info}/METADATA +2 -71
- jettask-0.2.20.dist-info/RECORD +145 -0
- jettask/__main__.py +0 -140
- jettask/api/__init__.py +0 -103
- jettask/backend/__init__.py +0 -1
- jettask/backend/api/__init__.py +0 -3
- jettask/backend/api/v1/__init__.py +0 -17
- jettask/backend/api/v1/monitoring.py +0 -431
- jettask/backend/api/v1/namespaces.py +0 -504
- jettask/backend/api/v1/queues.py +0 -342
- jettask/backend/api/v1/tasks.py +0 -367
- jettask/backend/core/__init__.py +0 -3
- jettask/backend/core/cache.py +0 -221
- jettask/backend/core/database.py +0 -200
- jettask/backend/core/exceptions.py +0 -102
- jettask/backend/dependencies.py +0 -261
- jettask/backend/init_meta_db.py +0 -158
- jettask/backend/main.py +0 -1426
- jettask/backend/main_unified.py +0 -78
- jettask/backend/main_v2.py +0 -394
- jettask/backend/models/__init__.py +0 -3
- jettask/backend/models/requests.py +0 -236
- jettask/backend/models/responses.py +0 -230
- jettask/backend/namespace_api_old.py +0 -267
- jettask/backend/services/__init__.py +0 -3
- jettask/backend/start.py +0 -42
- jettask/backend/unified_api_router.py +0 -1541
- jettask/cleanup_deprecated_tables.sql +0 -16
- jettask/core/consumer_manager.py +0 -1695
- jettask/core/delay_scanner.py +0 -256
- jettask/core/event_pool.py +0 -1700
- jettask/core/heartbeat_process.py +0 -222
- jettask/core/task_batch.py +0 -153
- jettask/core/worker_scanner.py +0 -271
- jettask/executors/__init__.py +0 -5
- jettask/executors/asyncio.py +0 -876
- jettask/executors/base.py +0 -30
- jettask/executors/common.py +0 -148
- jettask/executors/multi_asyncio.py +0 -309
- jettask/gradio_app.py +0 -570
- jettask/integrated_gradio_app.py +0 -1088
- jettask/main.py +0 -0
- jettask/monitoring/__init__.py +0 -3
- jettask/pg_consumer.py +0 -1896
- jettask/run_monitor.py +0 -22
- jettask/run_webui.py +0 -148
- jettask/scheduler/multi_namespace_scheduler.py +0 -294
- jettask/scheduler/unified_manager.py +0 -450
- jettask/task_center_client.py +0 -150
- jettask/utils/serializer_optimized.py +0 -33
- jettask/webui_exceptions.py +0 -67
- jettask-0.2.19.dist-info/RECORD +0 -150
- /jettask/{constants.py → config/constants.py} +0 -0
- /jettask/{backend/config.py → config/task_center.py} +0 -0
- /jettask/{pg_consumer → messaging/pg_consumer}/pg_consumer_v2.py +0 -0
- /jettask/{pg_consumer → messaging/pg_consumer}/sql/add_execution_time_field.sql +0 -0
- /jettask/{pg_consumer → messaging/pg_consumer}/sql/create_new_tables.sql +0 -0
- /jettask/{pg_consumer → messaging/pg_consumer}/sql/create_tables_v3.sql +0 -0
- /jettask/{pg_consumer → messaging/pg_consumer}/sql/migrate_to_new_structure.sql +0 -0
- /jettask/{pg_consumer → messaging/pg_consumer}/sql/modify_time_fields.sql +0 -0
- /jettask/{pg_consumer → messaging/pg_consumer}/sql_utils.py +0 -0
- /jettask/{models.py → persistence/models.py} +0 -0
- /jettask/scheduler/{manager.py → task_crud.py} +0 -0
- /jettask/{schema.sql → schemas/schema.sql} +0 -0
- /jettask/{task_center.py → task/task_center/client.py} +0 -0
- /jettask/{monitoring → utils}/file_watcher.py +0 -0
- /jettask/{services/redis_monitor_service.py → utils/redis_monitor.py} +0 -0
- /jettask/{api/v1 → webui/api}/__init__.py +0 -0
- /jettask/{webui_config.py → webui/config.py} +0 -0
- /jettask/{webui_models → webui/models}/__init__.py +0 -0
- /jettask/{webui_models → webui/models}/namespace.py +0 -0
- /jettask/{services → webui/services}/alert_service.py +0 -0
- /jettask/{services → webui/services}/analytics_service.py +0 -0
- /jettask/{services → webui/services}/scheduled_task_service.py +0 -0
- /jettask/{services → webui/services}/task_service.py +0 -0
- /jettask/{webui_sql → webui/sql}/batch_upsert_functions.sql +0 -0
- /jettask/{webui_sql → webui/sql}/verify_database.sql +0 -0
- {jettask-0.2.19.dist-info → jettask-0.2.20.dist-info}/WHEEL +0 -0
- {jettask-0.2.19.dist-info → jettask-0.2.20.dist-info}/entry_points.txt +0 -0
- {jettask-0.2.19.dist-info → jettask-0.2.20.dist-info}/licenses/LICENSE +0 -0
- {jettask-0.2.19.dist-info → jettask-0.2.20.dist-info}/top_level.txt +0 -0
@@ -8,9 +8,9 @@ import multiprocessing
|
|
8
8
|
from typing import Dict, Optional, Set
|
9
9
|
from jettask.core.unified_manager_base import UnifiedManagerBase
|
10
10
|
from jettask import Jettask
|
11
|
-
from jettask.task_center import TaskCenter
|
11
|
+
from jettask.task.task_center.client import TaskCenter
|
12
12
|
from .scheduler import TaskScheduler
|
13
|
-
from .
|
13
|
+
from .task_crud import ScheduledTaskManager
|
14
14
|
|
15
15
|
logger = logging.getLogger(__name__)
|
16
16
|
|
@@ -66,10 +66,10 @@ class UnifiedSchedulerManager(UnifiedManagerBase):
|
|
66
66
|
|
67
67
|
# 创建 Jettask 应用
|
68
68
|
app = Jettask(task_center=tc)
|
69
|
-
|
69
|
+
|
70
70
|
# 创建调度器管理器(不需要传递namespace参数)
|
71
71
|
manager = ScheduledTaskManager(app)
|
72
|
-
|
72
|
+
|
73
73
|
# 创建并启动调度器
|
74
74
|
self.scheduler_instance = TaskScheduler(
|
75
75
|
app=app,
|
@@ -77,7 +77,7 @@ class UnifiedSchedulerManager(UnifiedManagerBase):
|
|
77
77
|
scan_interval=self.scan_interval,
|
78
78
|
batch_size=self.batch_size
|
79
79
|
)
|
80
|
-
|
80
|
+
|
81
81
|
# 运行调度器
|
82
82
|
await self.scheduler_instance.run()
|
83
83
|
|
@@ -171,21 +171,26 @@ class UnifiedSchedulerManager(UnifiedManagerBase):
|
|
171
171
|
async def run_scheduler():
|
172
172
|
try:
|
173
173
|
# 构建命名空间特定的URL
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
174
|
+
# 需要处理多种格式:
|
175
|
+
# 1. http://localhost:8001 -> http://localhost:8001/api/v1/namespaces/{name}
|
176
|
+
# 2. http://localhost:8001/api/v1 -> http://localhost:8001/api/v1/namespaces/{name}
|
177
|
+
# 3. http://localhost:8001/api/v1/namespaces/old -> http://localhost:8001/api/v1/namespaces/{name}
|
178
|
+
|
179
|
+
if '/api/v1/namespaces/' in task_center_url:
|
180
|
+
# 替换现有的命名空间
|
179
181
|
base_url = task_center_url.split('/api/v1/namespaces/')[0]
|
180
182
|
url = f"{base_url}/api/v1/namespaces/{namespace_name}"
|
181
183
|
elif '/api/namespaces/' in task_center_url:
|
182
184
|
# 兼容旧格式
|
183
185
|
base_url = task_center_url.split('/api/namespaces/')[0]
|
184
186
|
url = f"{base_url}/api/namespaces/{namespace_name}"
|
187
|
+
elif task_center_url.endswith('/api/v1') or task_center_url.endswith('/api/v1/'):
|
188
|
+
# 多命名空间模式,URL 是 http://localhost:8001/api/v1
|
189
|
+
url = f"{task_center_url.rstrip('/')}/namespaces/{namespace_name}"
|
185
190
|
else:
|
186
|
-
#
|
191
|
+
# 基础URL,添加完整路径
|
187
192
|
url = f"{task_center_url.rstrip('/')}/api/v1/namespaces/{namespace_name}"
|
188
|
-
|
193
|
+
|
189
194
|
# 创建任务中心连接
|
190
195
|
tc = TaskCenter(url)
|
191
196
|
if not tc._connect_sync():
|
@@ -197,7 +202,7 @@ class UnifiedSchedulerManager(UnifiedManagerBase):
|
|
197
202
|
# 创建调度器管理器 - ScheduledTaskManager 只接受一个参数
|
198
203
|
manager = ScheduledTaskManager(app)
|
199
204
|
|
200
|
-
# 创建并启动调度器
|
205
|
+
# 创建并启动调度器 - 使用正确的参数名
|
201
206
|
scheduler = TaskScheduler(
|
202
207
|
app=app,
|
203
208
|
db_manager=manager,
|
jettask/task/__init__.py
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
"""
|
2
|
+
任务管理层
|
3
|
+
|
4
|
+
提供统一的任务注册、执行和生命周期管理
|
5
|
+
"""
|
6
|
+
|
7
|
+
from .task_registry import TaskRegistry, TaskDefinition
|
8
|
+
from .task_executor import TaskExecutor
|
9
|
+
from .router import TaskRouter
|
10
|
+
|
11
|
+
__all__ = [
|
12
|
+
'TaskRegistry',
|
13
|
+
'TaskDefinition',
|
14
|
+
'TaskExecutor',
|
15
|
+
'TaskRouter',
|
16
|
+
]
|
@@ -3,9 +3,9 @@ Task Router for modular task organization
|
|
3
3
|
Similar to FastAPI's APIRouter
|
4
4
|
"""
|
5
5
|
|
6
|
-
from typing import
|
7
|
-
from functools import wraps
|
6
|
+
from typing import Dict, List, Any, Callable
|
8
7
|
import logging
|
8
|
+
from jettask.utils.rate_limit.config import RateLimitConfig
|
9
9
|
|
10
10
|
logger = logging.getLogger(__name__)
|
11
11
|
|
@@ -71,18 +71,35 @@ class TaskRouter:
|
|
71
71
|
timeout: int = None,
|
72
72
|
max_retries: int = None,
|
73
73
|
retry_delay: int = None,
|
74
|
+
rate_limit: RateLimitConfig = None,
|
74
75
|
**kwargs
|
75
76
|
):
|
76
77
|
"""
|
77
78
|
任务装饰器
|
78
|
-
|
79
|
+
|
79
80
|
Args:
|
80
81
|
name: 任务名称(可选,默认使用函数名)
|
81
82
|
queue: 队列名(可选,默认使用路由器的默认队列)
|
82
83
|
timeout: 超时时间
|
83
84
|
max_retries: 最大重试次数
|
84
85
|
retry_delay: 重试延迟
|
86
|
+
rate_limit: 限流配置(QPSLimit 或 ConcurrencyLimit)
|
85
87
|
**kwargs: 其他任务参数
|
88
|
+
|
89
|
+
Example:
|
90
|
+
from jettask import TaskRouter, QPSLimit, ConcurrencyLimit
|
91
|
+
|
92
|
+
router = TaskRouter(prefix="email")
|
93
|
+
|
94
|
+
# QPS 限流
|
95
|
+
@router.task(rate_limit=QPSLimit(qps=100))
|
96
|
+
async def send_email(to: str):
|
97
|
+
pass
|
98
|
+
|
99
|
+
# 并发限流
|
100
|
+
@router.task(rate_limit=ConcurrencyLimit(max_concurrency=10))
|
101
|
+
async def heavy_task():
|
102
|
+
pass
|
86
103
|
"""
|
87
104
|
def decorator(func: Callable):
|
88
105
|
# 生成任务名
|
@@ -91,7 +108,7 @@ class TaskRouter:
|
|
91
108
|
full_task_name = f"{self.prefix}.{task_name}"
|
92
109
|
else:
|
93
110
|
full_task_name = task_name
|
94
|
-
|
111
|
+
|
95
112
|
# 合并参数(优先使用任务级别的参数)
|
96
113
|
task_config = {
|
97
114
|
'func': func,
|
@@ -100,19 +117,20 @@ class TaskRouter:
|
|
100
117
|
'timeout': timeout or self.default_timeout,
|
101
118
|
'max_retries': max_retries or self.default_max_retries,
|
102
119
|
'retry_delay': retry_delay or self.default_retry_delay,
|
120
|
+
'rate_limit': rate_limit,
|
103
121
|
'tags': self.tags,
|
104
122
|
**kwargs
|
105
123
|
}
|
106
|
-
|
124
|
+
|
107
125
|
# 移除None值
|
108
126
|
task_config = {k: v for k, v in task_config.items() if v is not None}
|
109
|
-
|
127
|
+
|
110
128
|
# 存储任务配置
|
111
129
|
self._tasks[full_task_name] = task_config
|
112
|
-
|
130
|
+
|
113
131
|
# 返回原函数,保持函数可以被直接调用
|
114
132
|
return func
|
115
|
-
|
133
|
+
|
116
134
|
return decorator
|
117
135
|
|
118
136
|
def include_router(self, router: 'TaskRouter', prefix: str = None):
|
@@ -0,0 +1,318 @@
|
|
1
|
+
"""
|
2
|
+
任务执行器
|
3
|
+
|
4
|
+
负责任务的执行、超时控制、错误处理
|
5
|
+
"""
|
6
|
+
|
7
|
+
from typing import List, Dict, Any, Optional
|
8
|
+
import asyncio
|
9
|
+
import logging
|
10
|
+
import traceback
|
11
|
+
from datetime import datetime
|
12
|
+
|
13
|
+
from .task_registry import TaskRegistry, TaskDefinition
|
14
|
+
|
15
|
+
logger = logging.getLogger('app')
|
16
|
+
|
17
|
+
|
18
|
+
class TaskExecutor:
|
19
|
+
"""
|
20
|
+
任务执行器
|
21
|
+
|
22
|
+
职责:
|
23
|
+
1. 任务执行
|
24
|
+
2. 超时控制
|
25
|
+
3. 错误处理
|
26
|
+
4. 结果存储
|
27
|
+
|
28
|
+
从ExecutorCore提取的纯任务执行逻辑
|
29
|
+
"""
|
30
|
+
|
31
|
+
def __init__(self,
|
32
|
+
task_registry: TaskRegistry,
|
33
|
+
data_access=None,
|
34
|
+
retry_manager=None):
|
35
|
+
"""
|
36
|
+
初始化任务执行器
|
37
|
+
|
38
|
+
Args:
|
39
|
+
task_registry: 任务注册器
|
40
|
+
data_access: 数据访问层(可选)
|
41
|
+
retry_manager: 重试管理器(可选)
|
42
|
+
"""
|
43
|
+
self.registry = task_registry
|
44
|
+
self.data_access = data_access
|
45
|
+
self.retry_manager = retry_manager
|
46
|
+
|
47
|
+
logger.debug("TaskExecutor initialized")
|
48
|
+
|
49
|
+
async def execute(self, message: dict) -> Any:
|
50
|
+
"""
|
51
|
+
执行任务
|
52
|
+
|
53
|
+
Args:
|
54
|
+
message: 任务消息
|
55
|
+
- task_name: 任务名称
|
56
|
+
- task_id: 任务ID
|
57
|
+
- args: 位置参数
|
58
|
+
- kwargs: 关键字参数
|
59
|
+
|
60
|
+
Returns:
|
61
|
+
Any: 任务执行结果
|
62
|
+
|
63
|
+
Raises:
|
64
|
+
ValueError: 任务不存在
|
65
|
+
asyncio.TimeoutError: 任务超时
|
66
|
+
Exception: 任务执行错误
|
67
|
+
"""
|
68
|
+
task_name = message.get('task_name')
|
69
|
+
task_id = message.get('task_id')
|
70
|
+
args = message.get('args', [])
|
71
|
+
kwargs = message.get('kwargs', {})
|
72
|
+
|
73
|
+
logger.debug(f"Executing task: {task_name} (id: {task_id})")
|
74
|
+
|
75
|
+
# 获取任务定义
|
76
|
+
task_def = self.registry.get(task_name)
|
77
|
+
if not task_def:
|
78
|
+
error_msg = f"Task {task_name} not found in registry"
|
79
|
+
logger.error(error_msg)
|
80
|
+
raise ValueError(error_msg)
|
81
|
+
|
82
|
+
# 执行任务(带超时)
|
83
|
+
try:
|
84
|
+
# 记录开始时间
|
85
|
+
start_time = datetime.now()
|
86
|
+
|
87
|
+
# 执行任务
|
88
|
+
if asyncio.iscoroutinefunction(task_def.func):
|
89
|
+
# 异步任务
|
90
|
+
result = await asyncio.wait_for(
|
91
|
+
task_def.func(*args, **kwargs),
|
92
|
+
timeout=task_def.timeout
|
93
|
+
)
|
94
|
+
else:
|
95
|
+
# 同步任务,在线程池中执行
|
96
|
+
loop = asyncio.get_event_loop()
|
97
|
+
result = await asyncio.wait_for(
|
98
|
+
loop.run_in_executor(None, task_def.func, *args, **kwargs),
|
99
|
+
timeout=task_def.timeout
|
100
|
+
)
|
101
|
+
|
102
|
+
# 计算执行时间
|
103
|
+
execution_time = (datetime.now() - start_time).total_seconds()
|
104
|
+
|
105
|
+
logger.info(
|
106
|
+
f"Task {task_name} (id: {task_id}) completed "
|
107
|
+
f"in {execution_time:.2f}s"
|
108
|
+
)
|
109
|
+
|
110
|
+
# 保存成功结果
|
111
|
+
if self.data_access:
|
112
|
+
await self.data_access.save_task_result(
|
113
|
+
task_id=task_id,
|
114
|
+
status='completed',
|
115
|
+
result=result,
|
116
|
+
execution_time=execution_time
|
117
|
+
)
|
118
|
+
|
119
|
+
return result
|
120
|
+
|
121
|
+
except asyncio.TimeoutError:
|
122
|
+
# 超时处理
|
123
|
+
logger.error(
|
124
|
+
f"Task {task_name} (id: {task_id}) timeout "
|
125
|
+
f"after {task_def.timeout}s"
|
126
|
+
)
|
127
|
+
await self._handle_timeout(task_id, task_def, message)
|
128
|
+
raise
|
129
|
+
|
130
|
+
except Exception as e:
|
131
|
+
# 错误处理
|
132
|
+
logger.error(
|
133
|
+
f"Task {task_name} (id: {task_id}) failed: {e}\n"
|
134
|
+
f"{traceback.format_exc()}"
|
135
|
+
)
|
136
|
+
await self._handle_error(task_id, task_def, message, e)
|
137
|
+
raise
|
138
|
+
|
139
|
+
async def _handle_timeout(self,
|
140
|
+
task_id: str,
|
141
|
+
task_def: TaskDefinition,
|
142
|
+
message: dict):
|
143
|
+
"""
|
144
|
+
处理超时
|
145
|
+
|
146
|
+
Args:
|
147
|
+
task_id: 任务ID
|
148
|
+
task_def: 任务定义
|
149
|
+
message: 任务消息
|
150
|
+
"""
|
151
|
+
# 保存超时状态
|
152
|
+
if self.data_access:
|
153
|
+
await self.data_access.save_task_result(
|
154
|
+
task_id=task_id,
|
155
|
+
status='timeout',
|
156
|
+
result=None,
|
157
|
+
error=f"Task timeout after {task_def.timeout}s"
|
158
|
+
)
|
159
|
+
|
160
|
+
# 重试逻辑
|
161
|
+
if task_def.max_retries > 0 and self.retry_manager:
|
162
|
+
current_retry = message.get('retry_count', 0)
|
163
|
+
if current_retry < task_def.max_retries:
|
164
|
+
logger.info(
|
165
|
+
f"Scheduling retry for task {task_def.name} "
|
166
|
+
f"(attempt {current_retry + 1}/{task_def.max_retries})"
|
167
|
+
)
|
168
|
+
await self.retry_manager.schedule_retry(
|
169
|
+
task_id=task_id,
|
170
|
+
task_def=task_def,
|
171
|
+
message=message,
|
172
|
+
retry_count=current_retry + 1,
|
173
|
+
delay=task_def.retry_delay
|
174
|
+
)
|
175
|
+
|
176
|
+
async def _handle_error(self,
|
177
|
+
task_id: str,
|
178
|
+
task_def: TaskDefinition,
|
179
|
+
message: dict,
|
180
|
+
error: Exception):
|
181
|
+
"""
|
182
|
+
处理错误
|
183
|
+
|
184
|
+
Args:
|
185
|
+
task_id: 任务ID
|
186
|
+
task_def: 任务定义
|
187
|
+
message: 任务消息
|
188
|
+
error: 错误对象
|
189
|
+
"""
|
190
|
+
# 保存失败状态
|
191
|
+
if self.data_access:
|
192
|
+
await self.data_access.save_task_result(
|
193
|
+
task_id=task_id,
|
194
|
+
status='failed',
|
195
|
+
result=None,
|
196
|
+
error=str(error),
|
197
|
+
traceback=traceback.format_exc()
|
198
|
+
)
|
199
|
+
|
200
|
+
# 重试逻辑
|
201
|
+
if task_def.max_retries > 0 and self.retry_manager:
|
202
|
+
current_retry = message.get('retry_count', 0)
|
203
|
+
if current_retry < task_def.max_retries:
|
204
|
+
logger.info(
|
205
|
+
f"Scheduling retry for task {task_def.name} "
|
206
|
+
f"(attempt {current_retry + 1}/{task_def.max_retries})"
|
207
|
+
)
|
208
|
+
await self.retry_manager.schedule_retry(
|
209
|
+
task_id=task_id,
|
210
|
+
task_def=task_def,
|
211
|
+
message=message,
|
212
|
+
retry_count=current_retry + 1,
|
213
|
+
delay=task_def.retry_delay
|
214
|
+
)
|
215
|
+
else:
|
216
|
+
logger.error(
|
217
|
+
f"Task {task_def.name} failed after {task_def.max_retries} retries"
|
218
|
+
)
|
219
|
+
|
220
|
+
async def batch_update_status(self, status_updates: List[dict]):
|
221
|
+
"""
|
222
|
+
批量更新任务状态
|
223
|
+
|
224
|
+
Args:
|
225
|
+
status_updates: 状态更新列表
|
226
|
+
- task_id: 任务ID
|
227
|
+
- status: 状态
|
228
|
+
- result: 结果(可选)
|
229
|
+
- error: 错误信息(可选)
|
230
|
+
"""
|
231
|
+
if not self.data_access:
|
232
|
+
logger.warning("No data_access configured, skipping batch status update")
|
233
|
+
return
|
234
|
+
|
235
|
+
for update in status_updates:
|
236
|
+
try:
|
237
|
+
await self.data_access.update_task_status(
|
238
|
+
task_id=update['task_id'],
|
239
|
+
status=update['status'],
|
240
|
+
result=update.get('result'),
|
241
|
+
error=update.get('error')
|
242
|
+
)
|
243
|
+
except Exception as e:
|
244
|
+
logger.error(f"Error updating task status: {e}")
|
245
|
+
|
246
|
+
async def execute_batch(self, messages: List[dict]) -> List[Dict[str, Any]]:
|
247
|
+
"""
|
248
|
+
批量执行任务
|
249
|
+
|
250
|
+
Args:
|
251
|
+
messages: 任务消息列表
|
252
|
+
|
253
|
+
Returns:
|
254
|
+
List[Dict]: 执行结果列表
|
255
|
+
- task_id: 任务ID
|
256
|
+
- status: 状态(completed/failed/timeout)
|
257
|
+
- result: 结果(可选)
|
258
|
+
- error: 错误信息(可选)
|
259
|
+
"""
|
260
|
+
results = []
|
261
|
+
|
262
|
+
# 并发执行所有任务
|
263
|
+
tasks = []
|
264
|
+
for message in messages:
|
265
|
+
task = asyncio.create_task(self._execute_with_result(message))
|
266
|
+
tasks.append(task)
|
267
|
+
|
268
|
+
# 等待所有任务完成
|
269
|
+
task_results = await asyncio.gather(*tasks, return_exceptions=True)
|
270
|
+
|
271
|
+
# 整理结果
|
272
|
+
for i, task_result in enumerate(task_results):
|
273
|
+
message = messages[i]
|
274
|
+
task_id = message.get('task_id')
|
275
|
+
|
276
|
+
if isinstance(task_result, Exception):
|
277
|
+
# 任务失败
|
278
|
+
if isinstance(task_result, asyncio.TimeoutError):
|
279
|
+
status = 'timeout'
|
280
|
+
error = 'Task timeout'
|
281
|
+
else:
|
282
|
+
status = 'failed'
|
283
|
+
error = str(task_result)
|
284
|
+
|
285
|
+
results.append({
|
286
|
+
'task_id': task_id,
|
287
|
+
'status': status,
|
288
|
+
'result': None,
|
289
|
+
'error': error
|
290
|
+
})
|
291
|
+
else:
|
292
|
+
# 任务成功
|
293
|
+
results.append({
|
294
|
+
'task_id': task_id,
|
295
|
+
'status': 'completed',
|
296
|
+
'result': task_result,
|
297
|
+
'error': None
|
298
|
+
})
|
299
|
+
|
300
|
+
return results
|
301
|
+
|
302
|
+
async def _execute_with_result(self, message: dict) -> Any:
|
303
|
+
"""
|
304
|
+
执行任务并返回结果(用于批量执行)
|
305
|
+
|
306
|
+
Args:
|
307
|
+
message: 任务消息
|
308
|
+
|
309
|
+
Returns:
|
310
|
+
Any: 任务执行结果
|
311
|
+
|
312
|
+
Raises:
|
313
|
+
Exception: 任务执行错误
|
314
|
+
"""
|
315
|
+
try:
|
316
|
+
return await self.execute(message)
|
317
|
+
except Exception:
|
318
|
+
raise # 让gather捕获异常
|