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.
- jettask/constants.py +213 -0
- jettask/core/app.py +525 -205
- jettask/core/cli.py +193 -185
- jettask/core/consumer_manager.py +126 -34
- jettask/core/context.py +3 -0
- jettask/core/enums.py +137 -0
- jettask/core/event_pool.py +501 -168
- jettask/core/message.py +147 -0
- jettask/core/offline_worker_recovery.py +181 -114
- jettask/core/task.py +10 -174
- jettask/core/task_batch.py +153 -0
- jettask/core/unified_manager_base.py +243 -0
- jettask/core/worker_scanner.py +54 -54
- jettask/executors/asyncio.py +184 -64
- jettask/webui/backend/config.py +51 -0
- jettask/webui/backend/data_access.py +2083 -92
- jettask/webui/backend/data_api.py +3294 -0
- jettask/webui/backend/dependencies.py +261 -0
- jettask/webui/backend/init_meta_db.py +158 -0
- jettask/webui/backend/main.py +1358 -69
- jettask/webui/backend/main_unified.py +78 -0
- jettask/webui/backend/main_v2.py +394 -0
- jettask/webui/backend/namespace_api.py +295 -0
- jettask/webui/backend/namespace_api_old.py +294 -0
- jettask/webui/backend/namespace_data_access.py +611 -0
- jettask/webui/backend/queue_backlog_api.py +727 -0
- jettask/webui/backend/queue_stats_v2.py +521 -0
- jettask/webui/backend/redis_monitor_api.py +476 -0
- jettask/webui/backend/unified_api_router.py +1601 -0
- jettask/webui/db_init.py +204 -32
- jettask/webui/frontend/package-lock.json +492 -1
- jettask/webui/frontend/package.json +4 -1
- jettask/webui/frontend/src/App.css +105 -7
- jettask/webui/frontend/src/App.jsx +49 -20
- jettask/webui/frontend/src/components/NamespaceSelector.jsx +166 -0
- jettask/webui/frontend/src/components/QueueBacklogChart.jsx +298 -0
- jettask/webui/frontend/src/components/QueueBacklogTrend.jsx +638 -0
- jettask/webui/frontend/src/components/QueueDetailsTable.css +65 -0
- jettask/webui/frontend/src/components/QueueDetailsTable.jsx +487 -0
- jettask/webui/frontend/src/components/QueueDetailsTableV2.jsx +465 -0
- jettask/webui/frontend/src/components/ScheduledTaskFilter.jsx +423 -0
- jettask/webui/frontend/src/components/TaskFilter.jsx +425 -0
- jettask/webui/frontend/src/components/TimeRangeSelector.css +21 -0
- jettask/webui/frontend/src/components/TimeRangeSelector.jsx +160 -0
- jettask/webui/frontend/src/components/layout/AppLayout.css +95 -0
- jettask/webui/frontend/src/components/layout/AppLayout.jsx +49 -0
- jettask/webui/frontend/src/components/layout/Header.css +34 -10
- jettask/webui/frontend/src/components/layout/Header.jsx +31 -23
- jettask/webui/frontend/src/components/layout/SideMenu.css +137 -0
- jettask/webui/frontend/src/components/layout/SideMenu.jsx +209 -0
- jettask/webui/frontend/src/components/layout/TabsNav.css +244 -0
- jettask/webui/frontend/src/components/layout/TabsNav.jsx +206 -0
- jettask/webui/frontend/src/components/layout/UserInfo.css +197 -0
- jettask/webui/frontend/src/components/layout/UserInfo.jsx +197 -0
- jettask/webui/frontend/src/contexts/NamespaceContext.jsx +72 -0
- jettask/webui/frontend/src/contexts/TabsContext.backup.jsx +245 -0
- jettask/webui/frontend/src/main.jsx +1 -0
- jettask/webui/frontend/src/pages/Alerts.jsx +684 -0
- jettask/webui/frontend/src/pages/Dashboard.jsx +1330 -0
- jettask/webui/frontend/src/pages/QueueDetail.jsx +1109 -10
- jettask/webui/frontend/src/pages/QueueMonitor.jsx +236 -115
- jettask/webui/frontend/src/pages/Queues.jsx +5 -1
- jettask/webui/frontend/src/pages/ScheduledTasks.jsx +809 -0
- jettask/webui/frontend/src/pages/Settings.jsx +800 -0
- jettask/webui/frontend/src/services/api.js +7 -5
- jettask/webui/frontend/src/utils/suppressWarnings.js +22 -0
- jettask/webui/frontend/src/utils/userPreferences.js +154 -0
- jettask/webui/multi_namespace_consumer.py +543 -0
- jettask/webui/pg_consumer.py +983 -246
- jettask/webui/static/dist/assets/index-7129cfe1.css +1 -0
- jettask/webui/static/dist/assets/index-8d1935cc.js +774 -0
- jettask/webui/static/dist/index.html +2 -2
- jettask/webui/task_center.py +216 -0
- jettask/webui/task_center_client.py +150 -0
- jettask/webui/unified_consumer_manager.py +193 -0
- {jettask-0.2.1.dist-info → jettask-0.2.4.dist-info}/METADATA +1 -1
- jettask-0.2.4.dist-info/RECORD +134 -0
- jettask/webui/pg_consumer_slow.py +0 -1099
- jettask/webui/pg_consumer_test.py +0 -678
- jettask/webui/static/dist/assets/index-823408e8.css +0 -1
- jettask/webui/static/dist/assets/index-9968b0b8.js +0 -543
- jettask/webui/test_pg_consumer_recovery.py +0 -547
- jettask/webui/test_recovery_simple.py +0 -492
- jettask/webui/test_self_recovery.py +0 -467
- jettask-0.2.1.dist-info/RECORD +0 -91
- {jettask-0.2.1.dist-info → jettask-0.2.4.dist-info}/WHEEL +0 -0
- {jettask-0.2.1.dist-info → jettask-0.2.4.dist-info}/entry_points.txt +0 -0
- {jettask-0.2.1.dist-info → jettask-0.2.4.dist-info}/licenses/LICENSE +0 -0
- {jettask-0.2.1.dist-info → jettask-0.2.4.dist-info}/top_level.txt +0 -0
jettask/core/cli.py
CHANGED
@@ -29,19 +29,75 @@ def cli():
|
|
29
29
|
@cli.command()
|
30
30
|
@click.option('--host', default='0.0.0.0', help='服务器监听地址')
|
31
31
|
@click.option('--port', default=8001, type=int, help='服务器监听端口')
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
32
|
+
@click.option('--reload', is_flag=True, default=False, help='启用自动重载')
|
33
|
+
@click.option('--log-level', default='info',
|
34
|
+
type=click.Choice(['debug', 'info', 'warning', 'error']),
|
35
|
+
help='日志级别')
|
36
|
+
@click.option('--unified', is_flag=True, default=False, help='使用统一的API路由器(实验性)')
|
37
|
+
def api(host, port, reload, log_level, unified):
|
38
|
+
"""启动 API 服务和监控界面
|
36
39
|
|
37
|
-
|
40
|
+
示例:
|
41
|
+
\b
|
42
|
+
# 使用默认配置启动
|
43
|
+
jettask api
|
44
|
+
|
45
|
+
# 指定端口和主机
|
46
|
+
jettask api --host 0.0.0.0 --port 8080
|
47
|
+
|
48
|
+
# 启用开发模式(自动重载)
|
49
|
+
jettask api --reload --log-level debug
|
50
|
+
|
51
|
+
# 使用旧版API(不推荐)
|
52
|
+
jettask api --no-unified
|
53
|
+
"""
|
54
|
+
import os
|
38
55
|
import uvicorn
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
)
|
56
|
+
|
57
|
+
# 设置环境变量(如果需要)
|
58
|
+
if not os.getenv('JETTASK_PG_URL'):
|
59
|
+
os.environ['JETTASK_PG_URL'] = 'postgresql://jettask:123456@localhost:5432/jettask'
|
60
|
+
|
61
|
+
if not os.getenv('JETTASK_CENTER_URL'):
|
62
|
+
os.environ['JETTASK_CENTER_URL'] = f'http://localhost:{port}/api/namespaces/default'
|
63
|
+
|
64
|
+
# 选择使用的应用模块
|
65
|
+
if unified:
|
66
|
+
app_module = "jettask.webui.backend.main_unified:app"
|
67
|
+
click.echo(f"Starting JetTask API Server (Unified Mode - Experimental) on {host}:{port}")
|
68
|
+
click.echo("All API endpoints are consolidated in unified_api_router.py")
|
69
|
+
click.echo("NOTE: This mode requires specific database schema. Use --no-unified for production.")
|
70
|
+
else:
|
71
|
+
app_module = "jettask.webui.backend.main:app"
|
72
|
+
click.echo(f"Starting JetTask API Server on {host}:{port}")
|
73
|
+
|
74
|
+
# 显示配置信息
|
75
|
+
click.echo("=" * 60)
|
76
|
+
click.echo("API Server Configuration")
|
77
|
+
click.echo("=" * 60)
|
78
|
+
click.echo(f"Host: {host}")
|
79
|
+
click.echo(f"Port: {port}")
|
80
|
+
click.echo(f"API Mode: {'Unified' if unified else 'Legacy'}")
|
81
|
+
click.echo(f"Auto-reload: {reload}")
|
82
|
+
click.echo(f"Log level: {log_level}")
|
83
|
+
click.echo(f"Database: {os.getenv('JETTASK_PG_URL', 'Not configured')}")
|
84
|
+
click.echo("=" * 60)
|
85
|
+
|
86
|
+
# 启动服务器
|
87
|
+
try:
|
88
|
+
uvicorn.run(
|
89
|
+
app_module,
|
90
|
+
host=host,
|
91
|
+
port=port,
|
92
|
+
log_level=log_level,
|
93
|
+
reload=reload
|
94
|
+
)
|
95
|
+
except KeyboardInterrupt:
|
96
|
+
click.echo("\nShutting down API Server...")
|
97
|
+
except Exception as e:
|
98
|
+
click.echo(f"Error starting API Server: {e}", err=True)
|
99
|
+
sys.exit(1)
|
100
|
+
|
45
101
|
|
46
102
|
def load_module_from_path(module_path: str):
|
47
103
|
"""从文件路径加载 Python 模块"""
|
@@ -231,18 +287,51 @@ def worker(app_str, queues, executor, concurrency, prefetch, reload, config):
|
|
231
287
|
sys.exit(1)
|
232
288
|
|
233
289
|
@cli.command('webui-consumer')
|
234
|
-
@click.option('--
|
235
|
-
|
236
|
-
|
237
|
-
|
290
|
+
@click.option('--task-center', '-tc', envvar='JETTASK_CENTER_URL', required=True,
|
291
|
+
help='任务中心URL,如: http://localhost:8001 或 http://localhost:8001/api/namespaces/default')
|
292
|
+
@click.option('--check-interval', type=int, default=30,
|
293
|
+
help='命名空间检测间隔(秒),默认30秒')
|
294
|
+
@click.option('--debug', is_flag=True, help='启用调试模式')
|
295
|
+
def webui_consumer(task_center, check_interval, debug):
|
296
|
+
"""启动数据消费者(自动识别单/多命名空间)
|
238
297
|
|
239
|
-
|
240
|
-
|
241
|
-
|
298
|
+
根据URL格式自动判断运行模式:
|
299
|
+
- 单命名空间: http://localhost:8001/api/namespaces/{name}
|
300
|
+
- 多命名空间: http://localhost:8001 或 http://localhost:8001/api
|
242
301
|
|
243
|
-
|
244
|
-
|
245
|
-
|
302
|
+
示例:
|
303
|
+
\b
|
304
|
+
# 为所有命名空间启动消费者(自动检测)
|
305
|
+
jettask webui-consumer --task-center http://localhost:8001
|
306
|
+
jettask webui-consumer --task-center http://localhost:8001/api
|
307
|
+
|
308
|
+
# 为单个命名空间启动消费者
|
309
|
+
jettask webui-consumer --task-center http://localhost:8001/api/namespaces/default
|
310
|
+
|
311
|
+
# 自定义检测间隔
|
312
|
+
jettask webui-consumer --task-center http://localhost:8001 --check-interval 60
|
313
|
+
|
314
|
+
# 使用环境变量
|
315
|
+
export JETTASK_CENTER_URL=http://localhost:8001
|
316
|
+
jettask webui-consumer
|
317
|
+
"""
|
318
|
+
import asyncio
|
319
|
+
from jettask.webui.unified_consumer_manager import UnifiedConsumerManager
|
320
|
+
|
321
|
+
# 运行消费者管理器
|
322
|
+
async def run_manager():
|
323
|
+
"""运行统一的消费者管理器"""
|
324
|
+
manager = UnifiedConsumerManager(
|
325
|
+
task_center_url=task_center,
|
326
|
+
check_interval=check_interval,
|
327
|
+
debug=debug
|
328
|
+
)
|
329
|
+
await manager.run()
|
330
|
+
|
331
|
+
try:
|
332
|
+
asyncio.run(run_manager())
|
333
|
+
except KeyboardInterrupt:
|
334
|
+
click.echo("\nShutdown complete")
|
246
335
|
|
247
336
|
@cli.command()
|
248
337
|
def monitor():
|
@@ -252,16 +341,57 @@ def monitor():
|
|
252
341
|
monitor_main()
|
253
342
|
|
254
343
|
@cli.command()
|
255
|
-
|
256
|
-
|
344
|
+
@click.option('--task-center', '-tc', envvar='JETTASK_CENTER_URL',
|
345
|
+
help='任务中心URL,如: http://localhost:8001/api/namespaces/default')
|
346
|
+
def init(task_center):
|
347
|
+
"""初始化数据库和配置
|
348
|
+
|
349
|
+
示例:
|
350
|
+
\b
|
351
|
+
# 使用任务中心初始化
|
352
|
+
jettask init --task-center http://localhost:8001/api/namespaces/default
|
353
|
+
jettask init -tc http://localhost:8001/api/namespaces/production
|
354
|
+
|
355
|
+
# 使用环境变量
|
356
|
+
export JETTASK_CENTER_URL=http://localhost:8001/api/namespaces/default
|
357
|
+
jettask init
|
358
|
+
|
359
|
+
# 不使用任务中心(仅使用本地环境变量)
|
360
|
+
jettask init
|
361
|
+
"""
|
257
362
|
click.echo("Initializing JetTask...")
|
258
363
|
|
364
|
+
import os
|
365
|
+
|
366
|
+
# 如果提供了任务中心URL,尝试从任务中心获取配置
|
367
|
+
if task_center:
|
368
|
+
os.environ['JETTASK_CENTER_URL'] = task_center
|
369
|
+
click.echo(f"Using Task Center: {task_center}")
|
370
|
+
|
371
|
+
# 尝试从任务中心获取数据库配置
|
372
|
+
try:
|
373
|
+
from jettask.webui.task_center import TaskCenter
|
374
|
+
tc = TaskCenter(task_center)
|
375
|
+
if tc._connect_sync():
|
376
|
+
p_config = tc.pg_config
|
377
|
+
# 从任务中心获取的配置中提取数据库连接参数
|
378
|
+
os.environ['JETTASK_PG_HOST'] = p_config['host']
|
379
|
+
os.environ['JETTASK_PG_PORT'] = str(p_config['port'])
|
380
|
+
os.environ['JETTASK_PG_DATABASE'] = p_config['database']
|
381
|
+
os.environ['JETTASK_PG_USER'] = p_config['user']
|
382
|
+
os.environ['JETTASK_PG_PASSWORD'] = p_config['password']
|
383
|
+
else:
|
384
|
+
click.echo("⚠ Failed to connect to Task Center, using local configuration", err=True)
|
385
|
+
except Exception as e:
|
386
|
+
click.echo(f"⚠ Could not get configuration from Task Center: {e}", err=True)
|
387
|
+
click.echo(" Falling back to local environment variables")
|
388
|
+
|
259
389
|
# 初始化数据库
|
260
390
|
from jettask.webui.db_init import init_database
|
261
|
-
click.echo("
|
391
|
+
click.echo("\nInitializing database...")
|
262
392
|
init_database()
|
263
393
|
|
264
|
-
click.echo("JetTask initialized successfully!")
|
394
|
+
click.echo("\n✓ JetTask initialized successfully!")
|
265
395
|
|
266
396
|
@cli.command()
|
267
397
|
def status():
|
@@ -296,179 +426,57 @@ def status():
|
|
296
426
|
click.echo("=" * 50)
|
297
427
|
|
298
428
|
@cli.command()
|
299
|
-
@click.
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
help='Redis 连接 URL(或设置 REDIS_URL 环境变量)')
|
429
|
+
@click.option('--task-center', '-tc', envvar='JETTASK_CENTER_URL', required=True,
|
430
|
+
help='任务中心URL,如: http://localhost:8001 或 http://localhost:8001/api/namespaces/default')
|
431
|
+
@click.option('--interval', '-i', type=float, default=0.1,
|
432
|
+
help='调度器扫描间隔(秒),默认0.1秒')
|
433
|
+
@click.option('--batch-size', '-b', type=int, default=100,
|
434
|
+
help='每批处理的最大任务数,默认100')
|
435
|
+
@click.option('--check-interval', type=int, default=30,
|
436
|
+
help='命名空间检测间隔(秒),仅多命名空间模式使用,默认30秒')
|
308
437
|
@click.option('--debug', is_flag=True, help='启用调试模式')
|
309
|
-
def scheduler(
|
310
|
-
"""
|
438
|
+
def scheduler(task_center, interval, batch_size, check_interval, debug):
|
439
|
+
"""启动定时任务调度器(自动识别单/多命名空间)
|
440
|
+
|
441
|
+
根据URL格式自动判断运行模式:
|
442
|
+
- 单命名空间: http://localhost:8001/api/namespaces/{name}
|
443
|
+
- 多命名空间: http://localhost:8001 或 http://localhost:8001/api
|
311
444
|
|
312
445
|
示例:
|
313
446
|
\b
|
314
|
-
#
|
315
|
-
jettask scheduler
|
316
|
-
jettask scheduler
|
447
|
+
# 为所有命名空间启动调度器(自动检测)
|
448
|
+
jettask scheduler --task-center http://localhost:8001
|
449
|
+
jettask scheduler --task-center http://localhost:8001/api
|
317
450
|
|
318
|
-
#
|
319
|
-
jettask scheduler
|
451
|
+
# 为单个命名空间启动调度器
|
452
|
+
jettask scheduler --task-center http://localhost:8001/api/namespaces/default
|
453
|
+
|
454
|
+
# 自定义配置
|
455
|
+
jettask scheduler --task-center http://localhost:8001 --check-interval 60 --interval 0.5
|
320
456
|
|
321
457
|
# 使用环境变量
|
322
|
-
export
|
323
|
-
export JETTASK_PG_URL=postgresql://user:pass@localhost/db
|
458
|
+
export JETTASK_CENTER_URL=http://localhost:8001
|
324
459
|
jettask scheduler
|
325
460
|
"""
|
326
461
|
import asyncio
|
462
|
+
from jettask.scheduler.unified_scheduler_manager import UnifiedSchedulerManager
|
463
|
+
|
464
|
+
# 运行调度器管理器
|
465
|
+
async def run_manager():
|
466
|
+
"""运行统一的调度器管理器"""
|
467
|
+
manager = UnifiedSchedulerManager(
|
468
|
+
task_center_url=task_center,
|
469
|
+
scan_interval=interval,
|
470
|
+
batch_size=batch_size,
|
471
|
+
check_interval=check_interval,
|
472
|
+
debug=debug
|
473
|
+
)
|
474
|
+
await manager.run()
|
327
475
|
|
328
|
-
# 处理相对导入
|
329
|
-
try:
|
330
|
-
from ..scheduler.scheduler import TaskScheduler
|
331
|
-
from ..scheduler.manager import ScheduledTaskManager
|
332
|
-
except ImportError:
|
333
|
-
# 直接运行时使用绝对导入
|
334
|
-
from jettask.scheduler.scheduler import TaskScheduler
|
335
|
-
from jettask.scheduler.manager import ScheduledTaskManager
|
336
|
-
|
337
|
-
# 设置日志级别
|
338
|
-
if debug:
|
339
|
-
import logging
|
340
|
-
logging.basicConfig(level=logging.DEBUG)
|
341
|
-
|
342
|
-
# 加载应用
|
343
|
-
try:
|
344
|
-
if app_str:
|
345
|
-
click.echo(f"Loading app from: {app_str}")
|
346
|
-
app = import_app(app_str)
|
347
|
-
else:
|
348
|
-
click.echo("Auto-discovering Jettask app...")
|
349
|
-
click.echo("Searching in: app.py, main.py, server.py, worker.py")
|
350
|
-
app = import_app() # 自动发现
|
351
|
-
|
352
|
-
# 显示应用信息
|
353
|
-
app_info = AppImporter.get_app_info(app)
|
354
|
-
click.echo(f"\nFound Jettask app:")
|
355
|
-
click.echo(f" Tasks: {app_info['tasks']} registered")
|
356
|
-
if app_info.get('task_names') and app_info['tasks'] > 0:
|
357
|
-
task_preview = app_info['task_names'][:3]
|
358
|
-
click.echo(f" Names: {', '.join(task_preview)}" +
|
359
|
-
(f" (+{app_info['tasks'] - 3} more)" if app_info['tasks'] > 3 else ""))
|
360
|
-
except ImportError as e:
|
361
|
-
click.echo(f"Error: {e}", err=True)
|
362
|
-
click.echo("\nTips:", err=True)
|
363
|
-
click.echo(" - Specify app location: jettask scheduler myapp:app", err=True)
|
364
|
-
click.echo(" - Or set environment variable: export JETTASK_APP=myapp:app", err=True)
|
365
|
-
click.echo(" - Or ensure app.py or main.py exists in current directory", err=True)
|
366
|
-
sys.exit(1)
|
367
|
-
except Exception as e:
|
368
|
-
import traceback
|
369
|
-
click.echo(f"Error loading app: {e}", err=True)
|
370
|
-
if debug:
|
371
|
-
traceback.print_exc()
|
372
|
-
sys.exit(1)
|
373
|
-
|
374
|
-
# 获取配置
|
375
|
-
# 优先级:命令行参数 > 环境变量 > app 配置
|
376
|
-
redis_url = redis_url or getattr(app, 'redis_url', 'redis://localhost:6379/0')
|
377
|
-
pg_url = pg_url or getattr(app, 'pg_url', None)
|
378
|
-
|
379
|
-
# 从app的scheduler_config获取默认值
|
380
|
-
scheduler_config = getattr(app, 'scheduler_config', {})
|
381
|
-
if interval is None:
|
382
|
-
interval = scheduler_config.get('scan_interval', 0.1)
|
383
|
-
if batch_size is None:
|
384
|
-
batch_size = scheduler_config.get('batch_size', 100)
|
385
|
-
|
386
|
-
# 显示配置信息
|
387
|
-
click.echo("\n" + "=" * 60)
|
388
|
-
click.echo("JetTask Scheduler Configuration")
|
389
|
-
click.echo("=" * 60)
|
390
|
-
click.echo(f"App: {app_str or 'auto-discovered'}")
|
391
|
-
click.echo(f"Redis URL: {redis_url}")
|
392
|
-
click.echo(f"PostgreSQL: {pg_url or 'Not configured'}")
|
393
|
-
if pg_url and not (pg_url.startswith('postgresql://') or pg_url.startswith('postgres://')):
|
394
|
-
click.echo(f" Source: From app configuration")
|
395
|
-
click.echo(f"Interval: {interval} seconds (from {'CLI' if '--interval' in sys.argv else 'app config'})")
|
396
|
-
click.echo(f"Batch Size: {batch_size} (from {'CLI' if '--batch-size' in sys.argv else 'app config'})")
|
397
|
-
click.echo(f"Debug: {'Enabled' if debug else 'Disabled'}")
|
398
|
-
click.echo("=" * 60)
|
399
|
-
|
400
|
-
# 检查 PostgreSQL 配置
|
401
|
-
if not pg_url:
|
402
|
-
click.echo("\n" + "=" * 60)
|
403
|
-
click.echo("ERROR: PostgreSQL configuration is required for scheduler")
|
404
|
-
click.echo("=" * 60)
|
405
|
-
click.echo("\nThe scheduler requires PostgreSQL to:")
|
406
|
-
click.echo(" • Store scheduled task definitions")
|
407
|
-
click.echo(" • Track task execution history")
|
408
|
-
click.echo(" • Manage cron schedules")
|
409
|
-
click.echo("\nPlease configure PostgreSQL using one of these methods:")
|
410
|
-
click.echo("\n1. Command line option:")
|
411
|
-
click.echo(" jettask scheduler --pg-url postgresql://user:pass@localhost/db")
|
412
|
-
click.echo("\n2. Environment variable:")
|
413
|
-
click.echo(" export JETTASK_PG_URL=postgresql://user:pass@localhost/db")
|
414
|
-
click.echo(" jettask scheduler")
|
415
|
-
click.echo("\n3. Example with local PostgreSQL:")
|
416
|
-
click.echo(" jettask scheduler --pg-url postgresql://jettask:123456@localhost/jettask")
|
417
|
-
click.echo("=" * 60)
|
418
|
-
sys.exit(1)
|
419
|
-
|
420
|
-
# 创建调度器实例
|
421
|
-
click.echo("\nUsing PostgreSQL for scheduled task management")
|
422
|
-
manager = ScheduledTaskManager(pg_url)
|
423
|
-
scheduler_instance = TaskScheduler(
|
424
|
-
app=app,
|
425
|
-
redis_url=redis_url,
|
426
|
-
db_manager=manager,
|
427
|
-
scan_interval=interval,
|
428
|
-
batch_size=batch_size
|
429
|
-
)
|
430
|
-
|
431
|
-
# 运行调度器
|
432
|
-
async def run_scheduler():
|
433
|
-
"""运行调度器的异步函数"""
|
434
|
-
try:
|
435
|
-
click.echo("\nStarting scheduler...")
|
436
|
-
# 先连接
|
437
|
-
await scheduler_instance.connect()
|
438
|
-
# 然后运行
|
439
|
-
await scheduler_instance.run()
|
440
|
-
except KeyboardInterrupt:
|
441
|
-
click.echo("\nReceived shutdown signal, stopping scheduler...")
|
442
|
-
except Exception as e:
|
443
|
-
click.echo(f"\nScheduler error: {e}", err=True)
|
444
|
-
if debug:
|
445
|
-
import traceback
|
446
|
-
traceback.print_exc()
|
447
|
-
finally:
|
448
|
-
# 停止调度器
|
449
|
-
scheduler_instance.stop()
|
450
|
-
|
451
|
-
# 强制清理leader锁
|
452
|
-
if scheduler_instance.is_leader and scheduler_instance.redis:
|
453
|
-
try:
|
454
|
-
leader_key = f"{scheduler_instance.redis_prefix}:leader"
|
455
|
-
await scheduler_instance.redis.delete(leader_key)
|
456
|
-
click.echo("Leader lock cleaned up")
|
457
|
-
except Exception as e:
|
458
|
-
click.echo(f"Failed to cleanup leader lock: {e}", err=True)
|
459
|
-
|
460
|
-
# 确保断开连接
|
461
|
-
await scheduler_instance.disconnect()
|
462
|
-
click.echo("Scheduler stopped")
|
463
|
-
|
464
|
-
# 启动事件循环
|
465
476
|
try:
|
466
|
-
asyncio.run(
|
477
|
+
asyncio.run(run_manager())
|
467
478
|
except KeyboardInterrupt:
|
468
|
-
click.echo("\
|
469
|
-
except Exception as e:
|
470
|
-
click.echo(f"Fatal error: {e}", err=True)
|
471
|
-
sys.exit(1)
|
479
|
+
click.echo("\nShutdown complete")
|
472
480
|
|
473
481
|
def main():
|
474
482
|
"""主入口函数"""
|