@h-ai/scheduler 0.1.0-alpha.53 → 0.1.0-alpha.55
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.
- package/README.md +14 -0
- package/dist/index.d.ts +7 -3
- package/dist/index.js +286 -118
- package/dist/index.js.map +1 -1
- package/dist/scheduler-js-worker.d.ts +22 -0
- package/dist/scheduler-js-worker.js +20 -0
- package/dist/scheduler-js-worker.js.map +1 -0
- package/package.json +9 -5
package/dist/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { cache } from '@h-ai/cache';
|
|
3
|
-
import { core,
|
|
3
|
+
import { core, ok, err } from '@h-ai/core';
|
|
4
4
|
import { reldb, BaseReldbCrudRepository } from '@h-ai/reldb';
|
|
5
5
|
import { createHash } from 'crypto';
|
|
6
6
|
import { Script } from 'vm';
|
|
7
|
+
import { Worker } from 'worker_threads';
|
|
7
8
|
import { Cron } from 'croner';
|
|
8
9
|
|
|
9
10
|
// src/scheduler-config.ts
|
|
@@ -29,16 +30,22 @@ var en_US_default = {
|
|
|
29
30
|
scheduler_initFailed: "Failed to initialize scheduler: {error}",
|
|
30
31
|
scheduler_alreadyRunning: "Scheduler is already running",
|
|
31
32
|
scheduler_notRunning: "Scheduler is not running",
|
|
33
|
+
scheduler_closing: "Scheduler is closing; task execution cancelled",
|
|
34
|
+
scheduler_definitionsChanged: "Task definitions changed during refresh; retry the operation",
|
|
35
|
+
scheduler_taskNoLongerDue: "Task {taskId} was removed, disabled, or rescheduled",
|
|
32
36
|
scheduler_taskNotFound: "Task not found: {taskId}",
|
|
33
37
|
scheduler_taskAlreadyExists: "Task already exists: {taskId}",
|
|
34
38
|
scheduler_invalidCron: "Invalid cron expression: {expression}",
|
|
35
39
|
scheduler_executionFailed: "Task execution failed: {error}",
|
|
36
40
|
scheduler_jsExecutionFailed: "JS code execution failed: {error}",
|
|
41
|
+
scheduler_jsTimedOut: "JS task timed out after {timeout}ms",
|
|
37
42
|
scheduler_apiExecutionFailed: "API call failed: {error}",
|
|
43
|
+
scheduler_apiResponseTooLarge: "API response exceeds the {maxResponseBytes} byte limit",
|
|
38
44
|
scheduler_dbSaveFailed: "Failed to save execution log: {error}",
|
|
39
45
|
scheduler_dbNotInitialized: "Database not initialized, cannot save execution logs",
|
|
40
46
|
scheduler_configError: "Scheduler config validation failed: {error}",
|
|
41
47
|
scheduler_taskIdEmpty: "Task ID cannot be empty",
|
|
48
|
+
scheduler_invalidPersistedTask: "Invalid persisted task {taskId}: {error}",
|
|
42
49
|
scheduler_taskRunning: "Task is currently running: {taskId}",
|
|
43
50
|
scheduler_invalidApiConfig: "Invalid api_config for task: {taskId}",
|
|
44
51
|
scheduler_lockAcquireFailed: "Failed to acquire distributed lock, another node already holds it: {taskId}",
|
|
@@ -57,16 +64,22 @@ var zh_CN_default = {
|
|
|
57
64
|
scheduler_initFailed: "\u5B9A\u65F6\u4EFB\u52A1\u6A21\u5757\u521D\u59CB\u5316\u5931\u8D25: {error}",
|
|
58
65
|
scheduler_alreadyRunning: "\u5B9A\u65F6\u4EFB\u52A1\u8C03\u5EA6\u5668\u5DF2\u5728\u8FD0\u884C\u4E2D",
|
|
59
66
|
scheduler_notRunning: "\u5B9A\u65F6\u4EFB\u52A1\u8C03\u5EA6\u5668\u672A\u8FD0\u884C",
|
|
67
|
+
scheduler_closing: "\u8C03\u5EA6\u5668\u6B63\u5728\u5173\u95ED\uFF0C\u4EFB\u52A1\u6267\u884C\u5DF2\u53D6\u6D88",
|
|
68
|
+
scheduler_definitionsChanged: "\u5237\u65B0\u671F\u95F4\u4EFB\u52A1\u5B9A\u4E49\u5DF2\u53D8\u5316\uFF0C\u8BF7\u91CD\u8BD5\u64CD\u4F5C",
|
|
69
|
+
scheduler_taskNoLongerDue: "\u4EFB\u52A1 {taskId} \u5DF2\u5220\u9664\u3001\u7981\u7528\u6216\u6539\u53D8\u8C03\u5EA6\u65F6\u95F4",
|
|
60
70
|
scheduler_taskNotFound: "\u4EFB\u52A1\u672A\u627E\u5230: {taskId}",
|
|
61
71
|
scheduler_taskAlreadyExists: "\u4EFB\u52A1\u5DF2\u5B58\u5728: {taskId}",
|
|
62
72
|
scheduler_invalidCron: "\u65E0\u6548\u7684 cron \u8868\u8FBE\u5F0F: {expression}",
|
|
63
73
|
scheduler_executionFailed: "\u4EFB\u52A1\u6267\u884C\u5931\u8D25: {error}",
|
|
64
74
|
scheduler_jsExecutionFailed: "JS \u4EE3\u7801\u6267\u884C\u5931\u8D25: {error}",
|
|
75
|
+
scheduler_jsTimedOut: "JS \u4EFB\u52A1\u6267\u884C\u8D85\u8FC7 {timeout} \u6BEB\u79D2",
|
|
65
76
|
scheduler_apiExecutionFailed: "API \u8C03\u7528\u5931\u8D25: {error}",
|
|
77
|
+
scheduler_apiResponseTooLarge: "API \u54CD\u5E94\u8D85\u8FC7 {maxResponseBytes} \u5B57\u8282\u4E0A\u9650",
|
|
66
78
|
scheduler_dbSaveFailed: "\u4FDD\u5B58\u6267\u884C\u8BB0\u5F55\u5931\u8D25: {error}",
|
|
67
79
|
scheduler_dbNotInitialized: "\u6570\u636E\u5E93\u672A\u521D\u59CB\u5316\uFF0C\u65E0\u6CD5\u4FDD\u5B58\u6267\u884C\u8BB0\u5F55",
|
|
68
80
|
scheduler_configError: "\u8C03\u5EA6\u5668\u914D\u7F6E\u6821\u9A8C\u5931\u8D25\uFF1A{error}",
|
|
69
81
|
scheduler_taskIdEmpty: "\u4EFB\u52A1 ID \u4E0D\u80FD\u4E3A\u7A7A",
|
|
82
|
+
scheduler_invalidPersistedTask: "\u6301\u4E45\u5316\u4EFB\u52A1 {taskId} \u65E0\u6548\uFF1A{error}",
|
|
70
83
|
scheduler_taskRunning: "\u4EFB\u52A1\u6B63\u5728\u6267\u884C\u4E2D: {taskId}",
|
|
71
84
|
scheduler_invalidApiConfig: "\u4EFB\u52A1 api_config \u683C\u5F0F\u65E0\u6548: {taskId}",
|
|
72
85
|
scheduler_lockAcquireFailed: "\u83B7\u53D6\u5206\u5E03\u5F0F\u9501\u5931\u8D25\uFF0C\u5176\u4ED6\u8282\u70B9\u5DF2\u83B7\u9501: {taskId}",
|
|
@@ -451,18 +464,15 @@ var SchedulerTaskRepository = class extends BaseReldbCrudRepository {
|
|
|
451
464
|
for (const row of rows.data) {
|
|
452
465
|
const paramsResult = TaskParamsSchema.safeParse(row.params ?? {});
|
|
453
466
|
if (!paramsResult.success) {
|
|
454
|
-
|
|
455
|
-
continue;
|
|
467
|
+
return err(HaiSchedulerError.CONFIG_ERROR, schedulerM("scheduler_invalidPersistedTask", { params: { taskId: row.taskId, error: paramsResult.error.message } }));
|
|
456
468
|
}
|
|
457
469
|
const handlerResult = row.handler == null ? { success: true, data: void 0 } : TaskHandlerConfigSchema.safeParse(row.handler);
|
|
458
470
|
if (!handlerResult.success) {
|
|
459
|
-
|
|
460
|
-
continue;
|
|
471
|
+
return err(HaiSchedulerError.CONFIG_ERROR, schedulerM("scheduler_invalidPersistedTask", { params: { taskId: row.taskId, error: handlerResult.error.message } }));
|
|
461
472
|
}
|
|
462
473
|
const retryResult = row.retry == null ? { success: true, data: void 0 } : TaskRetryPolicySchema.safeParse(row.retry);
|
|
463
474
|
if (!retryResult.success) {
|
|
464
|
-
|
|
465
|
-
continue;
|
|
475
|
+
return err(HaiSchedulerError.CONFIG_ERROR, schedulerM("scheduler_invalidPersistedTask", { params: { taskId: row.taskId, error: retryResult.error.message } }));
|
|
466
476
|
}
|
|
467
477
|
tasks.push({
|
|
468
478
|
id: row.taskId,
|
|
@@ -490,6 +500,18 @@ var SchedulerTaskRepository = class extends BaseReldbCrudRepository {
|
|
|
490
500
|
}
|
|
491
501
|
}
|
|
492
502
|
};
|
|
503
|
+
|
|
504
|
+
// src/scheduler-cancellation.ts
|
|
505
|
+
async function waitWithSignal(operation, signal) {
|
|
506
|
+
signal.throwIfAborted();
|
|
507
|
+
return new Promise((resolve, reject) => {
|
|
508
|
+
const aborted = () => reject(signal.reason);
|
|
509
|
+
signal.addEventListener("abort", aborted, { once: true });
|
|
510
|
+
Promise.resolve().then(operation).then(resolve, reject).finally(() => {
|
|
511
|
+
signal.removeEventListener("abort", aborted);
|
|
512
|
+
});
|
|
513
|
+
});
|
|
514
|
+
}
|
|
493
515
|
var compiledHandlerCache = /* @__PURE__ */ new Map();
|
|
494
516
|
function createCacheKey(config) {
|
|
495
517
|
const keySource = `${config.code}:${config.timeout ?? 0}`;
|
|
@@ -504,32 +526,36 @@ function compileJsTaskHandler(config) {
|
|
|
504
526
|
if (cachedHandler)
|
|
505
527
|
return ok(cachedHandler);
|
|
506
528
|
try {
|
|
507
|
-
|
|
508
|
-
const
|
|
509
|
-
|
|
510
|
-
return err(
|
|
511
|
-
HaiSchedulerError.JS_COMPILE_FAILED,
|
|
512
|
-
schedulerM("scheduler_jsCompileFailed", { params: { error: "Compiled result is not a function" } })
|
|
513
|
-
);
|
|
514
|
-
}
|
|
515
|
-
const handler = async (context) => {
|
|
516
|
-
const functionResult = candidate(context);
|
|
517
|
-
if (!config.timeout || config.timeout <= 0)
|
|
518
|
-
return functionResult;
|
|
529
|
+
void new Script(`(${config.code})`);
|
|
530
|
+
const handler = async (context, signal) => {
|
|
531
|
+
signal?.throwIfAborted();
|
|
519
532
|
return await new Promise((resolve, reject) => {
|
|
520
|
-
const
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
+
const timeout = config.timeout ?? 3e4;
|
|
534
|
+
const worker = new Worker(new URL("./scheduler-js-worker.js", import.meta.url), {
|
|
535
|
+
workerData: { code: config.code, context },
|
|
536
|
+
execArgv: []
|
|
537
|
+
});
|
|
538
|
+
let settled = false;
|
|
539
|
+
let timer;
|
|
540
|
+
const aborted = () => finish(new Error(schedulerM("scheduler_closing")));
|
|
541
|
+
function finish(error, result) {
|
|
542
|
+
if (settled)
|
|
543
|
+
return;
|
|
544
|
+
settled = true;
|
|
545
|
+
clearTimeout(timer);
|
|
546
|
+
signal?.removeEventListener("abort", aborted);
|
|
547
|
+
void worker.terminate().then(() => error ? reject(error) : resolve(result), reject);
|
|
548
|
+
}
|
|
549
|
+
timer = setTimeout(() => finish(new Error(schedulerM("scheduler_jsTimedOut", { params: { timeout } }))), timeout);
|
|
550
|
+
signal?.addEventListener("abort", aborted, { once: true });
|
|
551
|
+
worker.once("error", (error) => finish(error instanceof Error ? error : new Error(String(error))));
|
|
552
|
+
worker.once("exit", (code) => finish(new Error(`JS worker exited before returning a result (${code})`)));
|
|
553
|
+
worker.once("message", (message) => {
|
|
554
|
+
if (message.success)
|
|
555
|
+
finish(void 0, message.data);
|
|
556
|
+
else
|
|
557
|
+
finish(new Error(message.message));
|
|
558
|
+
});
|
|
533
559
|
});
|
|
534
560
|
};
|
|
535
561
|
compiledHandlerCache.set(cacheKey, handler);
|
|
@@ -584,7 +610,7 @@ async function notifyTaskStart(hooks, event) {
|
|
|
584
610
|
if (!hooks.onTaskStart)
|
|
585
611
|
return;
|
|
586
612
|
try {
|
|
587
|
-
await hooks.onTaskStart(event);
|
|
613
|
+
await waitWithSignal(() => hooks.onTaskStart(event), event.signal);
|
|
588
614
|
} catch (error) {
|
|
589
615
|
logger3.warn("Task start hook failed", { taskId: event.task.id, error });
|
|
590
616
|
}
|
|
@@ -593,7 +619,7 @@ async function notifyTaskInterrupted(hooks, event) {
|
|
|
593
619
|
if (!hooks.onTaskInterrupted)
|
|
594
620
|
return;
|
|
595
621
|
try {
|
|
596
|
-
await hooks.onTaskInterrupted(event);
|
|
622
|
+
await waitWithSignal(() => hooks.onTaskInterrupted(event), event.signal);
|
|
597
623
|
} catch (error) {
|
|
598
624
|
logger3.warn("Task interrupted hook failed", { taskId: event.task.id, error });
|
|
599
625
|
}
|
|
@@ -602,7 +628,7 @@ async function notifyTaskFinish(hooks, event) {
|
|
|
602
628
|
if (!hooks.onTaskFinish)
|
|
603
629
|
return;
|
|
604
630
|
try {
|
|
605
|
-
await hooks.onTaskFinish(event);
|
|
631
|
+
await waitWithSignal(() => hooks.onTaskFinish(event), event.signal);
|
|
606
632
|
} catch (error) {
|
|
607
633
|
logger3.warn("Task finish hook failed", { taskId: event.task.id, error });
|
|
608
634
|
}
|
|
@@ -639,10 +665,10 @@ async function saveExecutionLog(task, trigger, taskType, status, startedAt, fini
|
|
|
639
665
|
async function saveInterruptedTaskLog(task, trigger, reason, taskType, startedAt, finishedAt) {
|
|
640
666
|
return saveExecutionLog(task, trigger, taskType, "interrupted", startedAt, finishedAt, null, reason);
|
|
641
667
|
}
|
|
642
|
-
async function interruptTask(task, trigger, reason, hooks) {
|
|
668
|
+
async function interruptTask(task, trigger, reason, hooks, signal = new AbortController().signal) {
|
|
643
669
|
const startedAt = Date.now();
|
|
644
670
|
const taskType = resolveTaskType(task, hooks);
|
|
645
|
-
await notifyTaskStart(hooks, { task, trigger, startedAt });
|
|
671
|
+
await notifyTaskStart(hooks, { task, trigger, startedAt, signal });
|
|
646
672
|
const finishedAt = Date.now();
|
|
647
673
|
const interruptedLog = await saveInterruptedTaskLog(task, trigger, reason, taskType, startedAt, finishedAt);
|
|
648
674
|
await notifyTaskInterrupted(hooks, {
|
|
@@ -650,11 +676,12 @@ async function interruptTask(task, trigger, reason, hooks) {
|
|
|
650
676
|
trigger,
|
|
651
677
|
startedAt,
|
|
652
678
|
interruptedAt: finishedAt,
|
|
679
|
+
signal,
|
|
653
680
|
reason
|
|
654
681
|
});
|
|
655
682
|
return interruptedLog;
|
|
656
683
|
}
|
|
657
|
-
async function executeJsTask(task, context) {
|
|
684
|
+
async function executeJsTask(task, context, signal = new AbortController().signal) {
|
|
658
685
|
const handler = task.handler;
|
|
659
686
|
if (!handler || handler.kind !== "js") {
|
|
660
687
|
return err(
|
|
@@ -666,7 +693,7 @@ async function executeJsTask(task, context) {
|
|
|
666
693
|
if (!compileResult.success)
|
|
667
694
|
return compileResult;
|
|
668
695
|
try {
|
|
669
|
-
const result = await compileResult.data(context);
|
|
696
|
+
const result = await compileResult.data(context, signal);
|
|
670
697
|
return ok(result !== void 0 ? JSON.stringify(result) : null);
|
|
671
698
|
} catch (error) {
|
|
672
699
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -678,7 +705,7 @@ async function executeJsTask(task, context) {
|
|
|
678
705
|
);
|
|
679
706
|
}
|
|
680
707
|
}
|
|
681
|
-
async function executeApiTask(task, context) {
|
|
708
|
+
async function executeApiTask(task, context, signal = new AbortController().signal) {
|
|
682
709
|
const handler = task.handler;
|
|
683
710
|
if (!handler || handler.kind !== "api") {
|
|
684
711
|
return err(
|
|
@@ -686,7 +713,9 @@ async function executeApiTask(task, context) {
|
|
|
686
713
|
schedulerM("scheduler_invalidHandlerConfig", { params: { taskId: task.id } })
|
|
687
714
|
);
|
|
688
715
|
}
|
|
689
|
-
const { url, method = "GET", headers, body, timeout = 3e4 } = handler;
|
|
716
|
+
const { url, method = "GET", headers, body, timeout = 3e4, maxResponseBytes = 1024 * 1024 } = handler;
|
|
717
|
+
if (!Number.isSafeInteger(maxResponseBytes) || maxResponseBytes <= 0)
|
|
718
|
+
return err(HaiSchedulerError.CONFIG_ERROR, schedulerM("scheduler_invalidHandlerConfig", { params: { taskId: task.id } }));
|
|
690
719
|
const controller = new AbortController();
|
|
691
720
|
const timer = setTimeout(() => controller.abort(), timeout);
|
|
692
721
|
try {
|
|
@@ -696,11 +725,31 @@ async function executeApiTask(task, context) {
|
|
|
696
725
|
...headers,
|
|
697
726
|
...body !== void 0 ? { "Content-Type": "application/json" } : {}
|
|
698
727
|
},
|
|
699
|
-
signal: controller.signal,
|
|
728
|
+
signal: AbortSignal.any([signal, controller.signal]),
|
|
700
729
|
...body !== void 0 ? { body: JSON.stringify(body) } : {}
|
|
701
730
|
});
|
|
702
|
-
|
|
703
|
-
|
|
731
|
+
let receivedBytes = 0;
|
|
732
|
+
let responseText = "";
|
|
733
|
+
const reader = response.body?.getReader();
|
|
734
|
+
if (reader) {
|
|
735
|
+
const decoder = new TextDecoder();
|
|
736
|
+
try {
|
|
737
|
+
while (true) {
|
|
738
|
+
const chunk = await reader.read();
|
|
739
|
+
if (chunk.done)
|
|
740
|
+
break;
|
|
741
|
+
receivedBytes += chunk.value.byteLength;
|
|
742
|
+
if (receivedBytes > maxResponseBytes) {
|
|
743
|
+
controller.abort();
|
|
744
|
+
return err(HaiSchedulerError.API_EXECUTION_FAILED, schedulerM("scheduler_apiResponseTooLarge", { params: { maxResponseBytes } }));
|
|
745
|
+
}
|
|
746
|
+
responseText += decoder.decode(chunk.value, { stream: true });
|
|
747
|
+
}
|
|
748
|
+
responseText += decoder.decode();
|
|
749
|
+
} finally {
|
|
750
|
+
reader.releaseLock();
|
|
751
|
+
}
|
|
752
|
+
}
|
|
704
753
|
if (!response.ok) {
|
|
705
754
|
logger3.error("API task returned non-OK status", {
|
|
706
755
|
taskId: context.taskId,
|
|
@@ -716,7 +765,6 @@ async function executeApiTask(task, context) {
|
|
|
716
765
|
}
|
|
717
766
|
return ok(responseText || null);
|
|
718
767
|
} catch (error) {
|
|
719
|
-
clearTimeout(timer);
|
|
720
768
|
const message = error instanceof Error ? error.message : String(error);
|
|
721
769
|
logger3.error("API task execution failed", { taskId: context.taskId, error: message, url: sanitizeUrl(url) });
|
|
722
770
|
return err(
|
|
@@ -724,9 +772,11 @@ async function executeApiTask(task, context) {
|
|
|
724
772
|
schedulerM("scheduler_apiExecutionFailed", { params: { error: message } }),
|
|
725
773
|
error
|
|
726
774
|
);
|
|
775
|
+
} finally {
|
|
776
|
+
clearTimeout(timer);
|
|
727
777
|
}
|
|
728
778
|
}
|
|
729
|
-
async function executeHookTask(task, context, hooks, startedAt) {
|
|
779
|
+
async function executeHookTask(task, context, hooks, startedAt, signal) {
|
|
730
780
|
if (!hooks.onTaskExecute) {
|
|
731
781
|
return err(
|
|
732
782
|
HaiSchedulerError.EXECUTION_FAILED,
|
|
@@ -737,10 +787,11 @@ async function executeHookTask(task, context, hooks, startedAt) {
|
|
|
737
787
|
task,
|
|
738
788
|
trigger: context.trigger,
|
|
739
789
|
startedAt,
|
|
740
|
-
context
|
|
790
|
+
context,
|
|
791
|
+
signal
|
|
741
792
|
};
|
|
742
793
|
try {
|
|
743
|
-
const result = await hooks.onTaskExecute(executeEvent);
|
|
794
|
+
const result = await waitWithSignal(() => hooks.onTaskExecute(executeEvent), signal);
|
|
744
795
|
return ok(result !== void 0 ? JSON.stringify(result) : null);
|
|
745
796
|
} catch (error) {
|
|
746
797
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -752,21 +803,25 @@ async function executeHookTask(task, context, hooks, startedAt) {
|
|
|
752
803
|
);
|
|
753
804
|
}
|
|
754
805
|
}
|
|
755
|
-
async function executeTask(task, trigger, hooks) {
|
|
806
|
+
async function executeTask(task, trigger, hooks, signal = new AbortController().signal) {
|
|
756
807
|
const startedAt = Date.now();
|
|
757
808
|
const taskType = resolveTaskType(task, hooks);
|
|
758
809
|
const context = createTaskContext(task, trigger);
|
|
759
|
-
await notifyTaskStart(hooks, { task, trigger, startedAt });
|
|
810
|
+
await notifyTaskStart(hooks, { task, trigger, startedAt, signal });
|
|
760
811
|
let executionResult;
|
|
761
812
|
let shouldNotifyInterrupted = false;
|
|
762
|
-
if (
|
|
763
|
-
executionResult =
|
|
813
|
+
if (signal.aborted) {
|
|
814
|
+
executionResult = err(HaiSchedulerError.EXECUTION_FAILED, schedulerM("scheduler_closing"));
|
|
815
|
+
shouldNotifyInterrupted = true;
|
|
816
|
+
} else if (!task.handler) {
|
|
817
|
+
executionResult = await executeHookTask(task, context, hooks, startedAt, signal);
|
|
764
818
|
shouldNotifyInterrupted = !executionResult.success;
|
|
765
819
|
} else if (task.handler.kind === "api") {
|
|
766
|
-
executionResult = await executeApiTask(task, context);
|
|
820
|
+
executionResult = await executeApiTask(task, context, signal);
|
|
767
821
|
} else {
|
|
768
|
-
executionResult = await executeJsTask(task, context);
|
|
822
|
+
executionResult = await executeJsTask(task, context, signal);
|
|
769
823
|
}
|
|
824
|
+
shouldNotifyInterrupted ||= signal.aborted;
|
|
770
825
|
const finishedAt = Date.now();
|
|
771
826
|
const status = executionResult.success ? "success" : shouldNotifyInterrupted ? "interrupted" : "failed";
|
|
772
827
|
const log = await saveExecutionLog(
|
|
@@ -785,11 +840,12 @@ async function executeTask(task, trigger, hooks) {
|
|
|
785
840
|
trigger,
|
|
786
841
|
startedAt,
|
|
787
842
|
interruptedAt: finishedAt,
|
|
843
|
+
signal,
|
|
788
844
|
reason: executionResult.error.message
|
|
789
845
|
});
|
|
790
846
|
}
|
|
791
847
|
if (status !== "interrupted")
|
|
792
|
-
await notifyTaskFinish(hooks, { task, trigger, startedAt, finishedAt, log });
|
|
848
|
+
await notifyTaskFinish(hooks, { task, trigger, startedAt, finishedAt, log, signal });
|
|
793
849
|
return log;
|
|
794
850
|
}
|
|
795
851
|
function parseCronExpression(expression, timezone) {
|
|
@@ -808,6 +864,8 @@ function parseCronExpression(expression, timezone) {
|
|
|
808
864
|
var logger4 = core.logger.child({ module: "scheduler", scope: "functions" });
|
|
809
865
|
var taskRegistry = /* @__PURE__ */ new Map();
|
|
810
866
|
var cronCache = /* @__PURE__ */ new Map();
|
|
867
|
+
var persistedTaskIds = /* @__PURE__ */ new Set();
|
|
868
|
+
var definitionEpoch = 0;
|
|
811
869
|
var currentHooks = {};
|
|
812
870
|
function getTaskRegistry() {
|
|
813
871
|
return taskRegistry;
|
|
@@ -819,9 +877,11 @@ function hasTask(taskId) {
|
|
|
819
877
|
return taskRegistry.has(taskId);
|
|
820
878
|
}
|
|
821
879
|
function setTask(taskId, task) {
|
|
880
|
+
definitionEpoch++;
|
|
822
881
|
taskRegistry.set(taskId, task);
|
|
823
882
|
}
|
|
824
883
|
function deleteTask(taskId) {
|
|
884
|
+
definitionEpoch++;
|
|
825
885
|
taskRegistry.delete(taskId);
|
|
826
886
|
}
|
|
827
887
|
function getCron(taskId) {
|
|
@@ -843,6 +903,8 @@ function getHooks() {
|
|
|
843
903
|
return currentHooks;
|
|
844
904
|
}
|
|
845
905
|
function resetTaskState() {
|
|
906
|
+
definitionEpoch++;
|
|
907
|
+
persistedTaskIds.clear();
|
|
846
908
|
taskRegistry.clear();
|
|
847
909
|
cronCache.clear();
|
|
848
910
|
currentHooks = {};
|
|
@@ -876,46 +938,45 @@ function validateRetryPolicy(taskId, retry) {
|
|
|
876
938
|
}
|
|
877
939
|
async function loadPersistedTasks(taskRepo2) {
|
|
878
940
|
if (!taskRepo2)
|
|
879
|
-
return;
|
|
941
|
+
return ok(void 0);
|
|
942
|
+
const epoch = definitionEpoch;
|
|
880
943
|
const loadResult = await taskRepo2.loadTasks();
|
|
881
|
-
if (!loadResult.success)
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
944
|
+
if (!loadResult.success)
|
|
945
|
+
return loadResult;
|
|
946
|
+
if (epoch !== definitionEpoch)
|
|
947
|
+
return err(HaiSchedulerError.EXECUTION_FAILED, schedulerM("scheduler_definitionsChanged"));
|
|
948
|
+
const nextTasks = new Map([...taskRegistry].filter(([id]) => !persistedTaskIds.has(id)));
|
|
949
|
+
const nextCrons = new Map([...cronCache].filter(([id]) => !persistedTaskIds.has(id)));
|
|
886
950
|
for (const task of loadResult.data) {
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
951
|
+
if (!task.id)
|
|
952
|
+
return err(HaiSchedulerError.CONFIG_ERROR, schedulerM("scheduler_taskIdEmpty"));
|
|
953
|
+
if (nextTasks.has(task.id))
|
|
954
|
+
return err(HaiSchedulerError.TASK_ALREADY_EXISTS, schedulerM("scheduler_taskAlreadyExists", { params: { taskId: task.id } }));
|
|
955
|
+
const cronResult = parseCronExpression(task.cron, task.timezone);
|
|
956
|
+
if (!cronResult.success)
|
|
957
|
+
return cronResult;
|
|
958
|
+
nextTasks.set(task.id, task);
|
|
959
|
+
nextCrons.set(task.id, cronResult.data);
|
|
894
960
|
}
|
|
895
|
-
|
|
896
|
-
|
|
961
|
+
definitionEpoch++;
|
|
962
|
+
taskRegistry.clear();
|
|
963
|
+
cronCache.clear();
|
|
964
|
+
persistedTaskIds.clear();
|
|
965
|
+
for (const [id, task] of nextTasks)
|
|
966
|
+
taskRegistry.set(id, task);
|
|
967
|
+
for (const [id, cron] of nextCrons)
|
|
968
|
+
cronCache.set(id, cron);
|
|
969
|
+
for (const task of loadResult.data)
|
|
970
|
+
persistedTaskIds.add(task.id);
|
|
971
|
+
return ok(void 0);
|
|
897
972
|
}
|
|
898
973
|
async function loadConfigTasks(tasks) {
|
|
899
|
-
let loadedCount = 0;
|
|
900
974
|
for (const task of tasks) {
|
|
901
|
-
if (!task.id) {
|
|
902
|
-
logger4.warn("Skipping config task with empty id");
|
|
903
|
-
continue;
|
|
904
|
-
}
|
|
905
|
-
if (hasTask(task.id)) {
|
|
906
|
-
logger4.debug("Skipping config task, already registered from DB", { taskId: task.id });
|
|
907
|
-
continue;
|
|
908
|
-
}
|
|
909
975
|
const registerResult = await registerTask(task);
|
|
910
|
-
if (registerResult.success)
|
|
911
|
-
|
|
912
|
-
logger4.debug("Loaded config task", { taskId: task.id, taskName: task.name });
|
|
913
|
-
continue;
|
|
914
|
-
}
|
|
915
|
-
logger4.warn("Failed to load config task", { taskId: task.id, error: registerResult.error.message });
|
|
976
|
+
if (!registerResult.success)
|
|
977
|
+
return registerResult;
|
|
916
978
|
}
|
|
917
|
-
|
|
918
|
-
logger4.info("Loaded config tasks", { count: loadedCount });
|
|
979
|
+
return ok(void 0);
|
|
919
980
|
}
|
|
920
981
|
async function registerTask(task, taskRepo2) {
|
|
921
982
|
if (!task.id) {
|
|
@@ -959,6 +1020,7 @@ async function registerTask(task, taskRepo2) {
|
|
|
959
1020
|
saveResult.error
|
|
960
1021
|
);
|
|
961
1022
|
}
|
|
1023
|
+
persistedTaskIds.add(task.id);
|
|
962
1024
|
}
|
|
963
1025
|
return ok(void 0);
|
|
964
1026
|
}
|
|
@@ -1058,7 +1120,9 @@ async function queryTaskLogs(logRepo2, options) {
|
|
|
1058
1120
|
}
|
|
1059
1121
|
var logger5 = core.logger.child({ module: "scheduler", scope: "runner" });
|
|
1060
1122
|
var runningTasks = /* @__PURE__ */ new Set();
|
|
1123
|
+
var activeRuns = /* @__PURE__ */ new Map();
|
|
1061
1124
|
var tickTimer = null;
|
|
1125
|
+
var tickInProgress = false;
|
|
1062
1126
|
var lastTickMinute = -1;
|
|
1063
1127
|
var currentNodeId = "";
|
|
1064
1128
|
var currentLockTtlSec = 300;
|
|
@@ -1087,10 +1151,25 @@ function resetRunner() {
|
|
|
1087
1151
|
currentLockTtlSec = 300;
|
|
1088
1152
|
currentTaskRepo = null;
|
|
1089
1153
|
}
|
|
1090
|
-
async function sleep(ms) {
|
|
1091
|
-
if (ms <= 0)
|
|
1154
|
+
async function sleep(ms, signal) {
|
|
1155
|
+
if (ms <= 0 || signal.aborted)
|
|
1092
1156
|
return;
|
|
1093
|
-
await new Promise((resolve) =>
|
|
1157
|
+
await new Promise((resolve) => {
|
|
1158
|
+
let timer;
|
|
1159
|
+
const finish = () => {
|
|
1160
|
+
clearTimeout(timer);
|
|
1161
|
+
signal.removeEventListener("abort", finish);
|
|
1162
|
+
resolve();
|
|
1163
|
+
};
|
|
1164
|
+
timer = setTimeout(finish, ms);
|
|
1165
|
+
signal.addEventListener("abort", finish, { once: true });
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
async function drainRunner() {
|
|
1169
|
+
stopTimer();
|
|
1170
|
+
for (const controller of activeRuns.values())
|
|
1171
|
+
controller.abort(new Error(schedulerM("scheduler_closing")));
|
|
1172
|
+
await Promise.allSettled([...activeRuns.keys()]);
|
|
1094
1173
|
}
|
|
1095
1174
|
function resolveRetryBackoffMs(task, attempt) {
|
|
1096
1175
|
const backoff = task.retry?.backoffMs;
|
|
@@ -1099,7 +1178,24 @@ function resolveRetryBackoffMs(task, attempt) {
|
|
|
1099
1178
|
const index = Math.min(Math.max(attempt - 2, 0), backoff.length - 1);
|
|
1100
1179
|
return backoff[index] ?? 0;
|
|
1101
1180
|
}
|
|
1102
|
-
function tick() {
|
|
1181
|
+
async function tick() {
|
|
1182
|
+
if (tickInProgress)
|
|
1183
|
+
return;
|
|
1184
|
+
tickInProgress = true;
|
|
1185
|
+
try {
|
|
1186
|
+
const refreshed = await loadPersistedTasks(currentTaskRepo);
|
|
1187
|
+
if (!refreshed.success) {
|
|
1188
|
+
logger5.warn("Task definition refresh failed; skipping scheduling", { error: refreshed.error.message });
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1191
|
+
if (!tickTimer)
|
|
1192
|
+
return;
|
|
1193
|
+
scheduleDueTasks();
|
|
1194
|
+
} finally {
|
|
1195
|
+
tickInProgress = false;
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
function scheduleDueTasks() {
|
|
1103
1199
|
const now = /* @__PURE__ */ new Date();
|
|
1104
1200
|
const currentMinute = Math.floor(now.getTime() / 6e4);
|
|
1105
1201
|
if (currentMinute === lastTickMinute)
|
|
@@ -1109,7 +1205,7 @@ function tick() {
|
|
|
1109
1205
|
if (task.enabled === false)
|
|
1110
1206
|
continue;
|
|
1111
1207
|
const cron = getCron(taskId);
|
|
1112
|
-
if (!cron || !cron.match(
|
|
1208
|
+
if (!cron || !cron.match(new Date(currentMinute * 6e4)))
|
|
1113
1209
|
continue;
|
|
1114
1210
|
if (runningTasks.has(taskId)) {
|
|
1115
1211
|
logger5.debug("Skipping task already running", { taskId, taskName: task.name });
|
|
@@ -1120,17 +1216,30 @@ function tick() {
|
|
|
1120
1216
|
});
|
|
1121
1217
|
}
|
|
1122
1218
|
}
|
|
1123
|
-
|
|
1219
|
+
function runTask(task, minuteTimestamp, trigger = { type: "manual", source: null }) {
|
|
1220
|
+
const controller = new AbortController();
|
|
1221
|
+
const run = runTaskExecution(task, minuteTimestamp, trigger, controller.signal);
|
|
1222
|
+
activeRuns.set(run, controller);
|
|
1223
|
+
void run.finally(() => activeRuns.delete(run)).catch(() => {
|
|
1224
|
+
});
|
|
1225
|
+
return run;
|
|
1226
|
+
}
|
|
1227
|
+
async function runTaskExecution(task, minuteTimestamp, trigger, signal) {
|
|
1228
|
+
const hooks = getHooks();
|
|
1229
|
+
const repository = currentTaskRepo;
|
|
1124
1230
|
const lockKey = minuteTimestamp !== void 0 ? `hai:scheduler:${task.id}:${minuteTimestamp}` : void 0;
|
|
1125
1231
|
if (cache.isInitialized && lockKey) {
|
|
1126
|
-
const lockResult = await cache.lock.acquire(lockKey, { ttl: currentLockTtlSec, owner: currentNodeId });
|
|
1232
|
+
const lockResult = await waitWithSignal(() => cache.lock.acquire(lockKey, { ttl: currentLockTtlSec, owner: currentNodeId }), signal).catch(() => null);
|
|
1233
|
+
if (!lockResult)
|
|
1234
|
+
return interruptTask(task, trigger, schedulerM("scheduler_closing"), {}, signal);
|
|
1127
1235
|
if (!lockResult.success) {
|
|
1128
1236
|
logger5.warn("Failed to acquire distributed lock, interrupting task", { taskId: task.id, error: lockResult.error.message });
|
|
1129
1237
|
return interruptTask(
|
|
1130
1238
|
task,
|
|
1131
1239
|
trigger,
|
|
1132
1240
|
schedulerM("scheduler_lockAcquireFailed", { params: { taskId: task.id } }),
|
|
1133
|
-
|
|
1241
|
+
hooks,
|
|
1242
|
+
signal
|
|
1134
1243
|
);
|
|
1135
1244
|
}
|
|
1136
1245
|
if (!lockResult.data) {
|
|
@@ -1139,7 +1248,8 @@ async function runTask(task, minuteTimestamp, trigger = { type: "manual", source
|
|
|
1139
1248
|
task,
|
|
1140
1249
|
trigger,
|
|
1141
1250
|
schedulerM("scheduler_lockAcquireFailed", { params: { taskId: task.id } }),
|
|
1142
|
-
|
|
1251
|
+
hooks,
|
|
1252
|
+
signal
|
|
1143
1253
|
);
|
|
1144
1254
|
}
|
|
1145
1255
|
}
|
|
@@ -1148,7 +1258,8 @@ async function runTask(task, minuteTimestamp, trigger = { type: "manual", source
|
|
|
1148
1258
|
task,
|
|
1149
1259
|
trigger,
|
|
1150
1260
|
schedulerM("scheduler_taskRunning", { params: { taskId: task.id } }),
|
|
1151
|
-
|
|
1261
|
+
hooks,
|
|
1262
|
+
signal
|
|
1152
1263
|
);
|
|
1153
1264
|
}
|
|
1154
1265
|
runningTasks.add(task.id);
|
|
@@ -1156,7 +1267,23 @@ async function runTask(task, minuteTimestamp, trigger = { type: "manual", source
|
|
|
1156
1267
|
const maxAttempts = Math.max(1, task.retry?.maxAttempts ?? 1);
|
|
1157
1268
|
let log = null;
|
|
1158
1269
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
1159
|
-
|
|
1270
|
+
if (signal.aborted) {
|
|
1271
|
+
log = await interruptTask(task, trigger, schedulerM("scheduler_closing"), {}, signal);
|
|
1272
|
+
break;
|
|
1273
|
+
}
|
|
1274
|
+
const refreshed = await loadPersistedTasks(repository);
|
|
1275
|
+
if (!refreshed.success) {
|
|
1276
|
+
log = await interruptTask(task, trigger, refreshed.error.message, hooks, signal);
|
|
1277
|
+
break;
|
|
1278
|
+
}
|
|
1279
|
+
const latest = getTask(task.id);
|
|
1280
|
+
const stillDue = minuteTimestamp === void 0 || getCron(task.id)?.match(new Date(minuteTimestamp * 6e4));
|
|
1281
|
+
if (!latest || latest.enabled === false || !stillDue) {
|
|
1282
|
+
log = await interruptTask(task, trigger, schedulerM("scheduler_taskNoLongerDue", { params: { taskId: task.id } }), hooks, signal);
|
|
1283
|
+
break;
|
|
1284
|
+
}
|
|
1285
|
+
task = latest;
|
|
1286
|
+
log = await executeTask(task, trigger, hooks, signal);
|
|
1160
1287
|
if (log.status !== "failed")
|
|
1161
1288
|
break;
|
|
1162
1289
|
if (attempt >= maxAttempts)
|
|
@@ -1168,14 +1295,15 @@ async function runTask(task, minuteTimestamp, trigger = { type: "manual", source
|
|
|
1168
1295
|
maxAttempts,
|
|
1169
1296
|
backoffMs
|
|
1170
1297
|
});
|
|
1171
|
-
await sleep(backoffMs);
|
|
1298
|
+
await sleep(backoffMs, signal);
|
|
1172
1299
|
}
|
|
1173
1300
|
if (!log) {
|
|
1174
1301
|
return interruptTask(
|
|
1175
1302
|
task,
|
|
1176
1303
|
trigger,
|
|
1177
1304
|
schedulerM("scheduler_executionFailed", { params: { error: "Task execution produced no log" } }),
|
|
1178
|
-
|
|
1305
|
+
hooks,
|
|
1306
|
+
signal
|
|
1179
1307
|
);
|
|
1180
1308
|
}
|
|
1181
1309
|
if (log.status === "failed")
|
|
@@ -1184,8 +1312,8 @@ async function runTask(task, minuteTimestamp, trigger = { type: "manual", source
|
|
|
1184
1312
|
logger5.debug("Task execution interrupted", { taskId: task.id, error: log.error });
|
|
1185
1313
|
else
|
|
1186
1314
|
logger5.debug("Task execution succeeded", { taskId: task.id, duration: log.duration });
|
|
1187
|
-
if (task.deleteAfterRun === true) {
|
|
1188
|
-
const unregisterResult = await unregisterTask(task.id,
|
|
1315
|
+
if (task.deleteAfterRun === true && !signal.aborted && log.status !== "interrupted") {
|
|
1316
|
+
const unregisterResult = await unregisterTask(task.id, repository);
|
|
1189
1317
|
if (!unregisterResult.success) {
|
|
1190
1318
|
logger5.warn("Failed to auto-delete one-time task after run", {
|
|
1191
1319
|
taskId: task.id,
|
|
@@ -1215,6 +1343,7 @@ function stopTimer() {
|
|
|
1215
1343
|
var logger6 = core.logger.child({ module: "scheduler", scope: "main" });
|
|
1216
1344
|
var currentConfig = null;
|
|
1217
1345
|
var initInProgress = false;
|
|
1346
|
+
var closeInProgress = null;
|
|
1218
1347
|
var taskRepo = null;
|
|
1219
1348
|
var logRepo = null;
|
|
1220
1349
|
var notInitialized = core.module.createNotInitializedKit(
|
|
@@ -1261,6 +1390,8 @@ var scheduler = {
|
|
|
1261
1390
|
}
|
|
1262
1391
|
initInProgress = true;
|
|
1263
1392
|
try {
|
|
1393
|
+
if (closeInProgress)
|
|
1394
|
+
await closeInProgress;
|
|
1264
1395
|
if (currentConfig) {
|
|
1265
1396
|
logger6.warn("Scheduler module is already initialized, reinitializing");
|
|
1266
1397
|
await scheduler.close();
|
|
@@ -1279,9 +1410,16 @@ var scheduler = {
|
|
|
1279
1410
|
try {
|
|
1280
1411
|
currentConfig = await initDatabase(parseResult.data);
|
|
1281
1412
|
setHooks(hooks);
|
|
1282
|
-
await loadPersistedTasks(taskRepo);
|
|
1283
|
-
if (
|
|
1284
|
-
await
|
|
1413
|
+
const persistedResult = await loadPersistedTasks(taskRepo);
|
|
1414
|
+
if (!persistedResult.success) {
|
|
1415
|
+
await scheduler.close();
|
|
1416
|
+
return persistedResult;
|
|
1417
|
+
}
|
|
1418
|
+
const configResult = await loadConfigTasks(tasks);
|
|
1419
|
+
if (!configResult.success) {
|
|
1420
|
+
await scheduler.close();
|
|
1421
|
+
return configResult;
|
|
1422
|
+
}
|
|
1285
1423
|
logger6.info("Scheduler module initialized", { enableDb: currentConfig.enableDb, taskCount: getTaskRegistry().size });
|
|
1286
1424
|
return ok(void 0);
|
|
1287
1425
|
} catch (error) {
|
|
@@ -1302,6 +1440,12 @@ var scheduler = {
|
|
|
1302
1440
|
async register(task) {
|
|
1303
1441
|
if (!currentConfig)
|
|
1304
1442
|
return notInitialized.result();
|
|
1443
|
+
const config = currentConfig;
|
|
1444
|
+
const refreshed = await loadPersistedTasks(taskRepo);
|
|
1445
|
+
if (!refreshed.success)
|
|
1446
|
+
return refreshed;
|
|
1447
|
+
if (currentConfig !== config)
|
|
1448
|
+
return notInitialized.result();
|
|
1305
1449
|
const registerResult = await registerTask(task, taskRepo);
|
|
1306
1450
|
if (registerResult.success) {
|
|
1307
1451
|
logger6.info("Task registered", { taskId: task.id, taskName: task.name, cron: task.cron });
|
|
@@ -1311,6 +1455,12 @@ var scheduler = {
|
|
|
1311
1455
|
async unregister(taskId) {
|
|
1312
1456
|
if (!currentConfig)
|
|
1313
1457
|
return notInitialized.result();
|
|
1458
|
+
const config = currentConfig;
|
|
1459
|
+
const refreshed = await loadPersistedTasks(taskRepo);
|
|
1460
|
+
if (!refreshed.success)
|
|
1461
|
+
return refreshed;
|
|
1462
|
+
if (currentConfig !== config)
|
|
1463
|
+
return notInitialized.result();
|
|
1314
1464
|
const unregisterResult = await unregisterTask(taskId, taskRepo);
|
|
1315
1465
|
if (unregisterResult.success)
|
|
1316
1466
|
logger6.info("Task unregistered", { taskId });
|
|
@@ -1319,6 +1469,12 @@ var scheduler = {
|
|
|
1319
1469
|
async updateTask(taskId, updates) {
|
|
1320
1470
|
if (!currentConfig)
|
|
1321
1471
|
return notInitialized.result();
|
|
1472
|
+
const config = currentConfig;
|
|
1473
|
+
const refreshed = await loadPersistedTasks(taskRepo);
|
|
1474
|
+
if (!refreshed.success)
|
|
1475
|
+
return refreshed;
|
|
1476
|
+
if (currentConfig !== config)
|
|
1477
|
+
return notInitialized.result();
|
|
1322
1478
|
const updateResult = await updateRegisteredTask(taskId, updates, taskRepo);
|
|
1323
1479
|
if (updateResult.success)
|
|
1324
1480
|
logger6.info("Task updated", { taskId, updates: Object.keys(updates) });
|
|
@@ -1353,6 +1509,12 @@ var scheduler = {
|
|
|
1353
1509
|
async trigger(taskId, options) {
|
|
1354
1510
|
if (!currentConfig)
|
|
1355
1511
|
return notInitialized.result();
|
|
1512
|
+
const config = currentConfig;
|
|
1513
|
+
const refreshed = await loadPersistedTasks(taskRepo);
|
|
1514
|
+
if (!refreshed.success)
|
|
1515
|
+
return refreshed;
|
|
1516
|
+
if (currentConfig !== config)
|
|
1517
|
+
return notInitialized.result();
|
|
1356
1518
|
const task = getTask(taskId);
|
|
1357
1519
|
if (!task) {
|
|
1358
1520
|
return err(
|
|
@@ -1402,21 +1564,27 @@ var scheduler = {
|
|
|
1402
1564
|
return isTimerRunning();
|
|
1403
1565
|
},
|
|
1404
1566
|
async close() {
|
|
1405
|
-
if (
|
|
1406
|
-
|
|
1407
|
-
return;
|
|
1408
|
-
}
|
|
1567
|
+
if (closeInProgress)
|
|
1568
|
+
return closeInProgress;
|
|
1409
1569
|
logger6.info("Closing scheduler module");
|
|
1410
|
-
stopTimer();
|
|
1411
|
-
clearJsTaskHandlerCache();
|
|
1412
|
-
resetTaskState();
|
|
1413
|
-
resetRunner();
|
|
1414
|
-
setLogRepository(null);
|
|
1415
|
-
setTaskRepository(null);
|
|
1416
1570
|
currentConfig = null;
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1571
|
+
stopTimer();
|
|
1572
|
+
closeInProgress = (async () => {
|
|
1573
|
+
await drainRunner();
|
|
1574
|
+
clearJsTaskHandlerCache();
|
|
1575
|
+
resetTaskState();
|
|
1576
|
+
resetRunner();
|
|
1577
|
+
setLogRepository(null);
|
|
1578
|
+
setTaskRepository(null);
|
|
1579
|
+
taskRepo = null;
|
|
1580
|
+
logRepo = null;
|
|
1581
|
+
logger6.info("Scheduler module closed");
|
|
1582
|
+
})();
|
|
1583
|
+
try {
|
|
1584
|
+
await closeInProgress;
|
|
1585
|
+
} finally {
|
|
1586
|
+
closeInProgress = null;
|
|
1587
|
+
}
|
|
1420
1588
|
}
|
|
1421
1589
|
};
|
|
1422
1590
|
|