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.
Files changed (165) hide show
  1. jettask/__init__.py +10 -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.20.dist-info}/METADATA +2 -71
  86. jettask-0.2.20.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.20.dist-info}/WHEEL +0 -0
  163. {jettask-0.2.19.dist-info → jettask-0.2.20.dist-info}/entry_points.txt +0 -0
  164. {jettask-0.2.19.dist-info → jettask-0.2.20.dist-info}/licenses/LICENSE +0 -0
  165. {jettask-0.2.19.dist-info → jettask-0.2.20.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,638 @@
1
+ """
2
+ 多进程执行器的子进程入口
3
+
4
+ 职责:
5
+ 1. 清理继承的父进程状态
6
+ 2. 初始化子进程环境
7
+ 3. 启动任务执行器
8
+ """
9
+ import os
10
+ import gc
11
+ import sys
12
+ import signal
13
+ import asyncio
14
+ import logging
15
+ import multiprocessing
16
+ from typing import List, Dict
17
+ import time
18
+
19
+
20
+
21
+
22
+
23
+ import redis
24
+ async def execute_single_command(client: redis.Redis, index: int):
25
+ """
26
+ 执行单条 Redis 命令
27
+
28
+ Args:
29
+ client: Redis 客户端
30
+ index: 命令索引
31
+ """
32
+ await client.set(f"test_key_{index}", f"value_{index}")
33
+
34
+
35
+ async def execute_commands(client: redis.Redis, num_commands: int = 10000, concurrency: int = 100):
36
+ """
37
+ 并发执行大量 Redis 命令
38
+
39
+ Args:
40
+ client: Redis 客户端
41
+ num_commands: 命令数量
42
+ concurrency: 并发数(同时执行的任务数)
43
+ """
44
+ logger.info(f"开始并发执行 {num_commands} 条 Redis 命令(并发数: {concurrency})...")
45
+
46
+ start_time = time.time()
47
+
48
+ # 创建信号量控制并发数
49
+ semaphore = asyncio.Semaphore(concurrency)
50
+
51
+ async def execute_with_semaphore(index: int):
52
+ """使用信号量控制并发"""
53
+ # async with semaphore:
54
+ await execute_single_command(client, index)
55
+
56
+ # 每 1000 条命令打印一次进度
57
+ if (index + 1) % 1000 == 0:
58
+ logger.info(f"已完成 {index + 1} 条命令")
59
+
60
+ # 创建所有任务
61
+ tasks = [
62
+ asyncio.create_task(execute_with_semaphore(i))
63
+ for i in range(num_commands)
64
+ ]
65
+
66
+ # 等待所有任务完成
67
+ await asyncio.gather(*tasks)
68
+
69
+ elapsed = time.time() - start_time
70
+ logger.info(f"✅ 完成 {num_commands} 条命令,耗时: {elapsed:.2f}s,QPS: {num_commands/elapsed:.0f}")
71
+ await asyncio.sleep(999999999999999999)
72
+ return elapsed
73
+
74
+
75
+ # 不要在模块级别创建 logger,避免在 fork 时触发 logging 全局锁竞争
76
+ # logger 将在 subprocess_main 中创建
77
+ logger = None
78
+
79
+
80
+ def _reset_locks_after_fork():
81
+ """
82
+ 在 fork 后立即重置所有 logging 相关的锁
83
+
84
+ 关键:不能调用任何 logging API(如 getLogger),因为这些 API 本身需要获取锁。
85
+ 必须直接访问 logging 模块的内部数据结构。
86
+ """
87
+ import threading
88
+ import logging
89
+
90
+ # 1. 暴力替换 logging 模块的全局锁(不通过 API)
91
+ logging._lock = threading.RLock()
92
+
93
+ # 2. 直接访问 Logger.manager 的内部字典,不调用 getLogger()
94
+ # 这样避免触发锁的获取
95
+ if hasattr(logging.Logger, 'manager'):
96
+ manager = logging.Logger.manager
97
+
98
+ # 2.1 重置 manager 的锁
99
+ if hasattr(manager, 'lock'):
100
+ manager.lock = threading.RLock()
101
+
102
+ # 2.2 重置根 logger 的锁(直接访问 root 属性)
103
+ if hasattr(manager, 'root'):
104
+ root = manager.root
105
+ if hasattr(root, '_lock'):
106
+ root._lock = threading.RLock()
107
+
108
+ # 重置根 logger 的所有 handlers 的锁
109
+ if hasattr(root, 'handlers'):
110
+ for handler in list(root.handlers):
111
+ if hasattr(handler, 'lock'):
112
+ handler.lock = threading.RLock()
113
+
114
+ # 2.3 重置所有子 logger 的锁(直接访问字典,不调用 getLogger)
115
+ if hasattr(manager, 'loggerDict'):
116
+ for logger_obj in list(manager.loggerDict.values()):
117
+ # loggerDict 中的值可能是 PlaceHolder 或 Logger
118
+ if hasattr(logger_obj, '_lock'):
119
+ logger_obj._lock = threading.RLock()
120
+
121
+ # 重置 handlers 的锁
122
+ if hasattr(logger_obj, 'handlers'):
123
+ for handler in list(logger_obj.handlers):
124
+ if hasattr(handler, 'lock'):
125
+ handler.lock = threading.RLock()
126
+
127
+
128
+
129
+ class SubprocessInitializer:
130
+ """子进程初始化器 - 负责清理和准备环境"""
131
+
132
+ @staticmethod
133
+ def cleanup_inherited_state():
134
+ """
135
+ 清理从父进程继承的状态(fork模式)
136
+
137
+ 在 fork 模式下,子进程继承父进程的内存状态,包括:
138
+ - Redis连接池和客户端
139
+ - 事件循环对象
140
+ - 线程对象和锁
141
+ - 信号处理器
142
+
143
+ 我们需要正确清理这些资源,避免:
144
+ - 子进程复用父进程的连接(会导致数据混乱)
145
+ - 访问父进程的任务/线程(会导致死锁)
146
+ - 信号处理器冲突
147
+ """
148
+ # 1. 重置信号处理器
149
+ signal.signal(signal.SIGINT, signal.SIG_DFL)
150
+ signal.signal(signal.SIGTERM, signal.SIG_DFL)
151
+
152
+ # 2. 重置事件循环策略
153
+ # 不要尝试访问或关闭旧循环,直接设置新的策略
154
+ # 这样子进程在首次使用asyncio时会创建全新的循环
155
+ try:
156
+ asyncio.set_event_loop_policy(None)
157
+ asyncio.set_event_loop(None)
158
+ except Exception:
159
+ pass
160
+
161
+ # 3. 清空Redis连接池和客户端缓存
162
+ # 这非常重要!防止子进程复用父进程的连接
163
+ from jettask.utils.db_connector import clear_all_cache
164
+ clear_all_cache()
165
+
166
+ # 4. 强制垃圾回收
167
+ gc.collect()
168
+
169
+ @staticmethod
170
+ def setup_logging(process_id: int, redis_prefix: str):
171
+ """配置子进程日志
172
+
173
+ 注意:在 fork 模式下,子进程会继承父进程的 logging handlers。
174
+ 这些 handlers 可能持有父进程的锁或文件描述符,导致死锁。
175
+ 因此需要先清除所有继承的 handlers,再手动创建全新的 handler。
176
+ """
177
+ # 0. 重置 logging 模块的全局锁(关键!)
178
+ # 在 fork 后,logging 模块的全局锁可能处于被父进程持有的状态
179
+ # 需要手动重新创建这些锁,避免死锁
180
+ import threading
181
+ logging._lock = threading.RLock()
182
+
183
+ # 1. 清除根 logger 的所有 handlers
184
+ root_logger = logging.getLogger()
185
+
186
+ # 重置根logger的锁
187
+ if hasattr(root_logger, '_lock'):
188
+ root_logger._lock = threading.RLock()
189
+
190
+ for handler in root_logger.handlers[:]:
191
+ try:
192
+ # 重置handler的锁
193
+ if hasattr(handler, 'lock'):
194
+ handler.lock = threading.RLock()
195
+ handler.close()
196
+ except:
197
+ pass
198
+ root_logger.removeHandler(handler)
199
+
200
+ # 2. 清除所有已存在的 logger 的 handlers,并重置 propagate
201
+ for logger_name in list(logging.Logger.manager.loggerDict.keys()):
202
+ logger_obj = logging.getLogger(logger_name)
203
+
204
+ # 重置logger的锁
205
+ if hasattr(logger_obj, '_lock'):
206
+ logger_obj._lock = threading.RLock()
207
+
208
+ if hasattr(logger_obj, 'handlers'):
209
+ for handler in logger_obj.handlers[:]:
210
+ try:
211
+ # 重置handler的锁
212
+ if hasattr(handler, 'lock'):
213
+ handler.lock = threading.RLock()
214
+ handler.close()
215
+ except:
216
+ pass
217
+ logger_obj.removeHandler(handler)
218
+
219
+ # 确保所有子 logger 的日志都能传播到根 logger
220
+ logger_obj.propagate = True
221
+
222
+ # 3. 手动创建全新的 handler
223
+ # 不使用 logging.basicConfig(),因为它可能会复用某些全局状态
224
+ # 而是手动创建一个全新的 StreamHandler
225
+ formatter = logging.Formatter(
226
+ fmt=f"%(asctime)s - %(levelname)s - [{redis_prefix}-P{process_id}] - %(message)s",
227
+ datefmt="%Y-%m-%d %H:%M:%S"
228
+ )
229
+
230
+ handler = logging.StreamHandler(sys.stderr)
231
+ handler.setFormatter(formatter)
232
+ handler.setLevel(logging.INFO)
233
+ # 确保新handler有正确的锁
234
+ handler.createLock()
235
+
236
+ root_logger.setLevel(logging.INFO)
237
+ root_logger.addHandler(handler)
238
+
239
+ @staticmethod
240
+ def create_event_loop() -> asyncio.AbstractEventLoop:
241
+ """创建全新的事件循环"""
242
+ loop = asyncio.new_event_loop()
243
+ asyncio.set_event_loop(loop)
244
+ return loop
245
+
246
+
247
+ class MinimalApp:
248
+ """
249
+ 最小化的 App 接口
250
+
251
+ 为子进程提供必要的接口,而不需要完整的 App 实例
252
+ """
253
+ def __init__(
254
+ self,
255
+ redis_client,
256
+ async_redis_client,
257
+ redis_url: str,
258
+ redis_prefix: str,
259
+ tasks: Dict,
260
+ worker_id: str,
261
+ worker_key: str
262
+ ):
263
+ self.redis = redis_client
264
+ self.async_redis = async_redis_client
265
+ self.redis_url = redis_url
266
+ self.redis_prefix = redis_prefix
267
+ self._tasks = tasks
268
+ self.worker_id = worker_id
269
+ self.worker_key = worker_key
270
+ self._should_exit = False
271
+
272
+ # ExecutorCore 需要的属性
273
+ self._status_prefix = f"{redis_prefix}:STATUS:"
274
+ self._result_prefix = f"{redis_prefix}:RESULT:"
275
+
276
+ # EventPool 需要的属性
277
+ self._tasks_by_queue = {}
278
+ for task_name, task in tasks.items():
279
+ task_queue = task.queue or redis_prefix
280
+ if task_queue not in self._tasks_by_queue:
281
+ self._tasks_by_queue[task_queue] = []
282
+ self._tasks_by_queue[task_queue].append(task_name)
283
+
284
+ # 这些属性会在初始化时设置
285
+ self.ep = None
286
+ self.consumer_manager = None
287
+ self.worker_state_manager = None
288
+ self.worker_state = None # EventPool 的恢复机制需要这个属性
289
+
290
+ def get_task_by_name(self, task_name: str):
291
+ """根据任务名称获取任务对象"""
292
+ return self._tasks.get(task_name)
293
+
294
+ def cleanup(self):
295
+ """清理资源"""
296
+ pass
297
+
298
+
299
+ class SubprocessRunner:
300
+ """子进程运行器 - 负责实际执行任务"""
301
+
302
+ def __init__(
303
+ self,
304
+ process_id: int,
305
+ redis_url: str,
306
+ redis_prefix: str,
307
+ queues: List[str],
308
+ tasks: Dict,
309
+ concurrency: int,
310
+ prefetch_multiplier: int,
311
+ max_connections: int,
312
+ consumer_strategy: str,
313
+ consumer_config: Dict,
314
+ worker_id: str,
315
+ worker_key: str
316
+ ):
317
+ self.process_id = process_id
318
+ self.redis_url = redis_url
319
+ self.redis_prefix = redis_prefix
320
+ self.queues = queues
321
+ self.tasks = tasks
322
+ self.concurrency = concurrency
323
+ self.prefetch_multiplier = prefetch_multiplier
324
+ self.max_connections = max_connections
325
+ self.consumer_strategy = consumer_strategy
326
+ self.consumer_config = consumer_config or {}
327
+ self.worker_id = worker_id
328
+ self.worker_key = worker_key
329
+
330
+ # 子进程内部状态
331
+ self.redis_client = None
332
+ self.async_redis_client = None
333
+ self.minimal_app = None
334
+ self.event_pool = None
335
+ self.executors = []
336
+ self._should_exit = False
337
+
338
+ def setup_signal_handlers(self):
339
+ """设置信号处理器"""
340
+ def signal_handler(signum, _frame):
341
+ logger.info(f"Process #{self.process_id} received signal {signum}")
342
+ self._should_exit = True
343
+ if self.minimal_app:
344
+ self.minimal_app._should_exit = True
345
+ if self.event_pool:
346
+ self.event_pool._stop_reading = True
347
+
348
+ # signal.signal(signal.SIGINT, signal_handler)
349
+ # signal.signal(signal.SIGTERM, signal_handler)
350
+
351
+ def create_redis_connections(self):
352
+ """创建独立的Redis连接(使用全局客户端实例)"""
353
+ from jettask.utils.db_connector import get_sync_redis_client, get_async_redis_client
354
+
355
+ # logger.info(f"Process #{self.process_id}: Creating Redis connections")
356
+
357
+ # 同步连接(使用全局客户端实例)
358
+ self.redis_client = get_sync_redis_client(
359
+ redis_url=self.redis_url,
360
+ decode_responses=True,
361
+ max_connections=self.max_connections
362
+ )
363
+
364
+ # 异步连接(使用全局客户端实例)
365
+ self.async_redis_client = get_async_redis_client(
366
+ redis_url=self.redis_url,
367
+ decode_responses=True,
368
+ max_connections=self.max_connections
369
+ )
370
+
371
+ async def initialize_components(self):
372
+ """初始化执行器组件"""
373
+ from jettask.messaging.event_pool import EventPool
374
+ from jettask.executor.task_executor import TaskExecutor
375
+
376
+ logger.info(f"Process #{self.process_id}: Initializing components")
377
+
378
+ # 创建 MinimalApp
379
+ self.minimal_app = MinimalApp(
380
+ redis_client=self.redis_client,
381
+ async_redis_client=self.async_redis_client,
382
+ redis_url=self.redis_url,
383
+ redis_prefix=self.redis_prefix,
384
+ tasks=self.tasks,
385
+ worker_id=self.worker_id,
386
+ worker_key=self.worker_key
387
+ )
388
+
389
+ # 创建 EventPool
390
+ consumer_config = self.consumer_config.copy()
391
+ consumer_config['redis_prefix'] = self.redis_prefix
392
+ consumer_config['disable_heartbeat_process'] = True
393
+
394
+ self.event_pool = EventPool(
395
+ self.redis_client,
396
+ self.async_redis_client,
397
+ redis_url=self.redis_url,
398
+ consumer_strategy=self.consumer_strategy,
399
+ consumer_config=consumer_config,
400
+ redis_prefix=self.redis_prefix,
401
+ app=self.minimal_app
402
+ )
403
+
404
+
405
+ # logger.info('准备进入测试流程')
406
+ # await execute_commands(self.event_pool.async_redis_client, num_commands=100000)
407
+
408
+ # 将 EventPool 设置到 MinimalApp
409
+ self.minimal_app.ep = self.event_pool
410
+ self.minimal_app.consumer_manager = self.event_pool.consumer_manager
411
+
412
+ # 初始化 WorkerState
413
+ from jettask.worker.manager import WorkerState
414
+ self.minimal_app.worker_state = WorkerState(
415
+ redis_client=self.redis_client,
416
+ async_redis_client=self.async_redis_client,
417
+ redis_prefix=self.redis_prefix
418
+ )
419
+
420
+ # 初始化路由
421
+ self.event_pool.queues = self.queues
422
+ self.event_pool.init_routing()
423
+
424
+ # 收集任务(按队列分组)
425
+ tasks_by_queue = {}
426
+ for task_name, task in self.tasks.items():
427
+ task_queue = task.queue or self.redis_prefix
428
+ if task_queue in self.queues:
429
+ if task_queue not in tasks_by_queue:
430
+ tasks_by_queue[task_queue] = []
431
+ tasks_by_queue[task_queue].append(task_name)
432
+
433
+ # 收集所有需要监听的任务
434
+ all_tasks = set()
435
+ for queue in self.queues:
436
+ task_names = tasks_by_queue.get(queue, [])
437
+ all_tasks.update(task_names)
438
+
439
+ # 为每个任务创建独立的 asyncio.Queue
440
+ task_event_queues = {task_name: asyncio.Queue() for task_name in all_tasks}
441
+ logger.info(f"Process #{self.process_id}: Created event queues for tasks: {list(task_event_queues.keys())}")
442
+
443
+ # 启动异步事件监听
444
+ listening_task = asyncio.create_task(
445
+ self.event_pool.listening_event(task_event_queues, self.prefetch_multiplier)
446
+ )
447
+
448
+ # 为每个任务创建独立的 TaskExecutor
449
+ for task_name, task_queue in task_event_queues.items():
450
+ executor = TaskExecutor(
451
+ event_queue=task_queue,
452
+ app=self.minimal_app,
453
+ task_name=task_name,
454
+ concurrency=self.concurrency
455
+ )
456
+
457
+ # 初始化执行器
458
+ await executor.initialize()
459
+
460
+ # 启动执行器
461
+ executor_task = asyncio.create_task(executor.run())
462
+ self.executors.append((task_name, executor_task))
463
+ logger.info(f"Process #{self.process_id}: Started TaskExecutor for task '{task_name}'")
464
+
465
+ # 返回所有任务
466
+ return listening_task, [t for _, t in self.executors]
467
+
468
+ async def run(self):
469
+ """运行执行器主循环"""
470
+ logger.info(f"Process #{self.process_id} starting (PID: {os.getpid()})")
471
+
472
+ listening_task = None
473
+ executor_tasks = []
474
+
475
+ try:
476
+ listening_task, executor_tasks = await self.initialize_components()
477
+
478
+ # 等待所有任务完成
479
+ await asyncio.gather(listening_task, *executor_tasks)
480
+
481
+ except asyncio.CancelledError:
482
+ logger.info(f"Process #{self.process_id} cancelled")
483
+ except Exception as e:
484
+ logger.error(f"Process #{self.process_id} error: {e}", exc_info=True)
485
+ finally:
486
+ # 清理
487
+ logger.info(f"Process #{self.process_id} cleaning up")
488
+
489
+ if listening_task and not listening_task.done():
490
+ listening_task.cancel()
491
+
492
+ for _task_name, task in self.executors:
493
+ if not task.done():
494
+ task.cancel()
495
+
496
+ # 等待取消完成
497
+ try:
498
+ all_tasks = [listening_task] + executor_tasks if listening_task else executor_tasks
499
+ await asyncio.wait_for(
500
+ asyncio.gather(*all_tasks, return_exceptions=True),
501
+ timeout=0.5
502
+ )
503
+ except asyncio.TimeoutError:
504
+ pass
505
+
506
+ # 清理 EventPool
507
+ if self.event_pool and hasattr(self.event_pool, 'cleanup'):
508
+ try:
509
+ self.event_pool.cleanup()
510
+ except Exception as e:
511
+ logger.error(f"Error cleaning up EventPool: {e}")
512
+
513
+ # 清理 ConsumerManager
514
+ if self.minimal_app and self.minimal_app.consumer_manager:
515
+ try:
516
+ self.minimal_app.consumer_manager.cleanup()
517
+ except Exception as e:
518
+ logger.error(f"Error cleaning up ConsumerManager: {e}")
519
+
520
+ # 关闭 WorkerStateManager
521
+ if self.minimal_app and self.minimal_app.worker_state_manager:
522
+ try:
523
+ await self.minimal_app.worker_state_manager.stop_listener()
524
+ except Exception as e:
525
+ logger.error(f"Error stopping WorkerStateManager: {e}")
526
+
527
+ # 关闭 Redis 连接
528
+ if self.async_redis_client:
529
+ try:
530
+ await self.async_redis_client.aclose()
531
+ except Exception as e:
532
+ logger.error(f"Error closing async Redis client: {e}")
533
+
534
+ logger.info(f"Process #{self.process_id} stopped")
535
+
536
+
537
+ def subprocess_main(
538
+ process_id: int,
539
+ redis_url: str,
540
+ redis_prefix: str,
541
+ queues: List[str],
542
+ tasks: Dict,
543
+ concurrency: int,
544
+ prefetch_multiplier: int,
545
+ max_connections: int,
546
+ consumer_strategy: str,
547
+ consumer_config: Dict,
548
+ worker_id: str,
549
+ worker_key: str,
550
+ shutdown_event
551
+ ):
552
+ """
553
+ 子进程主函数 - 这是子进程的真正入口点
554
+
555
+ 职责:
556
+ 1. 调用初始化器清理环境
557
+ 2. 创建运行器并执行
558
+ 3. 确保资源正确清理
559
+ """
560
+
561
+ try:
562
+ # 设置进程名
563
+ # multiprocessing.current_process().name = f"JetTask-Worker-{process_id}"
564
+ print(f"Starting subprocess #{process_id} with PID {os.getpid()}", flush=True)
565
+ # ========== 阶段1:清理和初始化 ==========
566
+ initializer = SubprocessInitializer()
567
+ initializer.cleanup_inherited_state()
568
+ print(f"Process #{process_id} cleaned up inherited state")
569
+ initializer.setup_logging(process_id, redis_prefix)
570
+
571
+ # 在清理和配置logging后,创建一个新的logger实例
572
+ global logger
573
+ print("Creating new logger instance in subprocess")
574
+ logger = logging.getLogger()
575
+ logger.info(f"Process #{process_id} starting in PID {os.getpid()}")
576
+ print(f"Process #{process_id} setting up logging")
577
+ # ========== 阶段2:创建运行器 ==========
578
+ runner = SubprocessRunner(
579
+ process_id=process_id,
580
+ redis_url=redis_url,
581
+ redis_prefix=redis_prefix,
582
+ queues=queues,
583
+ tasks=tasks,
584
+ concurrency=concurrency,
585
+ prefetch_multiplier=prefetch_multiplier,
586
+ max_connections=max_connections,
587
+ consumer_strategy=consumer_strategy,
588
+ consumer_config=consumer_config,
589
+ worker_id=worker_id,
590
+ worker_key=worker_key
591
+ )
592
+ print(f"Process #{process_id} created SubprocessRunner")
593
+ # 设置信号处理
594
+ runner.setup_signal_handlers()
595
+ print(f"Process #{process_id} set up signal handlers")
596
+ # 创建 Redis 连接
597
+ runner.create_redis_connections()
598
+ print(f"Process #{process_id} created Redis connections")
599
+ # ========== 阶段3:运行 ==========
600
+ loop = initializer.create_event_loop()
601
+
602
+ try:
603
+ if not shutdown_event.is_set():
604
+ loop.run_until_complete(runner.run())
605
+ except KeyboardInterrupt:
606
+ logger.info(f"Process #{process_id} received interrupt")
607
+ except Exception as e:
608
+ logger.error(f"Process #{process_id} fatal error: {e}", exc_info=True)
609
+ finally:
610
+ # 清理并发锁
611
+ try:
612
+ if worker_id:
613
+ from jettask.utils.rate_limit.concurrency_limiter import ConcurrencyRateLimiter
614
+ task_names = list(tasks.keys()) if tasks else []
615
+ ConcurrencyRateLimiter.cleanup_worker_locks(
616
+ redis_url=redis_url,
617
+ redis_prefix=redis_prefix,
618
+ worker_id=worker_id,
619
+ task_names=task_names
620
+ )
621
+ except Exception as e:
622
+ logger.error(f"Error during lock cleanup: {e}")
623
+
624
+ # 关闭事件循环
625
+ try:
626
+ loop.close()
627
+ except:
628
+ pass
629
+
630
+ logger.info(f"Process #{process_id} exited")
631
+ sys.exit(0)
632
+ except Exception as e:
633
+ import traceback
634
+ traceback.print_exc()
635
+ print(f"Subprocess #{process_id} fatal error during initialization: {e}", file=sys.stderr)
636
+ sys.exit(1)
637
+
638
+ __all__ = ['subprocess_main']