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.
- jettask/__init__.py +12 -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.23.dist-info}/METADATA +2 -71
- jettask-0.2.23.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.23.dist-info}/WHEEL +0 -0
- {jettask-0.2.19.dist-info → jettask-0.2.23.dist-info}/entry_points.txt +0 -0
- {jettask-0.2.19.dist-info → jettask-0.2.23.dist-info}/licenses/LICENSE +0 -0
- {jettask-0.2.19.dist-info → jettask-0.2.23.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']
|