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
jettask/core/app_importer.py
CHANGED
@@ -29,6 +29,8 @@ class AppImporter:
|
|
29
29
|
- "module" - 从 module 自动查找应用实例
|
30
30
|
- "path/to/file.py:app" - 从文件路径导入
|
31
31
|
- "path/to/file.py" - 从文件自动查找
|
32
|
+
- "path/to/dir:app" - 从目录的 __init__.py 导入
|
33
|
+
- "path/to/dir" - 从目录的 __init__.py 自动查找
|
32
34
|
"""
|
33
35
|
# 分离模块路径和应用名称
|
34
36
|
if ':' in import_str:
|
@@ -37,8 +39,8 @@ class AppImporter:
|
|
37
39
|
module_str = import_str
|
38
40
|
app_name = None
|
39
41
|
|
40
|
-
#
|
41
|
-
if '/' in module_str or module_str.endswith('.py'):
|
42
|
+
# 判断是文件路径、目录路径还是模块名
|
43
|
+
if '/' in module_str or module_str.endswith('.py') or os.path.isdir(module_str):
|
42
44
|
module = cls._import_from_file(module_str)
|
43
45
|
else:
|
44
46
|
module = cls._import_from_module(module_str)
|
@@ -63,28 +65,43 @@ class AppImporter:
|
|
63
65
|
|
64
66
|
@classmethod
|
65
67
|
def _import_from_file(cls, file_path: str):
|
66
|
-
"""
|
68
|
+
"""从文件路径或目录导入模块"""
|
67
69
|
path = Path(file_path)
|
68
70
|
|
69
71
|
# 处理相对路径
|
70
72
|
if not path.is_absolute():
|
71
73
|
path = Path.cwd() / path
|
72
74
|
|
73
|
-
#
|
74
|
-
if path.
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
75
|
+
# 判断是目录还是文件
|
76
|
+
if path.is_dir():
|
77
|
+
# 目录:查找 __init__.py
|
78
|
+
init_file = path / '__init__.py'
|
79
|
+
if not init_file.exists():
|
80
|
+
raise ImportError(
|
81
|
+
f"Directory {path} does not contain __init__.py. "
|
82
|
+
f"Cannot import as a Python package."
|
83
|
+
)
|
84
|
+
py_file = init_file
|
85
|
+
module_name = path.name
|
86
|
+
parent_dir = path.parent
|
87
|
+
else:
|
88
|
+
# 文件:处理 .py 后缀
|
89
|
+
if path.suffix == '.py':
|
90
|
+
path = path.with_suffix('')
|
91
|
+
|
92
|
+
# 检查文件是否存在
|
93
|
+
py_file = path.with_suffix('.py')
|
94
|
+
if not py_file.exists():
|
95
|
+
raise ImportError(f"File not found: {py_file}")
|
96
|
+
|
97
|
+
module_name = path.name
|
98
|
+
parent_dir = path.parent
|
81
99
|
|
82
|
-
#
|
83
|
-
sys.path.insert(0, str(
|
100
|
+
# 添加父目录到 sys.path
|
101
|
+
sys.path.insert(0, str(parent_dir))
|
84
102
|
|
85
103
|
try:
|
86
104
|
# 导入模块
|
87
|
-
module_name = path.name
|
88
105
|
spec = importlib.util.spec_from_file_location(
|
89
106
|
module_name,
|
90
107
|
str(py_file)
|
@@ -113,8 +130,8 @@ class AppImporter:
|
|
113
130
|
raise ImportError(f"Cannot create module spec for {py_file}")
|
114
131
|
finally:
|
115
132
|
# 清理 sys.path
|
116
|
-
if str(
|
117
|
-
sys.path.remove(str(
|
133
|
+
if str(parent_dir) in sys.path:
|
134
|
+
sys.path.remove(str(parent_dir))
|
118
135
|
|
119
136
|
@classmethod
|
120
137
|
def _import_from_module(cls, module_str: str):
|
@@ -0,0 +1,532 @@
|
|
1
|
+
"""
|
2
|
+
依赖注入容器 - 统一管理服务实例
|
3
|
+
"""
|
4
|
+
|
5
|
+
import logging
|
6
|
+
from typing import Optional, Dict, Any
|
7
|
+
import redis
|
8
|
+
import redis.asyncio as aioredis
|
9
|
+
|
10
|
+
from jettask.config.config import JetTaskConfig
|
11
|
+
|
12
|
+
logger = logging.getLogger('app')
|
13
|
+
|
14
|
+
|
15
|
+
class ServiceContainer:
|
16
|
+
"""
|
17
|
+
服务容器 - 管理所有服务实例
|
18
|
+
|
19
|
+
职责:
|
20
|
+
1. 统一管理 Redis 客户端(避免重复创建)
|
21
|
+
2. 统一管理消息处理组件(MessageSender, MessageReader, DelayedMessageScanner)
|
22
|
+
3. 统一管理 Worker 相关组件(WorkerStateManager, WorkerScanner)
|
23
|
+
4. 提供单例模式,确保全局唯一
|
24
|
+
|
25
|
+
设计原则:
|
26
|
+
- 延迟初始化:只在需要时创建实例
|
27
|
+
- 单例模式:同一配置只创建一次实例
|
28
|
+
- 依赖注入:组件之间的依赖由容器管理
|
29
|
+
"""
|
30
|
+
|
31
|
+
def __init__(self, config: JetTaskConfig):
|
32
|
+
"""
|
33
|
+
初始化服务容器
|
34
|
+
|
35
|
+
Args:
|
36
|
+
config: JetTask配置对象
|
37
|
+
"""
|
38
|
+
self.config = config
|
39
|
+
|
40
|
+
# Redis客户端缓存(从全局客户端获取,不再管理连接池)
|
41
|
+
self._sync_text_redis: Optional[redis.Redis] = None
|
42
|
+
self._sync_binary_redis: Optional[redis.Redis] = None
|
43
|
+
self._async_text_redis: Optional[aioredis.Redis] = None
|
44
|
+
self._async_binary_redis: Optional[aioredis.Redis] = None
|
45
|
+
|
46
|
+
# 消息处理组件
|
47
|
+
self._message_sender = None
|
48
|
+
self._message_reader = None
|
49
|
+
self._delayed_scanner = None
|
50
|
+
|
51
|
+
# Worker管理组件
|
52
|
+
self._worker_state_manager = None
|
53
|
+
self._worker_scanner = None
|
54
|
+
self._worker_registry = None
|
55
|
+
|
56
|
+
# 队列和任务注册组件
|
57
|
+
self._queue_registry = None
|
58
|
+
self._task_registry_with_redis = None
|
59
|
+
|
60
|
+
# 其他组件
|
61
|
+
self._consumer_manager = None
|
62
|
+
|
63
|
+
# 新架构组件
|
64
|
+
self._queue_manager = None
|
65
|
+
self._queue_router = None
|
66
|
+
self._queue_monitor = None
|
67
|
+
self._task_registry = None
|
68
|
+
self._task_executor = None
|
69
|
+
self._worker_manager = None
|
70
|
+
|
71
|
+
logger.info(f"ServiceContainer initialized with redis_url={config.redis.url}, prefix={config.redis.prefix}")
|
72
|
+
|
73
|
+
# ==================== Redis 客户端管理 ====================
|
74
|
+
|
75
|
+
def _get_sync_text_client(self) -> redis.Redis:
|
76
|
+
"""获取同步文本模式客户端(使用全局客户端实例)"""
|
77
|
+
from jettask.utils.db_connector import get_sync_redis_client
|
78
|
+
return get_sync_redis_client(
|
79
|
+
redis_url=self.config.redis.url,
|
80
|
+
decode_responses=True,
|
81
|
+
max_connections=self.config.redis.max_connections
|
82
|
+
)
|
83
|
+
|
84
|
+
def _get_sync_binary_client(self) -> redis.Redis:
|
85
|
+
"""获取同步二进制模式客户端(使用全局客户端实例)"""
|
86
|
+
from jettask.utils.db_connector import get_sync_redis_client
|
87
|
+
return get_sync_redis_client(
|
88
|
+
redis_url=self.config.redis.url,
|
89
|
+
decode_responses=False,
|
90
|
+
max_connections=self.config.redis.max_connections
|
91
|
+
)
|
92
|
+
|
93
|
+
def _get_async_text_client(self) -> aioredis.Redis:
|
94
|
+
"""获取异步文本模式客户端(使用全局客户端实例)"""
|
95
|
+
from jettask.utils.db_connector import get_async_redis_client
|
96
|
+
return get_async_redis_client(
|
97
|
+
redis_url=self.config.redis.url,
|
98
|
+
decode_responses=True,
|
99
|
+
max_connections=self.config.redis.max_connections
|
100
|
+
)
|
101
|
+
|
102
|
+
def _get_async_binary_client(self) -> aioredis.Redis:
|
103
|
+
"""获取异步二进制模式客户端(使用全局客户端实例)"""
|
104
|
+
from jettask.utils.db_connector import get_async_redis_client
|
105
|
+
return get_async_redis_client(
|
106
|
+
redis_url=self.config.redis.url,
|
107
|
+
decode_responses=False,
|
108
|
+
max_connections=self.config.redis.max_connections
|
109
|
+
)
|
110
|
+
|
111
|
+
def get_redis_client(self, async_mode: bool = False, binary: bool = False) -> Any:
|
112
|
+
"""
|
113
|
+
获取 Redis 客户端(单例模式)
|
114
|
+
|
115
|
+
Args:
|
116
|
+
async_mode: 是否使用异步客户端
|
117
|
+
binary: 是否使用二进制模式(用于Stream操作)
|
118
|
+
|
119
|
+
Returns:
|
120
|
+
Redis客户端实例
|
121
|
+
|
122
|
+
示例:
|
123
|
+
# 同步文本模式(用于普通操作)
|
124
|
+
redis = container.get_redis_client(async_mode=False, binary=False)
|
125
|
+
|
126
|
+
# 异步二进制模式(用于Stream操作)
|
127
|
+
redis = container.get_redis_client(async_mode=True, binary=True)
|
128
|
+
"""
|
129
|
+
if async_mode:
|
130
|
+
if binary:
|
131
|
+
if not self._async_binary_redis:
|
132
|
+
self._async_binary_redis = self._get_async_binary_client()
|
133
|
+
logger.debug("Obtained async binary Redis client from global cache")
|
134
|
+
return self._async_binary_redis
|
135
|
+
else:
|
136
|
+
if not self._async_text_redis:
|
137
|
+
self._async_text_redis = self._get_async_text_client()
|
138
|
+
logger.debug("Obtained async text Redis client from global cache")
|
139
|
+
return self._async_text_redis
|
140
|
+
else:
|
141
|
+
if binary:
|
142
|
+
if not self._sync_binary_redis:
|
143
|
+
self._sync_binary_redis = self._get_sync_binary_client()
|
144
|
+
logger.debug("Obtained sync binary Redis client from global cache")
|
145
|
+
return self._sync_binary_redis
|
146
|
+
else:
|
147
|
+
if not self._sync_text_redis:
|
148
|
+
self._sync_text_redis = self._get_sync_text_client()
|
149
|
+
logger.debug("Obtained sync text Redis client from global cache")
|
150
|
+
return self._sync_text_redis
|
151
|
+
|
152
|
+
# ==================== 消息处理组件 ====================
|
153
|
+
|
154
|
+
def get_message_sender(self):
|
155
|
+
"""
|
156
|
+
获取 MessageSender 实例(单例)
|
157
|
+
|
158
|
+
Returns:
|
159
|
+
MessageSender实例
|
160
|
+
"""
|
161
|
+
if not self._message_sender:
|
162
|
+
from ..messaging.sender import MessageSender
|
163
|
+
|
164
|
+
self._message_sender = MessageSender(
|
165
|
+
async_redis_client=self.get_redis_client(async_mode=True, binary=True),
|
166
|
+
redis_prefix=self.config.redis.prefix
|
167
|
+
)
|
168
|
+
logger.info("Created MessageSender")
|
169
|
+
|
170
|
+
return self._message_sender
|
171
|
+
|
172
|
+
def get_message_reader(self):
|
173
|
+
"""
|
174
|
+
获取 MessageReader 实例(单例)
|
175
|
+
|
176
|
+
Returns:
|
177
|
+
MessageReader实例
|
178
|
+
"""
|
179
|
+
if not self._message_reader:
|
180
|
+
from ..messaging.reader import MessageReader
|
181
|
+
|
182
|
+
self._message_reader = MessageReader(
|
183
|
+
async_redis_client=self.get_redis_client(async_mode=True, binary=False),
|
184
|
+
async_binary_redis_client=self.get_redis_client(async_mode=True, binary=True),
|
185
|
+
redis_prefix=self.config.redis.prefix
|
186
|
+
)
|
187
|
+
logger.info("Created MessageReader")
|
188
|
+
|
189
|
+
return self._message_reader
|
190
|
+
|
191
|
+
def get_delayed_scanner(self):
|
192
|
+
"""
|
193
|
+
获取 DelayedMessageScanner 实例(单例)
|
194
|
+
|
195
|
+
Returns:
|
196
|
+
DelayedMessageScanner实例
|
197
|
+
"""
|
198
|
+
if not self._delayed_scanner:
|
199
|
+
from ..messaging.scanner import DelayedMessageScanner
|
200
|
+
|
201
|
+
self._delayed_scanner = DelayedMessageScanner(
|
202
|
+
async_binary_redis_client=self.get_redis_client(async_mode=True, binary=True),
|
203
|
+
redis_prefix=self.config.redis.prefix,
|
204
|
+
scan_interval=self.config.message.delayed_scan_interval,
|
205
|
+
batch_size=self.config.message.delayed_batch_size
|
206
|
+
)
|
207
|
+
logger.info("Created DelayedMessageScanner")
|
208
|
+
|
209
|
+
return self._delayed_scanner
|
210
|
+
|
211
|
+
# ==================== Worker 管理组件 ====================
|
212
|
+
|
213
|
+
def get_worker_state_manager(self):
|
214
|
+
"""
|
215
|
+
获取 WorkerStateManager 实例(单例)
|
216
|
+
|
217
|
+
Returns:
|
218
|
+
WorkerStateManager实例
|
219
|
+
"""
|
220
|
+
if not self._worker_state_manager:
|
221
|
+
from .worker_state_manager import WorkerStateManager
|
222
|
+
|
223
|
+
self._worker_state_manager = WorkerStateManager(
|
224
|
+
redis_client=self.get_redis_client(async_mode=True, binary=False),
|
225
|
+
redis_prefix=self.config.redis.prefix
|
226
|
+
)
|
227
|
+
logger.info("Created WorkerStateManager")
|
228
|
+
|
229
|
+
return self._worker_state_manager
|
230
|
+
|
231
|
+
def get_worker_scanner(self):
|
232
|
+
"""
|
233
|
+
获取 WorkerScanner 实例(单例)
|
234
|
+
|
235
|
+
Returns:
|
236
|
+
WorkerScanner实例
|
237
|
+
"""
|
238
|
+
if not self._worker_scanner:
|
239
|
+
from jettask.worker.lifecycle import WorkerScanner
|
240
|
+
|
241
|
+
self._worker_scanner = WorkerScanner(
|
242
|
+
sync_redis=self.get_redis_client(async_mode=False, binary=False),
|
243
|
+
async_redis=self.get_redis_client(async_mode=True, binary=False),
|
244
|
+
redis_prefix=self.config.redis.prefix,
|
245
|
+
heartbeat_timeout=self.config.executor.worker_heartbeat_timeout,
|
246
|
+
worker_state_manager=self.get_worker_state_manager()
|
247
|
+
)
|
248
|
+
logger.info("Created WorkerScanner")
|
249
|
+
|
250
|
+
return self._worker_scanner
|
251
|
+
|
252
|
+
# ==================== 注册管理组件 ====================
|
253
|
+
|
254
|
+
def get_worker_registry(self):
|
255
|
+
"""
|
256
|
+
获取 WorkerRegistry 实例(单例)
|
257
|
+
|
258
|
+
Returns:
|
259
|
+
WorkerState实例(之前叫 WorkerRegistry)
|
260
|
+
"""
|
261
|
+
if not self._worker_registry:
|
262
|
+
from jettask.worker.manager import WorkerState
|
263
|
+
|
264
|
+
self._worker_registry = WorkerState(
|
265
|
+
redis_client=self.get_redis_client(async_mode=False, binary=False),
|
266
|
+
async_redis_client=self.get_redis_client(async_mode=True, binary=False),
|
267
|
+
redis_prefix=self.config.redis.prefix
|
268
|
+
)
|
269
|
+
logger.info("Created WorkerState")
|
270
|
+
|
271
|
+
return self._worker_registry
|
272
|
+
|
273
|
+
def get_queue_registry(self):
|
274
|
+
"""
|
275
|
+
获取 QueueRegistry 实例(单例)
|
276
|
+
|
277
|
+
Returns:
|
278
|
+
QueueRegistry实例
|
279
|
+
"""
|
280
|
+
if not self._queue_registry:
|
281
|
+
from jettask.messaging.registry import QueueRegistry
|
282
|
+
|
283
|
+
self._queue_registry = QueueRegistry(
|
284
|
+
redis_client=self.get_redis_client(async_mode=False, binary=False),
|
285
|
+
async_redis_client=self.get_redis_client(async_mode=True, binary=False),
|
286
|
+
redis_prefix=self.config.redis.prefix
|
287
|
+
)
|
288
|
+
logger.info("Created QueueRegistry")
|
289
|
+
|
290
|
+
return self._queue_registry
|
291
|
+
|
292
|
+
def get_task_registry_with_redis(self):
|
293
|
+
"""
|
294
|
+
获取带 Redis 功能的 TaskRegistry 实例(单例)
|
295
|
+
|
296
|
+
Returns:
|
297
|
+
TaskRegistry实例
|
298
|
+
"""
|
299
|
+
if not self._task_registry_with_redis:
|
300
|
+
from jettask.task.task_registry import TaskRegistry
|
301
|
+
|
302
|
+
self._task_registry_with_redis = TaskRegistry(
|
303
|
+
redis_client=self.get_redis_client(async_mode=False, binary=False),
|
304
|
+
async_redis_client=self.get_redis_client(async_mode=True, binary=False),
|
305
|
+
redis_prefix=self.config.redis.prefix
|
306
|
+
)
|
307
|
+
logger.info("Created TaskRegistry with Redis")
|
308
|
+
|
309
|
+
return self._task_registry_with_redis
|
310
|
+
|
311
|
+
# ==================== 其他组件 ====================
|
312
|
+
|
313
|
+
def get_consumer_manager(self, queues=None, app=None):
|
314
|
+
"""
|
315
|
+
获取 ConsumerManager 实例(单例)
|
316
|
+
|
317
|
+
Args:
|
318
|
+
queues: 队列列表(可选,首次调用时需要)
|
319
|
+
app: Jettask应用实例(可选)
|
320
|
+
|
321
|
+
Returns:
|
322
|
+
ConsumerManager实例
|
323
|
+
"""
|
324
|
+
if not self._consumer_manager:
|
325
|
+
from .consumer_manager import ConsumerManager, ConsumerStrategy
|
326
|
+
|
327
|
+
if queues is None:
|
328
|
+
queues = []
|
329
|
+
|
330
|
+
strategy = ConsumerStrategy(self.config.consumer.strategy)
|
331
|
+
|
332
|
+
consumer_config = {
|
333
|
+
'queues': queues,
|
334
|
+
'redis_prefix': self.config.redis.prefix,
|
335
|
+
'redis_url': self.config.redis.url,
|
336
|
+
'heartbeat_interval': self.config.consumer.heartbeat_interval,
|
337
|
+
'heartbeat_timeout': self.config.consumer.heartbeat_timeout,
|
338
|
+
'reuse_timeout': self.config.consumer.reuse_timeout
|
339
|
+
}
|
340
|
+
|
341
|
+
self._consumer_manager = ConsumerManager(
|
342
|
+
redis_client=self.get_redis_client(async_mode=False, binary=False),
|
343
|
+
strategy=strategy,
|
344
|
+
config=consumer_config,
|
345
|
+
app=app
|
346
|
+
)
|
347
|
+
logger.info(f"Created ConsumerManager with strategy={strategy}")
|
348
|
+
|
349
|
+
return self._consumer_manager
|
350
|
+
|
351
|
+
# ==================== 新架构组件 ====================
|
352
|
+
|
353
|
+
def get_queue_manager(self):
|
354
|
+
"""
|
355
|
+
获取 QueueManager 实例(单例)
|
356
|
+
|
357
|
+
Returns:
|
358
|
+
QueueManager实例
|
359
|
+
"""
|
360
|
+
if not self._queue_manager:
|
361
|
+
from jettask.messaging import QueueManager
|
362
|
+
|
363
|
+
self._queue_manager = QueueManager(
|
364
|
+
message_sender=self.get_message_sender(),
|
365
|
+
message_reader=self.get_message_reader(),
|
366
|
+
redis_client=self.get_redis_client(async_mode=True, binary=False),
|
367
|
+
redis_prefix=self.config.redis.prefix
|
368
|
+
)
|
369
|
+
logger.info("Created QueueManager")
|
370
|
+
|
371
|
+
return self._queue_manager
|
372
|
+
|
373
|
+
def get_queue_router(self):
|
374
|
+
"""
|
375
|
+
获取 QueueRouter 实例(单例)
|
376
|
+
|
377
|
+
Returns:
|
378
|
+
QueueRouter实例
|
379
|
+
"""
|
380
|
+
if not self._queue_router:
|
381
|
+
from jettask.messaging import QueueRouter
|
382
|
+
|
383
|
+
self._queue_router = QueueRouter()
|
384
|
+
logger.info("Created QueueRouter")
|
385
|
+
|
386
|
+
return self._queue_router
|
387
|
+
|
388
|
+
def get_queue_monitor(self):
|
389
|
+
"""
|
390
|
+
获取 QueueMonitor 实例(单例)
|
391
|
+
|
392
|
+
Returns:
|
393
|
+
QueueMonitor实例
|
394
|
+
"""
|
395
|
+
if not self._queue_monitor:
|
396
|
+
from jettask.messaging import QueueMonitor
|
397
|
+
|
398
|
+
self._queue_monitor = QueueMonitor(
|
399
|
+
queue_manager=self.get_queue_manager()
|
400
|
+
)
|
401
|
+
logger.info("Created QueueMonitor")
|
402
|
+
|
403
|
+
return self._queue_monitor
|
404
|
+
|
405
|
+
def get_task_registry(self):
|
406
|
+
"""
|
407
|
+
获取 TaskRegistry 实例(单例)
|
408
|
+
|
409
|
+
Returns:
|
410
|
+
TaskRegistry实例
|
411
|
+
"""
|
412
|
+
if not self._task_registry:
|
413
|
+
from ..task import TaskRegistry
|
414
|
+
|
415
|
+
self._task_registry = TaskRegistry()
|
416
|
+
logger.info("Created TaskRegistry")
|
417
|
+
|
418
|
+
return self._task_registry
|
419
|
+
|
420
|
+
def get_task_executor(self, data_access=None, retry_manager=None):
|
421
|
+
"""
|
422
|
+
获取 TaskExecutor 实例(单例)
|
423
|
+
|
424
|
+
Args:
|
425
|
+
data_access: 数据访问层(可选)
|
426
|
+
retry_manager: 重试管理器(可选)
|
427
|
+
|
428
|
+
Returns:
|
429
|
+
TaskExecutor实例
|
430
|
+
"""
|
431
|
+
if not self._task_executor:
|
432
|
+
from ..task import TaskExecutor
|
433
|
+
|
434
|
+
self._task_executor = TaskExecutor(
|
435
|
+
task_registry=self.get_task_registry(),
|
436
|
+
data_access=data_access,
|
437
|
+
retry_manager=retry_manager
|
438
|
+
)
|
439
|
+
logger.info("Created TaskExecutor")
|
440
|
+
|
441
|
+
return self._task_executor
|
442
|
+
|
443
|
+
|
444
|
+
def get_worker_manager(self, strategy=None, worker_name=None):
|
445
|
+
"""
|
446
|
+
获取 WorkerManager 实例(单例)
|
447
|
+
|
448
|
+
Args:
|
449
|
+
strategy: Worker策略(可选)
|
450
|
+
worker_name: Worker名称(FIXED策略时使用)
|
451
|
+
|
452
|
+
Returns:
|
453
|
+
WorkerManager实例
|
454
|
+
"""
|
455
|
+
if not self._worker_manager:
|
456
|
+
from ..worker import WorkerManager, WorkerStrategy
|
457
|
+
|
458
|
+
# 使用配置中的策略,如果没有则默认DYNAMIC
|
459
|
+
if strategy is None:
|
460
|
+
strategy_str = self.config.consumer.strategy
|
461
|
+
if strategy_str == "fixed":
|
462
|
+
strategy = WorkerStrategy.FIXED
|
463
|
+
elif strategy_str == "pod":
|
464
|
+
strategy = WorkerStrategy.POD
|
465
|
+
else:
|
466
|
+
strategy = WorkerStrategy.DYNAMIC
|
467
|
+
|
468
|
+
self._worker_manager = WorkerManager(
|
469
|
+
redis_client=self.get_redis_client(async_mode=True, binary=False),
|
470
|
+
redis_prefix=self.config.redis.prefix,
|
471
|
+
strategy=strategy,
|
472
|
+
worker_name=worker_name,
|
473
|
+
heartbeat_interval=self.config.consumer.heartbeat_interval,
|
474
|
+
heartbeat_timeout=self.config.consumer.heartbeat_timeout
|
475
|
+
)
|
476
|
+
logger.info(f"Created WorkerManager with strategy={strategy}")
|
477
|
+
|
478
|
+
return self._worker_manager
|
479
|
+
|
480
|
+
# ==================== 清理资源 ====================
|
481
|
+
|
482
|
+
async def cleanup(self):
|
483
|
+
"""
|
484
|
+
清理所有资源(异步)
|
485
|
+
|
486
|
+
应在应用关闭时调用
|
487
|
+
"""
|
488
|
+
logger.info("Cleaning up ServiceContainer resources...")
|
489
|
+
|
490
|
+
# 停止延迟扫描器
|
491
|
+
if self._delayed_scanner:
|
492
|
+
await self._delayed_scanner.stop()
|
493
|
+
logger.debug("Stopped DelayedMessageScanner")
|
494
|
+
|
495
|
+
# 停止 WorkerStateManager
|
496
|
+
if self._worker_state_manager:
|
497
|
+
await self._worker_state_manager.stop_listener()
|
498
|
+
logger.debug("Stopped WorkerStateManager")
|
499
|
+
|
500
|
+
# 关闭异步Redis连接
|
501
|
+
if self._async_text_redis:
|
502
|
+
await self._async_text_redis.aclose()
|
503
|
+
logger.debug("Closed async text Redis")
|
504
|
+
|
505
|
+
if self._async_binary_redis:
|
506
|
+
await self._async_binary_redis.aclose()
|
507
|
+
logger.debug("Closed async binary Redis")
|
508
|
+
|
509
|
+
# 关闭同步Redis连接
|
510
|
+
if self._sync_text_redis:
|
511
|
+
self._sync_text_redis.close()
|
512
|
+
logger.debug("Closed sync text Redis")
|
513
|
+
|
514
|
+
if self._sync_binary_redis:
|
515
|
+
self._sync_binary_redis.close()
|
516
|
+
logger.debug("Closed sync binary Redis")
|
517
|
+
|
518
|
+
logger.info("ServiceContainer cleanup completed")
|
519
|
+
|
520
|
+
def __del__(self):
|
521
|
+
"""析构函数:确保同步资源被释放"""
|
522
|
+
if self._sync_text_redis:
|
523
|
+
try:
|
524
|
+
self._sync_text_redis.close()
|
525
|
+
except:
|
526
|
+
pass
|
527
|
+
|
528
|
+
if self._sync_binary_redis:
|
529
|
+
try:
|
530
|
+
self._sync_binary_redis.close()
|
531
|
+
except:
|
532
|
+
pass
|
jettask/core/task.py
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
from ..utils.serializer import dumps_str, loads_str
|
2
|
-
import time
|
3
2
|
import inspect
|
4
|
-
import asyncio
|
5
3
|
from dataclasses import dataclass
|
6
4
|
from typing import Any, Optional, TYPE_CHECKING, get_type_hints, Union
|
7
5
|
|
@@ -9,7 +7,6 @@ if TYPE_CHECKING:
|
|
9
7
|
from .app import Jettask
|
10
8
|
|
11
9
|
from .context import TaskContext
|
12
|
-
from .enums import TaskStatus
|
13
10
|
|
14
11
|
|
15
12
|
@dataclass
|
@@ -148,4 +145,4 @@ class Task:
|
|
148
145
|
|
149
146
|
async def _get_pending(self, queue: str):
|
150
147
|
return await self._app.ep.read_pending(queue, queue, asyncio=True)
|
151
|
-
|
148
|
+
|
@@ -150,7 +150,7 @@ class UnifiedManagerBase(ABC):
|
|
150
150
|
pg_url = data.get('pg_url', '')
|
151
151
|
|
152
152
|
if not redis_url or not pg_url:
|
153
|
-
logger.warning(f"跳过命名空间 {data['name']}:缺少 Redis 或 PostgreSQL 配置")
|
153
|
+
# logger.warning(f"跳过命名空间 {data['name']}:缺少 Redis 或 PostgreSQL 配置")
|
154
154
|
continue
|
155
155
|
|
156
156
|
redis_config = {'url': redis_url}
|
@@ -185,7 +185,7 @@ class UnifiedManagerBase(ABC):
|
|
185
185
|
pg_url = data.get('pg_url', '')
|
186
186
|
|
187
187
|
if not redis_url or not pg_url:
|
188
|
-
logger.warning(f"跳过命名空间 {data['name']}:缺少 Redis 或 PostgreSQL 配置")
|
188
|
+
# logger.warning(f"跳过命名空间 {data['name']}:缺少 Redis 或 PostgreSQL 配置")
|
189
189
|
continue
|
190
190
|
|
191
191
|
redis_config = {'url': redis_url}
|
@@ -0,0 +1,38 @@
|
|
1
|
+
"""
|
2
|
+
执行器模块
|
3
|
+
|
4
|
+
提供任务执行的核心功能,支持单进程和多进程两种模式。
|
5
|
+
|
6
|
+
主要组件:
|
7
|
+
- TaskExecutor: 任务执行器(新)
|
8
|
+
- ProcessOrchestrator: 多进程编排器
|
9
|
+
- ExecutorCore: 核心执行逻辑
|
10
|
+
"""
|
11
|
+
|
12
|
+
from .core import ExecutorCore
|
13
|
+
from .orchestrator import ProcessConfig, ProcessOrchestrator
|
14
|
+
from .task_executor import TaskExecutor
|
15
|
+
|
16
|
+
# 保留 UnifiedExecutor 作为兼容(已废弃)
|
17
|
+
try:
|
18
|
+
from .executor import UnifiedExecutor
|
19
|
+
except ImportError:
|
20
|
+
# 如果导入失败,提供一个废弃提示
|
21
|
+
class UnifiedExecutor:
|
22
|
+
def __init__(self, *args, **kwargs):
|
23
|
+
raise DeprecationWarning(
|
24
|
+
"UnifiedExecutor is deprecated. "
|
25
|
+
"Use TaskExecutor for single task execution, "
|
26
|
+
"or ProcessOrchestrator for multi-process management."
|
27
|
+
)
|
28
|
+
|
29
|
+
__all__ = [
|
30
|
+
# 新的推荐类
|
31
|
+
'TaskExecutor',
|
32
|
+
'ProcessOrchestrator',
|
33
|
+
'ProcessConfig',
|
34
|
+
'ExecutorCore',
|
35
|
+
|
36
|
+
# 废弃但保留兼容性
|
37
|
+
'UnifiedExecutor',
|
38
|
+
]
|