jettask 0.2.1__py3-none-any.whl → 0.2.4__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 (89) hide show
  1. jettask/constants.py +213 -0
  2. jettask/core/app.py +525 -205
  3. jettask/core/cli.py +193 -185
  4. jettask/core/consumer_manager.py +126 -34
  5. jettask/core/context.py +3 -0
  6. jettask/core/enums.py +137 -0
  7. jettask/core/event_pool.py +501 -168
  8. jettask/core/message.py +147 -0
  9. jettask/core/offline_worker_recovery.py +181 -114
  10. jettask/core/task.py +10 -174
  11. jettask/core/task_batch.py +153 -0
  12. jettask/core/unified_manager_base.py +243 -0
  13. jettask/core/worker_scanner.py +54 -54
  14. jettask/executors/asyncio.py +184 -64
  15. jettask/webui/backend/config.py +51 -0
  16. jettask/webui/backend/data_access.py +2083 -92
  17. jettask/webui/backend/data_api.py +3294 -0
  18. jettask/webui/backend/dependencies.py +261 -0
  19. jettask/webui/backend/init_meta_db.py +158 -0
  20. jettask/webui/backend/main.py +1358 -69
  21. jettask/webui/backend/main_unified.py +78 -0
  22. jettask/webui/backend/main_v2.py +394 -0
  23. jettask/webui/backend/namespace_api.py +295 -0
  24. jettask/webui/backend/namespace_api_old.py +294 -0
  25. jettask/webui/backend/namespace_data_access.py +611 -0
  26. jettask/webui/backend/queue_backlog_api.py +727 -0
  27. jettask/webui/backend/queue_stats_v2.py +521 -0
  28. jettask/webui/backend/redis_monitor_api.py +476 -0
  29. jettask/webui/backend/unified_api_router.py +1601 -0
  30. jettask/webui/db_init.py +204 -32
  31. jettask/webui/frontend/package-lock.json +492 -1
  32. jettask/webui/frontend/package.json +4 -1
  33. jettask/webui/frontend/src/App.css +105 -7
  34. jettask/webui/frontend/src/App.jsx +49 -20
  35. jettask/webui/frontend/src/components/NamespaceSelector.jsx +166 -0
  36. jettask/webui/frontend/src/components/QueueBacklogChart.jsx +298 -0
  37. jettask/webui/frontend/src/components/QueueBacklogTrend.jsx +638 -0
  38. jettask/webui/frontend/src/components/QueueDetailsTable.css +65 -0
  39. jettask/webui/frontend/src/components/QueueDetailsTable.jsx +487 -0
  40. jettask/webui/frontend/src/components/QueueDetailsTableV2.jsx +465 -0
  41. jettask/webui/frontend/src/components/ScheduledTaskFilter.jsx +423 -0
  42. jettask/webui/frontend/src/components/TaskFilter.jsx +425 -0
  43. jettask/webui/frontend/src/components/TimeRangeSelector.css +21 -0
  44. jettask/webui/frontend/src/components/TimeRangeSelector.jsx +160 -0
  45. jettask/webui/frontend/src/components/layout/AppLayout.css +95 -0
  46. jettask/webui/frontend/src/components/layout/AppLayout.jsx +49 -0
  47. jettask/webui/frontend/src/components/layout/Header.css +34 -10
  48. jettask/webui/frontend/src/components/layout/Header.jsx +31 -23
  49. jettask/webui/frontend/src/components/layout/SideMenu.css +137 -0
  50. jettask/webui/frontend/src/components/layout/SideMenu.jsx +209 -0
  51. jettask/webui/frontend/src/components/layout/TabsNav.css +244 -0
  52. jettask/webui/frontend/src/components/layout/TabsNav.jsx +206 -0
  53. jettask/webui/frontend/src/components/layout/UserInfo.css +197 -0
  54. jettask/webui/frontend/src/components/layout/UserInfo.jsx +197 -0
  55. jettask/webui/frontend/src/contexts/NamespaceContext.jsx +72 -0
  56. jettask/webui/frontend/src/contexts/TabsContext.backup.jsx +245 -0
  57. jettask/webui/frontend/src/main.jsx +1 -0
  58. jettask/webui/frontend/src/pages/Alerts.jsx +684 -0
  59. jettask/webui/frontend/src/pages/Dashboard.jsx +1330 -0
  60. jettask/webui/frontend/src/pages/QueueDetail.jsx +1109 -10
  61. jettask/webui/frontend/src/pages/QueueMonitor.jsx +236 -115
  62. jettask/webui/frontend/src/pages/Queues.jsx +5 -1
  63. jettask/webui/frontend/src/pages/ScheduledTasks.jsx +809 -0
  64. jettask/webui/frontend/src/pages/Settings.jsx +800 -0
  65. jettask/webui/frontend/src/services/api.js +7 -5
  66. jettask/webui/frontend/src/utils/suppressWarnings.js +22 -0
  67. jettask/webui/frontend/src/utils/userPreferences.js +154 -0
  68. jettask/webui/multi_namespace_consumer.py +543 -0
  69. jettask/webui/pg_consumer.py +983 -246
  70. jettask/webui/static/dist/assets/index-7129cfe1.css +1 -0
  71. jettask/webui/static/dist/assets/index-8d1935cc.js +774 -0
  72. jettask/webui/static/dist/index.html +2 -2
  73. jettask/webui/task_center.py +216 -0
  74. jettask/webui/task_center_client.py +150 -0
  75. jettask/webui/unified_consumer_manager.py +193 -0
  76. {jettask-0.2.1.dist-info → jettask-0.2.4.dist-info}/METADATA +1 -1
  77. jettask-0.2.4.dist-info/RECORD +134 -0
  78. jettask/webui/pg_consumer_slow.py +0 -1099
  79. jettask/webui/pg_consumer_test.py +0 -678
  80. jettask/webui/static/dist/assets/index-823408e8.css +0 -1
  81. jettask/webui/static/dist/assets/index-9968b0b8.js +0 -543
  82. jettask/webui/test_pg_consumer_recovery.py +0 -547
  83. jettask/webui/test_recovery_simple.py +0 -492
  84. jettask/webui/test_self_recovery.py +0 -467
  85. jettask-0.2.1.dist-info/RECORD +0 -91
  86. {jettask-0.2.1.dist-info → jettask-0.2.4.dist-info}/WHEEL +0 -0
  87. {jettask-0.2.1.dist-info → jettask-0.2.4.dist-info}/entry_points.txt +0 -0
  88. {jettask-0.2.1.dist-info → jettask-0.2.4.dist-info}/licenses/LICENSE +0 -0
  89. {jettask-0.2.1.dist-info → jettask-0.2.4.dist-info}/top_level.txt +0 -0
