jettask 0.2.23__py3-none-any.whl → 0.2.24__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 (110) hide show
  1. jettask/__init__.py +2 -0
  2. jettask/cli.py +12 -8
  3. jettask/config/lua_scripts.py +37 -0
  4. jettask/config/nacos_config.py +1 -1
  5. jettask/core/app.py +313 -340
  6. jettask/core/container.py +4 -4
  7. jettask/{persistence → core}/namespace.py +93 -27
  8. jettask/core/task.py +16 -9
  9. jettask/core/unified_manager_base.py +136 -26
  10. jettask/db/__init__.py +67 -0
  11. jettask/db/base.py +137 -0
  12. jettask/{utils/db_connector.py → db/connector.py} +130 -26
  13. jettask/db/models/__init__.py +16 -0
  14. jettask/db/models/scheduled_task.py +196 -0
  15. jettask/db/models/task.py +77 -0
  16. jettask/db/models/task_run.py +85 -0
  17. jettask/executor/__init__.py +0 -15
  18. jettask/executor/core.py +76 -31
  19. jettask/executor/process_entry.py +29 -114
  20. jettask/executor/task_executor.py +4 -0
  21. jettask/messaging/event_pool.py +928 -685
  22. jettask/messaging/scanner.py +30 -0
  23. jettask/persistence/__init__.py +28 -103
  24. jettask/persistence/buffer.py +170 -0
  25. jettask/persistence/consumer.py +330 -249
  26. jettask/persistence/manager.py +304 -0
  27. jettask/persistence/persistence.py +391 -0
  28. jettask/scheduler/__init__.py +15 -3
  29. jettask/scheduler/{task_crud.py → database.py} +61 -57
  30. jettask/scheduler/loader.py +2 -2
  31. jettask/scheduler/{scheduler_coordinator.py → manager.py} +23 -6
  32. jettask/scheduler/models.py +14 -10
  33. jettask/scheduler/schedule.py +166 -0
  34. jettask/scheduler/scheduler.py +12 -11
  35. jettask/schemas/__init__.py +50 -1
  36. jettask/schemas/backlog.py +43 -6
  37. jettask/schemas/namespace.py +70 -19
  38. jettask/schemas/queue.py +19 -3
  39. jettask/schemas/responses.py +493 -0
  40. jettask/task/__init__.py +0 -2
  41. jettask/task/router.py +3 -0
  42. jettask/test_connection_monitor.py +1 -1
  43. jettask/utils/__init__.py +7 -5
  44. jettask/utils/db_init.py +8 -4
  45. jettask/utils/namespace_dep.py +167 -0
  46. jettask/utils/queue_matcher.py +186 -0
  47. jettask/utils/rate_limit/concurrency_limiter.py +7 -1
  48. jettask/utils/stream_backlog.py +1 -1
  49. jettask/webui/__init__.py +0 -1
  50. jettask/webui/api/__init__.py +4 -4
  51. jettask/webui/api/alerts.py +806 -71
  52. jettask/webui/api/example_refactored.py +400 -0
  53. jettask/webui/api/namespaces.py +390 -45
  54. jettask/webui/api/overview.py +300 -54
  55. jettask/webui/api/queues.py +971 -267
  56. jettask/webui/api/scheduled.py +1249 -56
  57. jettask/webui/api/settings.py +129 -7
  58. jettask/webui/api/workers.py +442 -0
  59. jettask/webui/app.py +46 -2329
  60. jettask/webui/middleware/__init__.py +6 -0
  61. jettask/webui/middleware/namespace_middleware.py +135 -0
  62. jettask/webui/services/__init__.py +146 -0
  63. jettask/webui/services/heartbeat_service.py +251 -0
  64. jettask/webui/services/overview_service.py +60 -51
  65. jettask/webui/services/queue_monitor_service.py +426 -0
  66. jettask/webui/services/redis_monitor_service.py +87 -0
  67. jettask/webui/services/settings_service.py +174 -111
  68. jettask/webui/services/task_monitor_service.py +222 -0
  69. jettask/webui/services/timeline_pg_service.py +452 -0
  70. jettask/webui/services/timeline_service.py +189 -0
  71. jettask/webui/services/worker_monitor_service.py +467 -0
  72. jettask/webui/utils/__init__.py +11 -0
  73. jettask/webui/utils/time_utils.py +122 -0
  74. jettask/worker/lifecycle.py +8 -2
  75. {jettask-0.2.23.dist-info → jettask-0.2.24.dist-info}/METADATA +1 -1
  76. jettask-0.2.24.dist-info/RECORD +142 -0
  77. jettask/executor/executor.py +0 -338
  78. jettask/persistence/backlog_monitor.py +0 -567
  79. jettask/persistence/base.py +0 -2334
  80. jettask/persistence/db_manager.py +0 -516
  81. jettask/persistence/maintenance.py +0 -81
  82. jettask/persistence/message_consumer.py +0 -259
  83. jettask/persistence/models.py +0 -49
  84. jettask/persistence/offline_recovery.py +0 -196
  85. jettask/persistence/queue_discovery.py +0 -215
  86. jettask/persistence/task_persistence.py +0 -218
  87. jettask/persistence/task_updater.py +0 -583
  88. jettask/scheduler/add_execution_count.sql +0 -11
  89. jettask/scheduler/add_priority_field.sql +0 -26
  90. jettask/scheduler/add_scheduler_id.sql +0 -25
  91. jettask/scheduler/add_scheduler_id_index.sql +0 -10
  92. jettask/scheduler/make_scheduler_id_required.sql +0 -28
  93. jettask/scheduler/migrate_interval_seconds.sql +0 -9
  94. jettask/scheduler/performance_optimization.sql +0 -45
  95. jettask/scheduler/run_scheduler.py +0 -186
  96. jettask/scheduler/schema.sql +0 -84
  97. jettask/task/task_executor.py +0 -318
  98. jettask/webui/api/analytics.py +0 -323
  99. jettask/webui/config.py +0 -90
  100. jettask/webui/models/__init__.py +0 -3
  101. jettask/webui/models/namespace.py +0 -63
  102. jettask/webui/namespace_manager/__init__.py +0 -10
  103. jettask/webui/namespace_manager/multi.py +0 -593
  104. jettask/webui/namespace_manager/unified.py +0 -193
  105. jettask/webui/run.py +0 -46
  106. jettask-0.2.23.dist-info/RECORD +0 -145
  107. {jettask-0.2.23.dist-info → jettask-0.2.24.dist-info}/WHEEL +0 -0
  108. {jettask-0.2.23.dist-info → jettask-0.2.24.dist-info}/entry_points.txt +0 -0
  109. {jettask-0.2.23.dist-info → jettask-0.2.24.dist-info}/licenses/LICENSE +0 -0
  110. {jettask-0.2.23.dist-info → jettask-0.2.24.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,77 @@
1
+ """
2
+ Task 模型
3
+
4
+ 对应 tasks 表,用于存储任务消息(实际表结构)
5
+ """
6
+ from sqlalchemy import Column, String, Integer, Text, TIMESTAMP, Index
7
+ from sqlalchemy.dialects.postgresql import JSONB
8
+ from datetime import datetime
9
+ from typing import Optional, Dict, Any
10
+
11
+ from ..base import Base
12
+
13
+
14
+ class Task(Base):
15
+ """
16
+ 任务消息表
17
+
18
+ 存储发送到队列的任务消息
19
+ """
20
+ __tablename__ = 'tasks'
21
+
22
+ # 主键 - Redis Stream ID
23
+ stream_id = Column(Text, primary_key=True, comment='Redis Stream 事件ID')
24
+
25
+ # 队列和命名空间
26
+ queue = Column(Text, nullable=False, comment='队列名称')
27
+ namespace = Column(Text, nullable=False, comment='命名空间')
28
+
29
+ # 定时任务关联
30
+ scheduled_task_id = Column(Text, nullable=True, comment='关联的定时任务ID')
31
+
32
+ # 消息数据
33
+ payload = Column(JSONB, nullable=False, comment='消息载荷(任务数据)')
34
+
35
+ # 优先级
36
+ priority = Column(Integer, nullable=True, comment='优先级(数字越小优先级越高)')
37
+
38
+ # 时间戳
39
+ created_at = Column(
40
+ TIMESTAMP(timezone=True),
41
+ nullable=False,
42
+ default=datetime.utcnow,
43
+ comment='创建时间'
44
+ )
45
+
46
+ # 来源
47
+ source = Column(Text, nullable=False, comment='消息来源(如 scheduler, manual, api)')
48
+
49
+ # 元数据
50
+ task_metadata = Column('metadata', JSONB, nullable=True, comment='任务元数据')
51
+
52
+ # 索引
53
+ __table_args__ = (
54
+ Index('idx_tasks_queue', 'queue'),
55
+ Index('idx_tasks_namespace', 'namespace'),
56
+ Index('idx_tasks_queue_namespace', 'queue', 'namespace'),
57
+ Index('idx_tasks_scheduled_task_id', 'scheduled_task_id'),
58
+ Index('idx_tasks_created_at', 'created_at'),
59
+ Index('idx_tasks_source', 'source'),
60
+ )
61
+
62
+ def to_dict(self) -> Dict[str, Any]:
63
+ """转换为字典"""
64
+ return {
65
+ 'stream_id': self.stream_id,
66
+ 'queue': self.queue,
67
+ 'namespace': self.namespace,
68
+ 'scheduled_task_id': self.scheduled_task_id,
69
+ 'payload': self.payload,
70
+ 'priority': self.priority,
71
+ 'created_at': self.created_at.isoformat() if self.created_at else None,
72
+ 'source': self.source,
73
+ 'metadata': self.task_metadata,
74
+ }
75
+
76
+ def __repr__(self) -> str:
77
+ return f"<Task(stream_id='{self.stream_id}', queue='{self.queue}', namespace='{self.namespace}')>"
@@ -0,0 +1,85 @@
1
+ """
2
+ TaskRun 模型
3
+
4
+ 对应 task_runs 表,用于存储任务执行记录
5
+ """
6
+ from sqlalchemy import Column, String, Integer, Text, TIMESTAMP, Float, Index, ForeignKey
7
+ from sqlalchemy.dialects.postgresql import JSONB
8
+ from datetime import datetime
9
+ from typing import Optional, Dict, Any
10
+
11
+ from ..base import Base
12
+
13
+
14
+ class TaskRun(Base):
15
+ """
16
+ 任务执行记录表
17
+
18
+ 存储每次任务执行的状态、结果和执行时间等信息
19
+ """
20
+ __tablename__ = 'task_runs'
21
+
22
+ # 主键 - Redis Stream ID
23
+ stream_id = Column(Text, primary_key=True, comment='Redis Stream 事件ID,关联到 tasks 表')
24
+
25
+ # 执行状态
26
+ status = Column(String(50), nullable=True, comment='任务状态 (pending/running/success/failed/retrying)')
27
+
28
+ # 执行结果
29
+ result = Column(JSONB, nullable=True, comment='任务执行结果')
30
+ error = Column(Text, nullable=True, comment='错误信息(如果失败)')
31
+
32
+ # 执行时间
33
+ started_at = Column(Float, nullable=True, comment='开始执行时间(Unix 时间戳)')
34
+ completed_at = Column(Float, nullable=True, comment='完成时间(Unix 时间戳)')
35
+
36
+ # 重试次数
37
+ retries = Column(Integer, nullable=True, default=0, comment='重试次数')
38
+
39
+ # 执行时长(秒)
40
+ duration = Column(Float, nullable=True, comment='执行时长(秒)')
41
+
42
+ # 消费者信息
43
+ consumer = Column(Text, nullable=True, comment='执行该任务的消费者ID')
44
+
45
+ # 记录创建和更新时间
46
+ created_at = Column(
47
+ TIMESTAMP(timezone=True),
48
+ nullable=False,
49
+ default=datetime.utcnow,
50
+ comment='记录创建时间'
51
+ )
52
+ updated_at = Column(
53
+ TIMESTAMP(timezone=True),
54
+ nullable=False,
55
+ default=datetime.utcnow,
56
+ onupdate=datetime.utcnow,
57
+ comment='记录更新时间'
58
+ )
59
+
60
+ # 索引
61
+ __table_args__ = (
62
+ Index('idx_task_runs_status', 'status'),
63
+ Index('idx_task_runs_started_at', 'started_at'),
64
+ Index('idx_task_runs_completed_at', 'completed_at'),
65
+ Index('idx_task_runs_created_at', 'created_at'),
66
+ )
67
+
68
+ def to_dict(self) -> Dict[str, Any]:
69
+ """转换为字典"""
70
+ return {
71
+ 'stream_id': self.stream_id,
72
+ 'status': self.status,
73
+ 'result': self.result,
74
+ 'error': self.error,
75
+ 'started_at': self.started_at,
76
+ 'completed_at': self.completed_at,
77
+ 'retries': self.retries,
78
+ 'duration': self.duration,
79
+ 'consumer': self.consumer,
80
+ 'created_at': self.created_at.isoformat() if self.created_at else None,
81
+ 'updated_at': self.updated_at.isoformat() if self.updated_at else None,
82
+ }
83
+
84
+ def __repr__(self) -> str:
85
+ return f"<TaskRun(stream_id='{self.stream_id}', status='{self.status}')>"
@@ -13,18 +13,6 @@ from .core import ExecutorCore
13
13
  from .orchestrator import ProcessConfig, ProcessOrchestrator
14
14
  from .task_executor import TaskExecutor
15
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
16
 
29
17
  __all__ = [
30
18
  # 新的推荐类
@@ -32,7 +20,4 @@ __all__ = [
32
20
  'ProcessOrchestrator',
33
21
  'ProcessConfig',
34
22
  'ExecutorCore',
35
-
36
- # 废弃但保留兼容性
37
- 'UnifiedExecutor',
38
23
  ]
jettask/executor/core.py CHANGED
@@ -14,7 +14,7 @@ import logging
14
14
  import time
15
15
  import os
16
16
  from enum import Enum
17
- from typing import Dict, Optional
17
+ from typing import Dict, Optional, TYPE_CHECKING
18
18
  from collections import defaultdict, deque
19
19
 
20
20
  from ..utils.traceback_filter import filter_framework_traceback
@@ -23,6 +23,9 @@ from ..utils.serializer import dumps_str
23
23
  from ..exceptions import RetryableError
24
24
  from ..core.enums import TaskStatus
25
25
  from ..utils.rate_limit.limiter import RateLimiterManager, ConcurrencyRateLimiter
26
+ from ..config.lua_scripts import LUA_SCRIPT_BATCH_SEND_EVENT
27
+ if TYPE_CHECKING:
28
+ from ..core.app import Jettask
26
29
 
27
30
  logger = logging.getLogger('app')
28
31
 
@@ -61,7 +64,7 @@ class ExecutorCore:
61
64
  4. 统计收集
62
65
  """
63
66
 
64
- def __init__(self, app, task_name: str, concurrency: int = 100):
67
+ def __init__(self, app: "Jettask", task_name: str, concurrency: int = 100):
65
68
  """
66
69
  初始化执行器核心
67
70
 
@@ -218,39 +221,37 @@ class ExecutorCore:
218
221
  operations_count += 1
219
222
 
220
223
  self.pending_acks.clear()
221
-
222
224
  # 2. 处理任务信息更新
223
225
  task_change_events = []
224
226
  if self.task_info_updates:
225
227
  for event_key, updates in self.task_info_updates.items():
226
- key = f"{self.prefix}:TASK:{event_key}".encode()
228
+ if event_key.endswith('consumer._handle_status_update') or \
229
+ event_key.endswith('consumer._handle_persist_task'):
230
+ continue # 跳过无效的event_key
231
+ key = f"{self.prefix}:TASK:{event_key}"
232
+ key_bytes = key.encode()
227
233
  if updates:
228
234
  encoded_updates = {k.encode(): v.encode() if isinstance(v, str) else v
229
235
  for k, v in updates.items()}
230
- pipeline.hset(key, mapping=encoded_updates)
231
- pipeline.expire(key, 3600)
236
+ pipeline.hset(key_bytes, mapping=encoded_updates)
237
+ pipeline.expire(key_bytes, 3600)
232
238
  operations_count += 2
239
+ task_change_events.append(key)
233
240
 
234
- full_task_id = f"{self.prefix}:TASK:{event_key}"
235
- task_change_events.append(full_task_id)
236
-
237
- # 发送变更事件
238
- change_stream_key = f"{self.prefix}:TASK_CHANGES".encode()
239
- for task_id in task_change_events:
240
- change_data = {b'id': task_id.encode() if isinstance(task_id, str) else task_id}
241
- pipeline.xadd(change_stream_key, change_data, maxlen=1000000)
242
- operations_count += 1
241
+ # 发送变更事件(使用 Lua 脚本确保 offset 更新和 REGISTRY 注册)
242
+ if task_change_events:
243
+ self._send_task_changes_with_offset(task_change_events, pipeline)
243
244
 
244
245
  self.task_info_updates.clear()
245
246
 
246
247
  # 3. 处理统计信息
247
248
  if hasattr(self, 'stats_updates') and self.stats_updates:
248
- for stat_op in self.stats_updates:
249
- if 'queue' in stat_op and 'field' in stat_op:
250
- stats_key = f"{self.prefix}:STATS:{stat_op['queue']}".encode()
251
- field = stat_op['field'].encode() if isinstance(stat_op['field'], str) else stat_op['field']
252
- pipeline.hincrby(stats_key, field, stat_op.get('value', 1))
253
- operations_count += 1
249
+ # for stat_op in self.stats_updates:
250
+ # if 'queue' in stat_op and 'field' in stat_op:
251
+ # stats_key = f"{self.prefix}:STATS:{stat_op['queue']}".encode()
252
+ # field = stat_op['field'].encode() if isinstance(stat_op['field'], str) else stat_op['field']
253
+ # pipeline.hincrby(stats_key, field, stat_op.get('value', 1))
254
+ # operations_count += 1
254
255
  self.stats_updates.clear()
255
256
 
256
257
  # 执行pipeline
@@ -427,10 +428,20 @@ class ExecutorCore:
427
428
  if current_retry > 0:
428
429
  logger.debug(f"Retry attempt {current_retry}/{max_retries} for task {event_id}")
429
430
 
431
+ # 不过滤 __ 开头的参数,因为它们需要在 _inject_dependencies 中被提取
432
+ # (如 __scheduled_task_id, __queue 等)
433
+ # 只过滤单下划线开头的参数
430
434
  clean_kwargs = {k: v for k, v in kwargs_inner.items()
431
- if not k.startswith('_') and not k.startswith('__')}
432
-
433
- task_result = task(event_id, event_data['trigger_time'], *args, **clean_kwargs)
435
+ if not k.startswith('_') or k.startswith('__')}
436
+
437
+ print(f'{queue=} {args=} {kwargs_inner=}')
438
+ task_result = task(
439
+ event_id,
440
+ event_data.get('trigger_time'),
441
+ queue,
442
+ *args,
443
+ **clean_kwargs
444
+ )
434
445
  if asyncio.iscoroutine(task_result):
435
446
  ret = await task_result
436
447
  else:
@@ -445,13 +456,14 @@ class ExecutorCore:
445
456
  if asyncio.iscoroutine(result):
446
457
  await result
447
458
 
448
- # 任务成功,ACK消息
449
- offset = self._extract_offset(event_data)
450
- # await self.app.ep.async_redis_client.xack(
451
- # self._get_prefixed_queue_cached(queue), group_name, event_id
452
- # )
453
- # await self.app.ep.async_redis_client.close()
454
- await self._quick_ack(queue, event_id, group_name, offset)
459
+ # 任务成功,ACK消息(检查auto_ack配置)
460
+ task_config = self.app.get_task_config(task_name)
461
+ auto_ack = task_config.get('auto_ack', True) if task_config else True
462
+
463
+ if auto_ack:
464
+ offset = self._extract_offset(event_data)
465
+ await self._quick_ack(queue, event_id, group_name, offset)
466
+
455
467
  break
456
468
 
457
469
  except SystemExit:
@@ -599,6 +611,39 @@ class ExecutorCore:
599
611
  pass
600
612
  return None
601
613
 
614
+ def _send_task_changes_with_offset(self, task_ids: list, pipeline):
615
+ """发送 TASK_CHANGES 消息并自动更新 offset
616
+
617
+ 使用 Lua 脚本确保:
618
+ 1. 自动递增 offset
619
+ 2. 注册队列到 REGISTRY:QUEUES
620
+ 3. 消息中包含 offset 字段
621
+
622
+ Args:
623
+ task_ids: 任务 ID 列表
624
+ pipeline: Redis pipeline
625
+ """
626
+ from jettask.utils.serializer import dumps_str
627
+
628
+ # 准备Lua脚本参数
629
+ stream_key = f"{self.prefix}:QUEUE:TASK_CHANGES".encode()
630
+ lua_args = [self.prefix.encode() if isinstance(self.prefix, str) else self.prefix]
631
+
632
+ # 为每个 task_id 构建消息
633
+ for task_id in task_ids:
634
+ message_data = {'kwargs': {'task_id': task_id}}
635
+ data = dumps_str(message_data)
636
+ lua_args.append(data)
637
+
638
+ # 使用 pipeline 执行 Lua 脚本
639
+ pipeline.eval(
640
+ LUA_SCRIPT_BATCH_SEND_EVENT,
641
+ 1, # 1个KEY
642
+ stream_key, # KEY[1]: stream key
643
+ *lua_args # ARGV: prefix, data1, data2, ...
644
+ )
645
+ logger.debug(f"已添加 {len(task_ids)} 条 TASK_CHANGES 消息到 pipeline")
646
+
602
647
  async def cleanup(self):
603
648
  """清理资源"""
604
649
  logger.debug("ExecutorCore cleaning up...")
@@ -16,116 +16,11 @@ import multiprocessing
16
16
  from typing import List, Dict
17
17
  import time
18
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
19
  # 不要在模块级别创建 logger,避免在 fork 时触发 logging 全局锁竞争
76
20
  # logger 将在 subprocess_main 中创建
77
21
  logger = None
78
22
 
79
23
 
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
24
  class SubprocessInitializer:
130
25
  """子进程初始化器 - 负责清理和准备环境"""
131
26
 
@@ -160,7 +55,7 @@ class SubprocessInitializer:
160
55
 
161
56
  # 3. 清空Redis连接池和客户端缓存
162
57
  # 这非常重要!防止子进程复用父进程的连接
163
- from jettask.utils.db_connector import clear_all_cache
58
+ from jettask.db.connector import clear_all_cache
164
59
  clear_all_cache()
165
60
 
166
61
  # 4. 强制垃圾回收
@@ -291,6 +186,28 @@ class MinimalApp:
291
186
  """根据任务名称获取任务对象"""
292
187
  return self._tasks.get(task_name)
293
188
 
189
+ def get_task_config(self, task_name: str) -> dict:
190
+ """
191
+ 获取任务配置
192
+
193
+ Args:
194
+ task_name: 任务名称
195
+
196
+ Returns:
197
+ 任务配置字典,如果任务不存在则返回None
198
+ """
199
+ task = self.get_task_by_name(task_name)
200
+ if not task:
201
+ return None
202
+
203
+ return {
204
+ 'auto_ack': getattr(task, 'auto_ack', True),
205
+ 'queue': getattr(task, 'queue', None),
206
+ 'timeout': getattr(task, 'timeout', None),
207
+ 'max_retries': getattr(task, 'max_retries', 0),
208
+ 'retry_delay': getattr(task, 'retry_delay', None),
209
+ }
210
+
294
211
  def cleanup(self):
295
212
  """清理资源"""
296
213
  pass
@@ -345,12 +262,12 @@ class SubprocessRunner:
345
262
  if self.event_pool:
346
263
  self.event_pool._stop_reading = True
347
264
 
348
- # signal.signal(signal.SIGINT, signal_handler)
349
- # signal.signal(signal.SIGTERM, signal_handler)
265
+ signal.signal(signal.SIGINT, signal_handler)
266
+ signal.signal(signal.SIGTERM, signal_handler)
350
267
 
351
268
  def create_redis_connections(self):
352
269
  """创建独立的Redis连接(使用全局客户端实例)"""
353
- from jettask.utils.db_connector import get_sync_redis_client, get_async_redis_client
270
+ from jettask.db.connector import get_sync_redis_client, get_async_redis_client
354
271
 
355
272
  # logger.info(f"Process #{self.process_id}: Creating Redis connections")
356
273
 
@@ -394,16 +311,13 @@ class SubprocessRunner:
394
311
  self.event_pool = EventPool(
395
312
  self.redis_client,
396
313
  self.async_redis_client,
314
+ queues=self.queues, # ✅ 传递 queues 参数以支持通配符模式
397
315
  redis_url=self.redis_url,
398
316
  consumer_strategy=self.consumer_strategy,
399
317
  consumer_config=consumer_config,
400
318
  redis_prefix=self.redis_prefix,
401
319
  app=self.minimal_app
402
320
  )
403
-
404
-
405
- # logger.info('准备进入测试流程')
406
- # await execute_commands(self.event_pool.async_redis_client, num_commands=100000)
407
321
 
408
322
  # 将 EventPool 设置到 MinimalApp
409
323
  self.minimal_app.ep = self.event_pool
@@ -418,7 +332,8 @@ class SubprocessRunner:
418
332
  )
419
333
 
420
334
  # 初始化路由
421
- self.event_pool.queues = self.queues
335
+ # ✅ 不再需要这行,因为 EventPool.__init__ 已经正确处理了 queues(包括通配符)
336
+ # self.event_pool.queues = self.queues
422
337
  self.event_pool.init_routing()
423
338
 
424
339
  # 收集任务(按队列分组)
@@ -56,6 +56,10 @@ class TaskExecutor:
56
56
  concurrency=concurrency
57
57
  )
58
58
 
59
+ # 将executor_core存储到app,供手动ACK使用
60
+ if not hasattr(app, '_executor_core'):
61
+ app._executor_core = self.executor_core
62
+
59
63
  # 活动任务集合
60
64
  self._active_tasks: Set[asyncio.Task] = set()
61
65