jettask 0.2.19__py3-none-any.whl → 0.2.23__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.
Files changed (165) hide show
  1. jettask/__init__.py +12 -3
  2. jettask/cli.py +314 -228
  3. jettask/config/__init__.py +9 -1
  4. jettask/config/config.py +245 -0
  5. jettask/config/env_loader.py +381 -0
  6. jettask/config/lua_scripts.py +158 -0
  7. jettask/config/nacos_config.py +132 -5
  8. jettask/core/__init__.py +1 -1
  9. jettask/core/app.py +1573 -666
  10. jettask/core/app_importer.py +33 -16
  11. jettask/core/container.py +532 -0
  12. jettask/core/task.py +1 -4
  13. jettask/core/unified_manager_base.py +2 -2
  14. jettask/executor/__init__.py +38 -0
  15. jettask/executor/core.py +625 -0
  16. jettask/executor/executor.py +338 -0
  17. jettask/executor/orchestrator.py +290 -0
  18. jettask/executor/process_entry.py +638 -0
  19. jettask/executor/task_executor.py +317 -0
  20. jettask/messaging/__init__.py +68 -0
  21. jettask/messaging/event_pool.py +2188 -0
  22. jettask/messaging/reader.py +519 -0
  23. jettask/messaging/registry.py +266 -0
  24. jettask/messaging/scanner.py +369 -0
  25. jettask/messaging/sender.py +312 -0
  26. jettask/persistence/__init__.py +118 -0
  27. jettask/persistence/backlog_monitor.py +567 -0
  28. jettask/{backend/data_access.py → persistence/base.py} +58 -57
  29. jettask/persistence/consumer.py +315 -0
  30. jettask/{core → persistence}/db_manager.py +23 -22
  31. jettask/persistence/maintenance.py +81 -0
  32. jettask/persistence/message_consumer.py +259 -0
  33. jettask/{backend/namespace_data_access.py → persistence/namespace.py} +66 -98
  34. jettask/persistence/offline_recovery.py +196 -0
  35. jettask/persistence/queue_discovery.py +215 -0
  36. jettask/persistence/task_persistence.py +218 -0
  37. jettask/persistence/task_updater.py +583 -0
  38. jettask/scheduler/__init__.py +2 -2
  39. jettask/scheduler/loader.py +6 -5
  40. jettask/scheduler/run_scheduler.py +1 -1
  41. jettask/scheduler/scheduler.py +7 -7
  42. jettask/scheduler/{unified_scheduler_manager.py → scheduler_coordinator.py} +18 -13
  43. jettask/task/__init__.py +16 -0
  44. jettask/{router.py → task/router.py} +26 -8
  45. jettask/task/task_center/__init__.py +9 -0
  46. jettask/task/task_executor.py +318 -0
  47. jettask/task/task_registry.py +291 -0
  48. jettask/test_connection_monitor.py +73 -0
  49. jettask/utils/__init__.py +31 -1
  50. jettask/{monitor/run_backlog_collector.py → utils/backlog_collector.py} +1 -1
  51. jettask/utils/db_connector.py +1629 -0
  52. jettask/{db_init.py → utils/db_init.py} +1 -1
  53. jettask/utils/rate_limit/__init__.py +30 -0
  54. jettask/utils/rate_limit/concurrency_limiter.py +665 -0
  55. jettask/utils/rate_limit/config.py +145 -0
  56. jettask/utils/rate_limit/limiter.py +41 -0
  57. jettask/utils/rate_limit/manager.py +269 -0
  58. jettask/utils/rate_limit/qps_limiter.py +154 -0
  59. jettask/utils/rate_limit/task_limiter.py +384 -0
  60. jettask/utils/serializer.py +3 -0
  61. jettask/{monitor/stream_backlog_monitor.py → utils/stream_backlog.py} +14 -6
  62. jettask/utils/time_sync.py +173 -0
  63. jettask/webui/__init__.py +27 -0
  64. jettask/{api/v1 → webui/api}/alerts.py +1 -1
  65. jettask/{api/v1 → webui/api}/analytics.py +2 -2
  66. jettask/{api/v1 → webui/api}/namespaces.py +1 -1
  67. jettask/{api/v1 → webui/api}/overview.py +1 -1
  68. jettask/{api/v1 → webui/api}/queues.py +3 -3
  69. jettask/{api/v1 → webui/api}/scheduled.py +1 -1
  70. jettask/{api/v1 → webui/api}/settings.py +1 -1
  71. jettask/{api.py → webui/app.py} +253 -145
  72. jettask/webui/namespace_manager/__init__.py +10 -0
  73. jettask/{multi_namespace_consumer.py → webui/namespace_manager/multi.py} +69 -22
  74. jettask/{unified_consumer_manager.py → webui/namespace_manager/unified.py} +1 -1
  75. jettask/{run.py → webui/run.py} +2 -2
  76. jettask/{services → webui/services}/__init__.py +1 -3
  77. jettask/{services → webui/services}/overview_service.py +34 -16
  78. jettask/{services → webui/services}/queue_service.py +1 -1
  79. jettask/{backend → webui/services}/queue_stats_v2.py +1 -1
  80. jettask/{services → webui/services}/settings_service.py +1 -1
  81. jettask/worker/__init__.py +53 -0
  82. jettask/worker/lifecycle.py +1507 -0
  83. jettask/worker/manager.py +583 -0
  84. jettask/{core/offline_worker_recovery.py → worker/recovery.py} +268 -175
  85. {jettask-0.2.19.dist-info → jettask-0.2.23.dist-info}/METADATA +2 -71
  86. jettask-0.2.23.dist-info/RECORD +145 -0
  87. jettask/__main__.py +0 -140
  88. jettask/api/__init__.py +0 -103
  89. jettask/backend/__init__.py +0 -1
  90. jettask/backend/api/__init__.py +0 -3
  91. jettask/backend/api/v1/__init__.py +0 -17
  92. jettask/backend/api/v1/monitoring.py +0 -431
  93. jettask/backend/api/v1/namespaces.py +0 -504
  94. jettask/backend/api/v1/queues.py +0 -342
  95. jettask/backend/api/v1/tasks.py +0 -367
  96. jettask/backend/core/__init__.py +0 -3
  97. jettask/backend/core/cache.py +0 -221
  98. jettask/backend/core/database.py +0 -200
  99. jettask/backend/core/exceptions.py +0 -102
  100. jettask/backend/dependencies.py +0 -261
  101. jettask/backend/init_meta_db.py +0 -158
  102. jettask/backend/main.py +0 -1426
  103. jettask/backend/main_unified.py +0 -78
  104. jettask/backend/main_v2.py +0 -394
  105. jettask/backend/models/__init__.py +0 -3
  106. jettask/backend/models/requests.py +0 -236
  107. jettask/backend/models/responses.py +0 -230
  108. jettask/backend/namespace_api_old.py +0 -267
  109. jettask/backend/services/__init__.py +0 -3
  110. jettask/backend/start.py +0 -42
  111. jettask/backend/unified_api_router.py +0 -1541
  112. jettask/cleanup_deprecated_tables.sql +0 -16
  113. jettask/core/consumer_manager.py +0 -1695
  114. jettask/core/delay_scanner.py +0 -256
  115. jettask/core/event_pool.py +0 -1700
  116. jettask/core/heartbeat_process.py +0 -222
  117. jettask/core/task_batch.py +0 -153
  118. jettask/core/worker_scanner.py +0 -271
  119. jettask/executors/__init__.py +0 -5
  120. jettask/executors/asyncio.py +0 -876
  121. jettask/executors/base.py +0 -30
  122. jettask/executors/common.py +0 -148
  123. jettask/executors/multi_asyncio.py +0 -309
  124. jettask/gradio_app.py +0 -570
  125. jettask/integrated_gradio_app.py +0 -1088
  126. jettask/main.py +0 -0
  127. jettask/monitoring/__init__.py +0 -3
  128. jettask/pg_consumer.py +0 -1896
  129. jettask/run_monitor.py +0 -22
  130. jettask/run_webui.py +0 -148
  131. jettask/scheduler/multi_namespace_scheduler.py +0 -294
  132. jettask/scheduler/unified_manager.py +0 -450
  133. jettask/task_center_client.py +0 -150
  134. jettask/utils/serializer_optimized.py +0 -33
  135. jettask/webui_exceptions.py +0 -67
  136. jettask-0.2.19.dist-info/RECORD +0 -150
  137. /jettask/{constants.py → config/constants.py} +0 -0
  138. /jettask/{backend/config.py → config/task_center.py} +0 -0
  139. /jettask/{pg_consumer → messaging/pg_consumer}/pg_consumer_v2.py +0 -0
  140. /jettask/{pg_consumer → messaging/pg_consumer}/sql/add_execution_time_field.sql +0 -0
  141. /jettask/{pg_consumer → messaging/pg_consumer}/sql/create_new_tables.sql +0 -0
  142. /jettask/{pg_consumer → messaging/pg_consumer}/sql/create_tables_v3.sql +0 -0
  143. /jettask/{pg_consumer → messaging/pg_consumer}/sql/migrate_to_new_structure.sql +0 -0
  144. /jettask/{pg_consumer → messaging/pg_consumer}/sql/modify_time_fields.sql +0 -0
  145. /jettask/{pg_consumer → messaging/pg_consumer}/sql_utils.py +0 -0
  146. /jettask/{models.py → persistence/models.py} +0 -0
  147. /jettask/scheduler/{manager.py → task_crud.py} +0 -0
  148. /jettask/{schema.sql → schemas/schema.sql} +0 -0
  149. /jettask/{task_center.py → task/task_center/client.py} +0 -0
  150. /jettask/{monitoring → utils}/file_watcher.py +0 -0
  151. /jettask/{services/redis_monitor_service.py → utils/redis_monitor.py} +0 -0
  152. /jettask/{api/v1 → webui/api}/__init__.py +0 -0
  153. /jettask/{webui_config.py → webui/config.py} +0 -0
  154. /jettask/{webui_models → webui/models}/__init__.py +0 -0
  155. /jettask/{webui_models → webui/models}/namespace.py +0 -0
  156. /jettask/{services → webui/services}/alert_service.py +0 -0
  157. /jettask/{services → webui/services}/analytics_service.py +0 -0
  158. /jettask/{services → webui/services}/scheduled_task_service.py +0 -0
  159. /jettask/{services → webui/services}/task_service.py +0 -0
  160. /jettask/{webui_sql → webui/sql}/batch_upsert_functions.sql +0 -0
  161. /jettask/{webui_sql → webui/sql}/verify_database.sql +0 -0
  162. {jettask-0.2.19.dist-info → jettask-0.2.23.dist-info}/WHEEL +0 -0
  163. {jettask-0.2.19.dist-info → jettask-0.2.23.dist-info}/entry_points.txt +0 -0
  164. {jettask-0.2.19.dist-info → jettask-0.2.23.dist-info}/licenses/LICENSE +0 -0
  165. {jettask-0.2.19.dist-info → jettask-0.2.23.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 .manager import ScheduledTaskManager
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
- if task_center_url.endswith('/api/v1/') or task_center_url.endswith('/api/v1'):
175
- # 多命名空间模式,URL是 http://localhost:8001/api/v1/
176
- url = f"{task_center_url.rstrip('/')}/namespaces/{namespace_name}"
177
- elif '/api/v1/namespaces/' in task_center_url:
178
- # 单命名空间模式的URL,需要替换命名空间
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
- # 如果是基础URL,添加命名空间路径
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,
@@ -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 Optional, Dict, List, Any, Callable
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,9 @@
1
+ """
2
+ 任务中心客户端模块
3
+
4
+ 提供与任务中心的通信功能
5
+ """
6
+
7
+ from .client import TaskCenter
8
+
9
+ __all__ = ['TaskCenter']
@@ -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捕获异常