@@ -1,492 +0,0 @@
1
- #!/usr/bin/env python
2
- """简化的恢复机制测试脚本"""
3
-
4
- import asyncio
5
- import json
6
- import logging
7
- import os
8
- import signal
9
- import sys
10
- import time
11
- from typing import Dict, List, Optional
12
- from datetime import datetime, timezone
13
-
14
- import redis.asyncio as redis
15
- from redis.asyncio import Redis
16
- from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
17
- from sqlalchemy.orm import sessionmaker
18
- from sqlalchemy import text
19
-
20
- from jettask.webui.config import PostgreSQLConfig, RedisConfig
21
- from jettask.core.consumer_manager import ConsumerManager, ConsumerStrategy
22
- from jettask.utils.serializer import dumps_str
23
-
24
- logger = logging.getLogger(__name__)
25
- logging.basicConfig(
26
- level=logging.INFO,
27
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
28
- )
29
-
30
- class RecoveryTester:
31
- """恢复机制测试器"""
32
-
33
- def __init__(self):
34
- self.pg_config = PostgreSQLConfig(
35
- host=os.getenv('JETTASK_PG_HOST', 'localhost'),
36
- port=int(os.getenv('JETTASK_PG_PORT', '5432')),
37
- database=os.getenv('JETTASK_PG_DB', 'jettask'),
38
- user=os.getenv('JETTASK_PG_USER', 'jettask'),
39
- password=os.getenv('JETTASK_PG_PASSWORD', '123456'),
40
- )
41
-
42
- self.redis_config = RedisConfig(
43
- host=os.getenv('REDIS_HOST', 'localhost'),
44
- port=int(os.getenv('REDIS_PORT', '6379')),
45
- db=int(os.getenv('REDIS_DB', '0')),
46
- password=os.getenv('REDIS_PASSWORD'),
47
- )
48
-
49
- self.prefix = "jettask"
50
- self.redis_client: Optional[Redis] = None
51
- self.async_engine = None
52
- self.AsyncSessionLocal = None
53
-
54
- async def setup(self):
55
- """初始化连接"""
56
- self.redis_client = await redis.Redis(
57
- host=self.redis_config.host,
58
- port=self.redis_config.port,
59
- db=self.redis_config.db,
60
- password=self.redis_config.password,
61
- decode_responses=False
62
- )
63
-
64
- if self.pg_config.dsn.startswith('postgresql://'):
65
- dsn = self.pg_config.dsn.replace('postgresql://', 'postgresql+psycopg://', 1)
66
- else:
67
- dsn = self.pg_config.dsn
68
-
69
- self.async_engine = create_async_engine(dsn, pool_size=20, echo=False)
70
-
71
- self.AsyncSessionLocal = sessionmaker(
72
- self.async_engine,
73
- class_=AsyncSession,
74
- expire_on_commit=False
75
- )
76
-
77
- async def cleanup(self):
78
- """清理连接"""
79
- if self.redis_client:
80
- await self.redis_client.aclose()
81
- if self.async_engine:
82
- await self.async_engine.dispose()
83
-
84
- async def test_queue_recovery(self):
85
- """测试队列消息的恢复机制"""
86
- logger.info("=" * 60)
87
- logger.info("测试 _consume_queues 的恢复机制")
88
- logger.info("=" * 60)
89
-
90
- queue_name = 'RECOVERY_TEST_QUEUE'
91
- stream_key = f"{self.prefix}:QUEUE:{queue_name}"
92
- consumer_group = f"{self.prefix}_pg_consumer1"
93
-
94
- # 1. 清理旧数据
95
- logger.info("\n1. 清理旧数据...")
96
- try:
97
- await self.redis_client.delete(stream_key)
98
- except:
99
- pass
100
-
101
- # 2. 发送测试消息
102
- logger.info("\n2. 发送测试消息到队列...")
103
- task_ids = []
104
- for i in range(5):
105
- task_data = {
106
- 'name': f'recovery_test_task_{i}',
107
- 'queue': queue_name,
108
- 'priority': 1,
109
- 'trigger_time': time.time(),
110
- 'test_id': f'queue_recovery_{i}'
111
- }
112
-
113
- msg_id = await self.redis_client.xadd(
114
- stream_key,
115
- {'data': dumps_str(task_data)}
116
- )
117
- task_ids.append(msg_id)
118
- logger.info(f" - 发送消息 {i}: {msg_id}")
119
-
120
- # 3. 创建消费者组
121
- logger.info("\n3. 创建消费者组...")
122
- try:
123
- await self.redis_client.xgroup_create(
124
- stream_key, consumer_group, id='0', mkstream=True
125
- )
126
- logger.info(f" - 创建消费者组: {consumer_group}")
127
- except:
128
- pass
129
-
130
- # 4. 模拟consumer_1读取消息但不ACK(突然挂掉)
131
- logger.info("\n4. 模拟 consumer_1 读取消息但不ACK...")
132
- consumer_1 = "test_consumer_1"
133
-
134
- messages = await self.redis_client.xreadgroup(
135
- consumer_group,
136
- consumer_1,
137
- {stream_key: '>'},
138
- count=5,
139
- block=1000
140
- )
141
-
142
- if messages:
143
- logger.info(f" - consumer_1 读取了 {len(messages[0][1])} 条消息")
144
- for msg_id, data in messages[0][1]:
145
- logger.info(f" - 消息ID: {msg_id}")
146
-
147
- # 5. 检查pending消息
148
- logger.info("\n5. 检查pending消息...")
149
- pending_info = await self.redis_client.xpending(stream_key, consumer_group)
150
- logger.info(f" - 总pending消息数: {pending_info['pending']}")
151
-
152
- if pending_info['pending'] > 0:
153
- detailed = await self.redis_client.xpending_range(
154
- stream_key, consumer_group,
155
- min='-', max='+', count=10
156
- )
157
- for msg in detailed:
158
- logger.info(f" - 消息 {msg['message_id']}: consumer={msg['consumer']}, times_delivered={msg['times_delivered']}")
159
-
160
- # 6. 模拟consumer_2接管pending消息
161
- logger.info("\n6. 模拟 consumer_2 尝试接管pending消息...")
162
- consumer_2 = "test_consumer_2"
163
-
164
- # 使用XCLAIM接管消息
165
- if pending_info['pending'] > 0:
166
- # 获取所有pending消息的ID
167
- pending_msg_ids = [msg['message_id'] for msg in detailed]
168
-
169
- # XCLAIM接管消息(idle时间设为0表示立即接管)
170
- claimed = await self.redis_client.xclaim(
171
- stream_key,
172
- consumer_group,
173
- consumer_2,
174
- min_idle_time=0, # 立即接管
175
- message_ids=pending_msg_ids
176
- )
177
-
178
- logger.info(f" - consumer_2 接管了 {len(claimed)} 条消息")
179
-
180
- # ACK消息
181
- if claimed:
182
- msg_ids_to_ack = [msg[0] for msg in claimed]
183
- await self.redis_client.xack(stream_key, consumer_group, *msg_ids_to_ack)
184
- logger.info(f" - consumer_2 ACK了 {len(msg_ids_to_ack)} 条消息")
185
-
186
- # 7. 再次检查pending消息
187
- logger.info("\n7. 再次检查pending消息...")
188
- pending_info_after = await self.redis_client.xpending(stream_key, consumer_group)
189
- logger.info(f" - 总pending消息数: {pending_info_after['pending']}")
190
-
191
- # 8. 验证结果
192
- logger.info("\n" + "=" * 60)
193
- if pending_info['pending'] > 0 and pending_info_after['pending'] == 0:
194
- logger.info("✓ 队列消息恢复测试通过!pending消息成功被接管和处理")
195
- else:
196
- logger.warning(f"✗ 队列消息恢复测试失败。恢复前: {pending_info['pending']},恢复后: {pending_info_after['pending']}")
197
-
198
- return pending_info['pending'] > 0 and pending_info_after['pending'] == 0
199
-
200
- async def test_task_changes_recovery(self):
201
- """测试TASK_CHANGES的恢复机制"""
202
- logger.info("\n" + "=" * 60)
203
- logger.info("测试 _consume_task_changes 的恢复机制")
204
- logger.info("=" * 60)
205
-
206
- change_stream_key = f"{self.prefix}:TASK_CHANGES"
207
- consumer_group = f"{self.prefix}_changes_consumer"
208
-
209
- # 1. 清理旧数据
210
- logger.info("\n1. 清理旧数据...")
211
- try:
212
- await self.redis_client.delete(change_stream_key)
213
- except:
214
- pass
215
-
216
- # 2. 发送任务变更事件
217
- logger.info("\n2. 发送任务变更事件...")
218
- event_ids = []
219
- for i in range(5):
220
- event_data = {
221
- 'event_id': f'task_change_test_{i}',
222
- 'event_type': 'task_updated',
223
- 'timestamp': str(time.time())
224
- }
225
-
226
- msg_id = await self.redis_client.xadd(
227
- change_stream_key,
228
- event_data
229
- )
230
- event_ids.append(msg_id)
231
- logger.info(f" - 发送事件 {i}: {msg_id}")
232
-
233
- # 同时创建对应的任务数据
234
- task_key = f"{self.prefix}:TASK:task_change_test_{i}"
235
- task_data = {
236
- 'status': 'completed',
237
- 'completed_at': str(time.time()),
238
- 'result': json.dumps({'success': True})
239
- }
240
- await self.redis_client.hset(task_key, mapping={
241
- k: dumps_str(v) if not isinstance(v, (str, bytes)) else v
242
- for k, v in task_data.items()
243
- })
244
-
245
- # 3. 创建消费者组
246
- logger.info("\n3. 创建消费者组...")
247
- try:
248
- await self.redis_client.xgroup_create(
249
- change_stream_key, consumer_group, id='0', mkstream=True
250
- )
251
- logger.info(f" - 创建消费者组: {consumer_group}")
252
- except:
253
- pass
254
-
255
- # 4. 模拟consumer_1读取消息但不ACK
256
- logger.info("\n4. 模拟 consumer_1 读取消息但不ACK...")
257
- consumer_1 = "changes_consumer_1"
258
-
259
- messages = await self.redis_client.xreadgroup(
260
- consumer_group,
261
- consumer_1,
262
- {change_stream_key: '>'},
263
- count=5,
264
- block=1000
265
- )
266
-
267
- if messages:
268
- logger.info(f" - consumer_1 读取了 {len(messages[0][1])} 条事件")
269
- for msg_id, data in messages[0][1]:
270
- logger.info(f" - 事件ID: {msg_id}")
271
-
272
- # 5. 检查pending消息
273
- logger.info("\n5. 检查pending事件...")
274
- pending_info = await self.redis_client.xpending(change_stream_key, consumer_group)
275
- logger.info(f" - 总pending事件数: {pending_info['pending']}")
276
-
277
- if pending_info['pending'] > 0:
278
- detailed = await self.redis_client.xpending_range(
279
- change_stream_key, consumer_group,
280
- min='-', max='+', count=10
281
- )
282
- for msg in detailed:
283
- logger.info(f" - 事件 {msg['message_id']}: consumer={msg['consumer']}, times_delivered={msg['times_delivered']}")
284
-
285
- # 6. 模拟consumer_2接管pending消息
286
- logger.info("\n6. 模拟 consumer_2 接管pending事件...")
287
- consumer_2 = "changes_consumer_2"
288
-
289
- if pending_info['pending'] > 0:
290
- pending_msg_ids = [msg['message_id'] for msg in detailed]
291
-
292
- claimed = await self.redis_client.xclaim(
293
- change_stream_key,
294
- consumer_group,
295
- consumer_2,
296
- min_idle_time=0,
297
- message_ids=pending_msg_ids
298
- )
299
-
300
- logger.info(f" - consumer_2 接管了 {len(claimed)} 条事件")
301
-
302
- # ACK消息
303
- if claimed:
304
- msg_ids_to_ack = [msg[0] for msg in claimed]
305
- await self.redis_client.xack(change_stream_key, consumer_group, *msg_ids_to_ack)
306
- logger.info(f" - consumer_2 ACK了 {len(msg_ids_to_ack)} 条事件")
307
-
308
- # 7. 再次检查pending消息
309
- logger.info("\n7. 再次检查pending事件...")
310
- pending_info_after = await self.redis_client.xpending(change_stream_key, consumer_group)
311
- logger.info(f" - 总pending事件数: {pending_info_after['pending']}")
312
-
313
- # 8. 验证结果
314
- logger.info("\n" + "=" * 60)
315
- if pending_info['pending'] > 0 and pending_info_after['pending'] == 0:
316
- logger.info("✓ TASK_CHANGES恢复测试通过!pending事件成功被接管和处理")
317
- else:
318
- logger.warning(f"✗ TASK_CHANGES恢复测试失败。恢复前: {pending_info['pending']},恢复后: {pending_info_after['pending']}")
319
-
320
- return pending_info['pending'] > 0 and pending_info_after['pending'] == 0
321
-
322
- async def test_offline_worker_recovery(self):
323
- """测试OfflineWorkerRecovery的功能"""
324
- logger.info("\n" + "=" * 60)
325
- logger.info("测试 OfflineWorkerRecovery 的功能")
326
- logger.info("=" * 60)
327
-
328
- # 导入OfflineWorkerRecovery
329
- from jettask.core.offline_worker_recovery import OfflineWorkerRecovery
330
-
331
- # 创建同步Redis客户端(ConsumerManager需要)
332
- import redis as sync_redis
333
- sync_redis_client = sync_redis.StrictRedis(
334
- host=self.redis_config.host,
335
- port=self.redis_config.port,
336
- db=self.redis_config.db,
337
- password=self.redis_config.password,
338
- decode_responses=True
339
- )
340
-
341
- # 创建ConsumerManager
342
- consumer_manager = ConsumerManager(
343
- redis_client=sync_redis_client,
344
- strategy=ConsumerStrategy.HEARTBEAT,
345
- config={
346
- 'redis_prefix': self.prefix,
347
- 'queues': ['OFFLINE_TEST_QUEUE'],
348
- 'worker_prefix': 'TEST_WORKER'
349
- }
350
- )
351
-
352
- # 创建OfflineWorkerRecovery
353
- recovery = OfflineWorkerRecovery(
354
- async_redis_client=self.redis_client,
355
- redis_prefix=self.prefix,
356
- worker_prefix='TEST_WORKER',
357
- consumer_manager=consumer_manager
358
- )
359
-
360
- # 准备测试数据
361
- queue_name = 'OFFLINE_TEST_QUEUE'
362
- stream_key = f"{self.prefix}:QUEUE:{queue_name}"
363
- consumer_group = f"{self.prefix}_pg_consumer1"
364
-
365
- # 清理旧数据
366
- logger.info("\n1. 清理旧数据...")
367
- try:
368
- await self.redis_client.delete(stream_key)
369
- except:
370
- pass
371
-
372
- # 发送测试消息
373
- logger.info("\n2. 发送测试消息...")
374
- for i in range(3):
375
- task_data = {
376
- 'name': f'offline_test_{i}',
377
- 'queue': queue_name,
378
- 'test_id': f'offline_{i}'
379
- }
380
- await self.redis_client.xadd(
381
- stream_key,
382
- {'data': dumps_str(task_data)}
383
- )
384
-
385
- # 创建消费者组
386
- try:
387
- await self.redis_client.xgroup_create(
388
- stream_key, consumer_group, id='0', mkstream=True
389
- )
390
- except:
391
- pass
392
-
393
- # 模拟离线worker读取消息
394
- logger.info("\n3. 模拟离线worker读取消息...")
395
- offline_worker = "offline_worker_1"
396
- messages = await self.redis_client.xreadgroup(
397
- consumer_group,
398
- offline_worker,
399
- {stream_key: '>'},
400
- count=3,
401
- block=1000
402
- )
403
-
404
- if messages:
405
- logger.info(f" - 离线worker读取了 {len(messages[0][1])} 条消息")
406
-
407
- # 模拟worker离线(删除其注册信息)
408
- worker_key = f"{self.prefix}:TEST_WORKER:{offline_worker}"
409
- await self.redis_client.delete(worker_key)
410
- logger.info(f" - 模拟worker离线: 删除 {worker_key}")
411
-
412
- # 定义处理回调
413
- processed_messages = []
414
- async def process_callback(msg_id, msg_data, queue, consumer_id):
415
- processed_messages.append({
416
- 'msg_id': msg_id,
417
- 'queue': queue,
418
- 'consumer_id': consumer_id
419
- })
420
- logger.info(f" - 处理恢复的消息: {msg_id} from {consumer_id}")
421
-
422
- # 执行恢复
423
- logger.info("\n4. 执行离线worker恢复...")
424
- current_consumer = "active_worker_1"
425
- recovered_count = await recovery.recover_offline_workers(
426
- queue=queue_name,
427
- current_consumer_name=current_consumer,
428
- process_message_callback=process_callback
429
- )
430
-
431
- logger.info(f" - 恢复了 {recovered_count} 条消息")
432
-
433
- # 检查pending消息
434
- logger.info("\n5. 检查恢复后的pending消息...")
435
- pending_info = await self.redis_client.xpending(stream_key, consumer_group)
436
- logger.info(f" - 剩余pending消息数: {pending_info['pending']}")
437
-
438
- # 验证结果
439
- logger.info("\n" + "=" * 60)
440
- if recovered_count > 0 and len(processed_messages) == recovered_count:
441
- logger.info("✓ OfflineWorkerRecovery测试通过!成功恢复离线worker的消息")
442
- else:
443
- logger.warning(f"✗ OfflineWorkerRecovery测试失败。恢复数量: {recovered_count},处理数量: {len(processed_messages)}")
444
-
445
- # 清理
446
- consumer_manager.cleanup()
447
- sync_redis_client.close()
448
-
449
- return recovered_count > 0
450
-
451
-
452
- async def main():
453
- """主测试函数"""
454
- from dotenv import load_dotenv
455
- load_dotenv()
456
-
457
- tester = RecoveryTester()
458
- await tester.setup()
459
-
460
- try:
461
- # 测试1:队列消息恢复
462
- queue_test = await tester.test_queue_recovery()
463
- await asyncio.sleep(1)
464
-
465
- # 测试2:TASK_CHANGES恢复
466
- changes_test = await tester.test_task_changes_recovery()
467
- await asyncio.sleep(1)
468
-
469
- # 测试3:OfflineWorkerRecovery
470
- offline_test = await tester.test_offline_worker_recovery()
471
-
472
- # 总结
473
- logger.info("\n" + "=" * 60)
474
- logger.info("测试总结")
475
- logger.info("=" * 60)
476
- logger.info(f"队列消息恢复测试: {'✓ 通过' if queue_test else '✗ 失败'}")
477
- logger.info(f"TASK_CHANGES恢复测试: {'✓ 通过' if changes_test else '✗ 失败'}")
478
- logger.info(f"OfflineWorkerRecovery测试: {'✓ 通过' if offline_test else '✗ 失败'}")
479
-
480
- if queue_test and changes_test and offline_test:
481
- logger.info("\n🎉 所有测试通过!恢复机制工作正常")
482
- else:
483
- logger.warning("\n⚠️ 部分测试失败,需要检查恢复机制")
484
-
485
- except Exception as e:
486
- logger.error(f"测试出错: {e}", exc_info=True)
487
- finally:
488
- await tester.cleanup()
489
-
490
-
491
- if __name__ == '__main__':
492
- asyncio.run(main())