@dbos-inc/dbos-sdk 4.9.6-preview → 4.9.9-preview
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/dist/schemas/system_db_schema.d.ts +9 -0
- package/dist/schemas/system_db_schema.d.ts.map +1 -1
- package/dist/schemas/system_db_schema.js.map +1 -1
- package/dist/src/client.d.ts +26 -0
- package/dist/src/client.d.ts.map +1 -1
- package/dist/src/client.js +59 -0
- package/dist/src/client.js.map +1 -1
- package/dist/src/conductor/conductor.d.ts.map +1 -1
- package/dist/src/conductor/conductor.js +105 -0
- package/dist/src/conductor/conductor.js.map +1 -1
- package/dist/src/conductor/protocol.d.ts +83 -1
- package/dist/src/conductor/protocol.d.ts.map +1 -1
- package/dist/src/conductor/protocol.js +119 -1
- package/dist/src/conductor/protocol.js.map +1 -1
- package/dist/src/config.d.ts +1 -0
- package/dist/src/config.d.ts.map +1 -1
- package/dist/src/config.js +3 -0
- package/dist/src/config.js.map +1 -1
- package/dist/src/datasource.js +1 -1
- package/dist/src/datasource.js.map +1 -1
- package/dist/src/dbos-executor.d.ts +4 -0
- package/dist/src/dbos-executor.d.ts.map +1 -1
- package/dist/src/dbos-executor.js +4 -2
- package/dist/src/dbos-executor.js.map +1 -1
- package/dist/src/dbos.d.ts +28 -4
- package/dist/src/dbos.d.ts.map +1 -1
- package/dist/src/dbos.js +134 -7
- package/dist/src/dbos.js.map +1 -1
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/scheduler/scheduler.d.ts +18 -35
- package/dist/src/scheduler/scheduler.d.ts.map +1 -1
- package/dist/src/scheduler/scheduler.js +193 -134
- package/dist/src/scheduler/scheduler.js.map +1 -1
- package/dist/src/scheduler/scheduler_decorator.d.ts +41 -0
- package/dist/src/scheduler/scheduler_decorator.d.ts.map +1 -0
- package/dist/src/scheduler/scheduler_decorator.js +172 -0
- package/dist/src/scheduler/scheduler_decorator.js.map +1 -0
- package/dist/src/sysdb_migrations/internal/migrations.d.ts +3 -1
- package/dist/src/sysdb_migrations/internal/migrations.d.ts.map +1 -1
- package/dist/src/sysdb_migrations/internal/migrations.js +21 -5
- package/dist/src/sysdb_migrations/internal/migrations.js.map +1 -1
- package/dist/src/system_database.d.ts +33 -2
- package/dist/src/system_database.d.ts.map +1 -1
- package/dist/src/system_database.js +145 -4
- package/dist/src/system_database.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +27 -1
|
@@ -1,172 +1,231 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.backfillSchedule = exports.triggerSchedule = exports.DynamicSchedulerLoop = exports.createScheduleId = exports.toWorkflowSchedule = void 0;
|
|
4
|
+
const serialization_1 = require("../serialization");
|
|
5
|
+
const crypto_1 = require("crypto");
|
|
4
6
|
const __1 = require("..");
|
|
7
|
+
const decorators_1 = require("../decorators");
|
|
5
8
|
const utils_1 = require("../utils");
|
|
6
9
|
const crontab_1 = require("./crontab");
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
constructor() {
|
|
10
|
+
const dbos_executor_1 = require("../dbos-executor");
|
|
11
|
+
const error_1 = require("../error");
|
|
12
|
+
const workflow_1 = require("../workflow");
|
|
13
|
+
function toWorkflowSchedule(internal, serializer) {
|
|
14
|
+
const context = serializer.parse(internal.context);
|
|
15
|
+
return {
|
|
16
|
+
scheduleId: internal.scheduleId,
|
|
17
|
+
scheduleName: internal.scheduleName,
|
|
18
|
+
workflowName: internal.workflowName,
|
|
19
|
+
workflowClassName: internal.workflowClassName,
|
|
20
|
+
schedule: internal.schedule,
|
|
21
|
+
status: internal.status,
|
|
22
|
+
context,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
exports.toWorkflowSchedule = toWorkflowSchedule;
|
|
26
|
+
function createScheduleId() {
|
|
27
|
+
return (0, crypto_1.randomUUID)();
|
|
28
|
+
}
|
|
29
|
+
exports.createScheduleId = createScheduleId;
|
|
30
|
+
class DynamicSchedulerLoop {
|
|
31
|
+
#mainController = new AbortController();
|
|
32
|
+
#pollingPromise;
|
|
33
|
+
#scheduleLoops = new Map();
|
|
34
|
+
#pollingIntervalMs;
|
|
35
|
+
constructor(pollingIntervalMs) {
|
|
36
|
+
this.#pollingIntervalMs = pollingIntervalMs ?? 30000;
|
|
35
37
|
__1.DBOS.registerLifecycleCallback(this);
|
|
36
38
|
}
|
|
37
|
-
// eslint-disable-next-line @typescript-eslint/require-await
|
|
38
39
|
async initialize() {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
__1.DBOS.logger.warn(`Scheduled workflow ${regOp.methodReg.className}.${regOp.methodReg.name} is missing registered function; skipping`);
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
const { crontab, mode, queueName } = regOp.methodConfig;
|
|
45
|
-
if (!crontab) {
|
|
46
|
-
__1.DBOS.logger.warn(`Scheduled workflow ${regOp.methodReg.className}.${regOp.methodReg.name} is missing crontab; skipping`);
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
const timeMatcher = new crontab_1.TimeMatcher(crontab);
|
|
50
|
-
const promise = ScheduledReceiver.#schedulerLoop(regOp.methodReg, timeMatcher, mode ?? SchedulerMode.ExactlyOncePerIntervalWhenActive, queueName, this.#controller.signal);
|
|
51
|
-
this.#disposables.push(promise);
|
|
52
|
-
}
|
|
40
|
+
this.#pollingPromise = this.#pollingLoop(this.#mainController.signal);
|
|
41
|
+
await Promise.resolve();
|
|
53
42
|
}
|
|
54
43
|
async destroy() {
|
|
55
|
-
this.#
|
|
56
|
-
|
|
57
|
-
|
|
44
|
+
this.#mainController.abort();
|
|
45
|
+
// Abort all per-schedule loops
|
|
46
|
+
for (const entry of this.#scheduleLoops.values()) {
|
|
47
|
+
entry.controller.abort();
|
|
48
|
+
}
|
|
49
|
+
const allPromises = [];
|
|
50
|
+
if (this.#pollingPromise) {
|
|
51
|
+
allPromises.push(this.#pollingPromise);
|
|
52
|
+
}
|
|
53
|
+
for (const entry of this.#scheduleLoops.values()) {
|
|
54
|
+
allPromises.push(entry.promise);
|
|
55
|
+
}
|
|
56
|
+
await Promise.allSettled(allPromises);
|
|
57
|
+
this.#scheduleLoops.clear();
|
|
58
58
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
59
|
+
async #pollingLoop(signal) {
|
|
60
|
+
while (!signal.aborted) {
|
|
61
|
+
let schedules;
|
|
62
|
+
try {
|
|
63
|
+
const executor = dbos_executor_1.DBOSExecutor.globalInstance;
|
|
64
|
+
schedules = await executor.systemDatabase.listSchedules();
|
|
65
|
+
}
|
|
66
|
+
catch (e) {
|
|
67
|
+
__1.DBOS.logger.warn(`Dynamic scheduler: error listing schedules: ${e.message}`);
|
|
68
|
+
await DynamicSchedulerLoop.#cancellableSleep(this.#pollingIntervalMs, signal);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
// Build set of current schedule names
|
|
72
|
+
const currentNames = new Set(schedules.map((s) => s.scheduleName));
|
|
73
|
+
// Stop loops for deleted schedules
|
|
74
|
+
for (const [name, entry] of this.#scheduleLoops) {
|
|
75
|
+
if (!currentNames.has(name)) {
|
|
76
|
+
entry.controller.abort();
|
|
77
|
+
this.#scheduleLoops.delete(name);
|
|
78
|
+
}
|
|
66
79
|
}
|
|
67
|
-
|
|
68
|
-
|
|
80
|
+
// Process each schedule
|
|
81
|
+
for (const sched of schedules) {
|
|
82
|
+
const existing = this.#scheduleLoops.get(sched.scheduleName);
|
|
83
|
+
if (sched.status === 'PAUSED' && existing) {
|
|
84
|
+
// Paused but has a running loop — stop it
|
|
85
|
+
existing.controller.abort();
|
|
86
|
+
this.#scheduleLoops.delete(sched.scheduleName);
|
|
87
|
+
}
|
|
88
|
+
else if (sched.status === 'ACTIVE') {
|
|
89
|
+
// If schedule was replaced (different scheduleId), restart the loop
|
|
90
|
+
if (existing && existing.scheduleId !== sched.scheduleId) {
|
|
91
|
+
existing.controller.abort();
|
|
92
|
+
this.#scheduleLoops.delete(sched.scheduleName);
|
|
93
|
+
}
|
|
94
|
+
if (!this.#scheduleLoops.has(sched.scheduleName)) {
|
|
95
|
+
// Active and no running loop — start one
|
|
96
|
+
const controller = new AbortController();
|
|
97
|
+
const executor = dbos_executor_1.DBOSExecutor.globalInstance;
|
|
98
|
+
const promise = DynamicSchedulerLoop.#scheduleLoop(sched.scheduleName, sched.workflowName, sched.workflowClassName, sched.schedule, sched.context, executor.serializer, controller.signal);
|
|
99
|
+
this.#scheduleLoops.set(sched.scheduleName, { controller, promise, scheduleId: sched.scheduleId });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
69
102
|
}
|
|
103
|
+
await DynamicSchedulerLoop.#cancellableSleep(this.#pollingIntervalMs, signal);
|
|
70
104
|
}
|
|
71
105
|
}
|
|
72
|
-
static async #
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
lastExec = parseFloat(lastState.value);
|
|
79
|
-
}
|
|
106
|
+
static async #scheduleLoop(scheduleName, workflowName, workflowClassName, cronExpression, serializedContext, serializer, signal) {
|
|
107
|
+
// Look up the registered workflow function
|
|
108
|
+
const methReg = (0, decorators_1.getFunctionRegistrationByName)(workflowClassName, workflowName);
|
|
109
|
+
if (!methReg || !methReg.registeredFunction) {
|
|
110
|
+
__1.DBOS.logger.warn(`Dynamic scheduler: workflow ${workflowClassName}.${workflowName} for schedule "${scheduleName}" is not registered; skipping`);
|
|
111
|
+
return;
|
|
80
112
|
}
|
|
113
|
+
const timeMatcher = new crontab_1.TimeMatcher(cronExpression);
|
|
114
|
+
let lastExec = new Date().setMilliseconds(0);
|
|
81
115
|
while (!signal.aborted) {
|
|
82
116
|
const nextExec = timeMatcher.nextWakeupTime(lastExec).getTime();
|
|
83
117
|
let sleepTime = nextExec - Date.now();
|
|
84
|
-
//
|
|
85
|
-
// apply jitter of up to 10% the sleep time, capped at 10 seconds
|
|
118
|
+
// Apply jitter to prevent thundering herd
|
|
86
119
|
if (sleepTime > 0) {
|
|
87
120
|
const maxJitter = Math.min(sleepTime / 10, 10000);
|
|
88
|
-
|
|
89
|
-
sleepTime += jitter;
|
|
121
|
+
sleepTime += Math.random() * maxJitter;
|
|
90
122
|
}
|
|
91
123
|
if (sleepTime > 0) {
|
|
92
|
-
await
|
|
93
|
-
// eslint-disable-next-line prefer-const
|
|
94
|
-
let timeoutID;
|
|
95
|
-
const onAbort = () => {
|
|
96
|
-
clearTimeout(timeoutID);
|
|
97
|
-
reject(new Error('Abort signal received'));
|
|
98
|
-
};
|
|
99
|
-
signal.addEventListener('abort', onAbort, { once: true });
|
|
100
|
-
if (signal.aborted) {
|
|
101
|
-
signal.removeEventListener('abort', onAbort);
|
|
102
|
-
reject(new Error('Abort signal received'));
|
|
103
|
-
}
|
|
104
|
-
timeoutID = setTimeout(() => {
|
|
105
|
-
signal.removeEventListener('abort', onAbort);
|
|
106
|
-
resolve();
|
|
107
|
-
}, sleepTime);
|
|
108
|
-
});
|
|
124
|
+
await DynamicSchedulerLoop.#cancellableSleep(sleepTime, signal);
|
|
109
125
|
}
|
|
110
126
|
if (signal.aborted) {
|
|
111
127
|
break;
|
|
112
128
|
}
|
|
113
|
-
if (!timeMatcher.match(nextExec)) {
|
|
114
|
-
lastExec = nextExec;
|
|
115
|
-
continue;
|
|
116
|
-
}
|
|
117
129
|
const date = new Date(nextExec);
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
__1.DBOS.
|
|
122
|
-
|
|
130
|
+
const workflowID = `sched-${scheduleName}-${date.toISOString()}`;
|
|
131
|
+
try {
|
|
132
|
+
// Idempotency check -- for performance only, not needed for correctness
|
|
133
|
+
const existing = await __1.DBOS.getWorkflowStatus(workflowID);
|
|
134
|
+
if (existing) {
|
|
135
|
+
lastExec = nextExec;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
const wfParams = { workflowID, queueName: utils_1.INTERNAL_QUEUE_NAME };
|
|
139
|
+
const context = serializer.parse(serializedContext);
|
|
140
|
+
await __1.DBOS.startWorkflow(methReg.registeredFunction, wfParams)(date, context);
|
|
123
141
|
}
|
|
124
|
-
|
|
125
|
-
__1.DBOS.logger.
|
|
142
|
+
catch (e) {
|
|
143
|
+
__1.DBOS.logger.warn(`Dynamic scheduler: error firing workflow for schedule "${scheduleName}": ${e.message}`);
|
|
126
144
|
}
|
|
127
|
-
lastExec =
|
|
145
|
+
lastExec = nextExec;
|
|
128
146
|
}
|
|
129
147
|
}
|
|
130
|
-
static async #
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
148
|
+
static async #cancellableSleep(ms, signal) {
|
|
149
|
+
if (signal.aborted)
|
|
150
|
+
return;
|
|
151
|
+
await new Promise((resolve) => {
|
|
152
|
+
// eslint-disable-next-line prefer-const
|
|
153
|
+
let timeoutID;
|
|
154
|
+
const onAbort = () => {
|
|
155
|
+
clearTimeout(timeoutID);
|
|
156
|
+
resolve();
|
|
139
157
|
};
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
158
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
159
|
+
if (signal.aborted) {
|
|
160
|
+
signal.removeEventListener('abort', onAbort);
|
|
161
|
+
resolve();
|
|
162
|
+
return;
|
|
144
163
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const err = e;
|
|
150
|
-
__1.DBOS.logger.warn(`Scheduler caught an error writing to system DB: ${err.message}`);
|
|
151
|
-
__1.DBOS.logger.error(e);
|
|
152
|
-
}
|
|
153
|
-
return time;
|
|
154
|
-
}
|
|
155
|
-
// registerScheduled is static so it can be called before an instance is created during DBOS.launch.
|
|
156
|
-
// This means we can't use the instance as the external info key for associateFunctionWithInfo below
|
|
157
|
-
// or in getAssociatedInfo above...which means we can only have one scheduled receiver instance.
|
|
158
|
-
// However, since this is an internal receiver, it's safe to assume there is ever only one instnace.
|
|
159
|
-
static registerScheduled(func, config) {
|
|
160
|
-
const { regInfo } = __1.DBOS.associateFunctionWithInfo(SCHEDULER_EVENT_SERVICE_NAME, func, {
|
|
161
|
-
ctorOrProto: config.ctorOrProto,
|
|
162
|
-
className: config.className,
|
|
163
|
-
name: config.name ?? func.name,
|
|
164
|
+
timeoutID = setTimeout(() => {
|
|
165
|
+
signal.removeEventListener('abort', onAbort);
|
|
166
|
+
resolve();
|
|
167
|
+
}, ms);
|
|
164
168
|
});
|
|
165
|
-
const schedRegInfo = regInfo;
|
|
166
|
-
schedRegInfo.crontab = config.crontab;
|
|
167
|
-
schedRegInfo.mode = config.mode;
|
|
168
|
-
schedRegInfo.queueName = config.queueName;
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
|
-
exports.
|
|
171
|
+
exports.DynamicSchedulerLoop = DynamicSchedulerLoop;
|
|
172
|
+
function enqueueScheduledWorkflow(systemDatabase, serializer, sched, workflowID, scheduledDate, context) {
|
|
173
|
+
const serparam = (0, serialization_1.serializeArgs)([scheduledDate, context], undefined, serializer, undefined);
|
|
174
|
+
const internalStatus = {
|
|
175
|
+
workflowUUID: workflowID,
|
|
176
|
+
status: workflow_1.StatusString.ENQUEUED,
|
|
177
|
+
workflowName: sched.workflowName,
|
|
178
|
+
workflowClassName: sched.workflowClassName,
|
|
179
|
+
workflowConfigName: '',
|
|
180
|
+
queueName: utils_1.INTERNAL_QUEUE_NAME,
|
|
181
|
+
authenticatedUser: '',
|
|
182
|
+
output: null,
|
|
183
|
+
error: null,
|
|
184
|
+
assumedRole: '',
|
|
185
|
+
authenticatedRoles: [],
|
|
186
|
+
request: {},
|
|
187
|
+
executorId: '',
|
|
188
|
+
applicationID: '',
|
|
189
|
+
createdAt: Date.now(),
|
|
190
|
+
input: serparam.serializedValue,
|
|
191
|
+
deduplicationID: undefined,
|
|
192
|
+
priority: 0,
|
|
193
|
+
queuePartitionKey: undefined,
|
|
194
|
+
serialization: serparam.serialization,
|
|
195
|
+
};
|
|
196
|
+
return systemDatabase.initWorkflowStatus(internalStatus, null).then(() => { });
|
|
197
|
+
}
|
|
198
|
+
async function triggerSchedule(systemDatabase, serializer, name) {
|
|
199
|
+
const sched = await systemDatabase.getSchedule(name);
|
|
200
|
+
if (!sched) {
|
|
201
|
+
throw new error_1.DBOSError(`Schedule "${name}" not found`);
|
|
202
|
+
}
|
|
203
|
+
const context = serializer.parse(sched.context);
|
|
204
|
+
const now = new Date();
|
|
205
|
+
const workflowID = `sched-${name}-trigger-${now.toISOString()}`;
|
|
206
|
+
await enqueueScheduledWorkflow(systemDatabase, serializer, sched, workflowID, now, context);
|
|
207
|
+
return workflowID;
|
|
208
|
+
}
|
|
209
|
+
exports.triggerSchedule = triggerSchedule;
|
|
210
|
+
async function backfillSchedule(systemDatabase, serializer, name, start, end) {
|
|
211
|
+
const sched = await systemDatabase.getSchedule(name);
|
|
212
|
+
if (!sched) {
|
|
213
|
+
throw new error_1.DBOSError(`Schedule "${name}" not found`);
|
|
214
|
+
}
|
|
215
|
+
const context = serializer.parse(sched.context);
|
|
216
|
+
const timeMatcher = new crontab_1.TimeMatcher(sched.schedule);
|
|
217
|
+
const workflowIDs = [];
|
|
218
|
+
let current = start.getTime();
|
|
219
|
+
while (current < end.getTime()) {
|
|
220
|
+
const next = timeMatcher.nextWakeupTime(current);
|
|
221
|
+
if (next.getTime() >= end.getTime())
|
|
222
|
+
break;
|
|
223
|
+
const workflowID = `sched-${name}-${next.toISOString()}`;
|
|
224
|
+
await enqueueScheduledWorkflow(systemDatabase, serializer, sched, workflowID, next, context);
|
|
225
|
+
workflowIDs.push(workflowID);
|
|
226
|
+
current = next.getTime();
|
|
227
|
+
}
|
|
228
|
+
return workflowIDs;
|
|
229
|
+
}
|
|
230
|
+
exports.backfillSchedule = backfillSchedule;
|
|
172
231
|
//# sourceMappingURL=scheduler.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../../../src/scheduler/scheduler.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../../../src/scheduler/scheduler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { DBOSLifecycleCallback, FunctionName } from '../decorators';
|
|
2
|
+
/**
|
|
3
|
+
* Choices for scheduler mode for `@DBOS.scheduled` workflows
|
|
4
|
+
*/
|
|
5
|
+
export declare enum SchedulerMode {
|
|
6
|
+
/**
|
|
7
|
+
* Using `ExactlyOncePerInterval` causes the scheduler to add "make-up work" for any
|
|
8
|
+
* schedule slots that occurred when the app was not running
|
|
9
|
+
*/
|
|
10
|
+
ExactlyOncePerInterval = "ExactlyOncePerInterval",
|
|
11
|
+
/**
|
|
12
|
+
* Using `ExactlyOncePerIntervalWhenActive` causes the scheduler to run the workflow once
|
|
13
|
+
* per interval when the application is active. If the app is not running at a time
|
|
14
|
+
* otherwise indicated by the schedule, no workflow will be run.
|
|
15
|
+
*/
|
|
16
|
+
ExactlyOncePerIntervalWhenActive = "ExactlyOncePerIntervalWhenActive"
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Configuration for a `@DBOS.scheduled` workflow
|
|
20
|
+
*/
|
|
21
|
+
export interface SchedulerConfig {
|
|
22
|
+
/** Schedule, in 5- or 6-spot crontab format */
|
|
23
|
+
crontab: string;
|
|
24
|
+
/**
|
|
25
|
+
* Indicates whether or not to retroactively start workflows that were scheduled during
|
|
26
|
+
* times when the app was not running. @see `SchedulerMode`.
|
|
27
|
+
*/
|
|
28
|
+
mode?: SchedulerMode;
|
|
29
|
+
/** If set, workflows will be enqueued on the named queue, rather than being started immediately */
|
|
30
|
+
queueName?: string;
|
|
31
|
+
}
|
|
32
|
+
export type ScheduledArgs = [Date, Date];
|
|
33
|
+
export declare class ScheduledReceiver implements DBOSLifecycleCallback {
|
|
34
|
+
#private;
|
|
35
|
+
constructor();
|
|
36
|
+
initialize(): Promise<void>;
|
|
37
|
+
destroy(): Promise<void>;
|
|
38
|
+
logRegisteredEndpoints(): void;
|
|
39
|
+
static registerScheduled<This, Return>(func: (this: This, ...args: ScheduledArgs) => Promise<Return>, config: SchedulerConfig & FunctionName): void;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=scheduler_decorator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduler_decorator.d.ts","sourceRoot":"","sources":["../../../src/scheduler/scheduler_decorator.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,YAAY,EAA0B,MAAM,eAAe,CAAC;AAQ5F;;GAEG;AACH,oBAAY,aAAa;IACvB;;;OAGG;IACH,sBAAsB,2BAA2B;IACjD;;;;OAIG;IACH,gCAAgC,qCAAqC;CACtE;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,+CAA+C;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,mGAAmG;IACnG,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAOD,MAAM,MAAM,aAAa,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AASzC,qBAAa,iBAAkB,YAAW,qBAAqB;;;IASvD,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IA6B3B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAM9B,sBAAsB,IAAI,IAAI;IAuH9B,MAAM,CAAC,iBAAiB,CAAC,IAAI,EAAE,MAAM,EACnC,IAAI,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,aAAa,KAAK,OAAO,CAAC,MAAM,CAAC,EAC7D,MAAM,EAAE,eAAe,GAAG,YAAY;CAazC"}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ScheduledReceiver = exports.SchedulerMode = void 0;
|
|
4
|
+
const __1 = require("..");
|
|
5
|
+
const utils_1 = require("../utils");
|
|
6
|
+
const crontab_1 = require("./crontab");
|
|
7
|
+
////
|
|
8
|
+
// Configuration
|
|
9
|
+
////
|
|
10
|
+
/**
|
|
11
|
+
* Choices for scheduler mode for `@DBOS.scheduled` workflows
|
|
12
|
+
*/
|
|
13
|
+
var SchedulerMode;
|
|
14
|
+
(function (SchedulerMode) {
|
|
15
|
+
/**
|
|
16
|
+
* Using `ExactlyOncePerInterval` causes the scheduler to add "make-up work" for any
|
|
17
|
+
* schedule slots that occurred when the app was not running
|
|
18
|
+
*/
|
|
19
|
+
SchedulerMode["ExactlyOncePerInterval"] = "ExactlyOncePerInterval";
|
|
20
|
+
/**
|
|
21
|
+
* Using `ExactlyOncePerIntervalWhenActive` causes the scheduler to run the workflow once
|
|
22
|
+
* per interval when the application is active. If the app is not running at a time
|
|
23
|
+
* otherwise indicated by the schedule, no workflow will be run.
|
|
24
|
+
*/
|
|
25
|
+
SchedulerMode["ExactlyOncePerIntervalWhenActive"] = "ExactlyOncePerIntervalWhenActive";
|
|
26
|
+
})(SchedulerMode || (exports.SchedulerMode = SchedulerMode = {}));
|
|
27
|
+
///////////////////////////
|
|
28
|
+
// Scheduler Management
|
|
29
|
+
///////////////////////////
|
|
30
|
+
const SCHEDULER_EVENT_SERVICE_NAME = 'dbos.scheduler';
|
|
31
|
+
class ScheduledReceiver {
|
|
32
|
+
#controller = new AbortController();
|
|
33
|
+
#disposables = new Array();
|
|
34
|
+
constructor() {
|
|
35
|
+
__1.DBOS.registerLifecycleCallback(this);
|
|
36
|
+
}
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
38
|
+
async initialize() {
|
|
39
|
+
for (const regOp of __1.DBOS.getAssociatedInfo(SCHEDULER_EVENT_SERVICE_NAME)) {
|
|
40
|
+
if (regOp.methodReg.registeredFunction === undefined) {
|
|
41
|
+
__1.DBOS.logger.warn(`Scheduled workflow ${regOp.methodReg.className}.${regOp.methodReg.name} is missing registered function; skipping`);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const { crontab, mode, queueName } = regOp.methodConfig;
|
|
45
|
+
if (!crontab) {
|
|
46
|
+
__1.DBOS.logger.warn(`Scheduled workflow ${regOp.methodReg.className}.${regOp.methodReg.name} is missing crontab; skipping`);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const timeMatcher = new crontab_1.TimeMatcher(crontab);
|
|
50
|
+
const promise = ScheduledReceiver.#schedulerLoop(regOp.methodReg, timeMatcher, mode ?? SchedulerMode.ExactlyOncePerIntervalWhenActive, queueName, this.#controller.signal);
|
|
51
|
+
this.#disposables.push(promise);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async destroy() {
|
|
55
|
+
this.#controller.abort();
|
|
56
|
+
const promises = this.#disposables.splice(0);
|
|
57
|
+
await Promise.allSettled(promises);
|
|
58
|
+
}
|
|
59
|
+
logRegisteredEndpoints() {
|
|
60
|
+
__1.DBOS.logger.info('Scheduled endpoints:');
|
|
61
|
+
for (const regOp of __1.DBOS.getAssociatedInfo(SCHEDULER_EVENT_SERVICE_NAME)) {
|
|
62
|
+
const name = `${regOp.methodReg.className}.${regOp.methodReg.name}`;
|
|
63
|
+
const { crontab, mode } = regOp.methodConfig;
|
|
64
|
+
if (crontab) {
|
|
65
|
+
__1.DBOS.logger.info(` ${name} @ ${crontab}; ${mode ?? SchedulerMode.ExactlyOncePerIntervalWhenActive}`);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
__1.DBOS.logger.info(` ${name} is missing crontab; skipping`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
static async #schedulerLoop(methodReg, timeMatcher, mode, queueName, signal) {
|
|
73
|
+
const name = `${methodReg.className}.${methodReg.name}`;
|
|
74
|
+
let lastExec = new Date().setMilliseconds(0);
|
|
75
|
+
if (mode === SchedulerMode.ExactlyOncePerInterval) {
|
|
76
|
+
const lastState = await __1.DBOS.getEventDispatchState(SCHEDULER_EVENT_SERVICE_NAME, name, 'lastState');
|
|
77
|
+
if (lastState?.value) {
|
|
78
|
+
lastExec = parseFloat(lastState.value);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
while (!signal.aborted) {
|
|
82
|
+
const nextExec = timeMatcher.nextWakeupTime(lastExec).getTime();
|
|
83
|
+
let sleepTime = nextExec - Date.now();
|
|
84
|
+
// To prevent a "thundering herd" problem in a distributed setting,
|
|
85
|
+
// apply jitter of up to 10% the sleep time, capped at 10 seconds
|
|
86
|
+
if (sleepTime > 0) {
|
|
87
|
+
const maxJitter = Math.min(sleepTime / 10, 10000);
|
|
88
|
+
const jitter = Math.random() * maxJitter;
|
|
89
|
+
sleepTime += jitter;
|
|
90
|
+
}
|
|
91
|
+
if (sleepTime > 0) {
|
|
92
|
+
await new Promise((resolve, reject) => {
|
|
93
|
+
// eslint-disable-next-line prefer-const
|
|
94
|
+
let timeoutID;
|
|
95
|
+
const onAbort = () => {
|
|
96
|
+
clearTimeout(timeoutID);
|
|
97
|
+
reject(new Error('Abort signal received'));
|
|
98
|
+
};
|
|
99
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
100
|
+
if (signal.aborted) {
|
|
101
|
+
signal.removeEventListener('abort', onAbort);
|
|
102
|
+
reject(new Error('Abort signal received'));
|
|
103
|
+
}
|
|
104
|
+
timeoutID = setTimeout(() => {
|
|
105
|
+
signal.removeEventListener('abort', onAbort);
|
|
106
|
+
resolve();
|
|
107
|
+
}, sleepTime);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
if (signal.aborted) {
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
if (!timeMatcher.match(nextExec)) {
|
|
114
|
+
lastExec = nextExec;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const date = new Date(nextExec);
|
|
118
|
+
if (methodReg.workflowConfig && methodReg.registeredFunction) {
|
|
119
|
+
const workflowID = `sched-${name}-${date.toISOString()}`;
|
|
120
|
+
const wfParams = { workflowID, queueName: queueName ?? utils_1.INTERNAL_QUEUE_NAME };
|
|
121
|
+
__1.DBOS.logger.debug(`Executing scheduled workflow ${workflowID}`);
|
|
122
|
+
await __1.DBOS.startWorkflow(methodReg.registeredFunction, wfParams)(date, new Date());
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
__1.DBOS.logger.error(`${name} is @scheduled but not a workflow`);
|
|
126
|
+
}
|
|
127
|
+
lastExec = await ScheduledReceiver.#setLastExecTime(name, nextExec);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
static async #setLastExecTime(name, time) {
|
|
131
|
+
// Record the time of the wf kicked off
|
|
132
|
+
try {
|
|
133
|
+
const state = {
|
|
134
|
+
service: SCHEDULER_EVENT_SERVICE_NAME,
|
|
135
|
+
workflowFnName: name,
|
|
136
|
+
key: 'lastState',
|
|
137
|
+
value: `${time}`,
|
|
138
|
+
updateTime: time,
|
|
139
|
+
};
|
|
140
|
+
const newState = await __1.DBOS.upsertEventDispatchState(state);
|
|
141
|
+
const dbTime = parseFloat(newState.value);
|
|
142
|
+
if (dbTime && dbTime > time) {
|
|
143
|
+
return dbTime;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch (e) {
|
|
147
|
+
// This write is not strictly essential and the scheduler is often the "canary in the coal mine"
|
|
148
|
+
// We will simply continue after giving full details.
|
|
149
|
+
const err = e;
|
|
150
|
+
__1.DBOS.logger.warn(`Scheduler caught an error writing to system DB: ${err.message}`);
|
|
151
|
+
__1.DBOS.logger.error(e);
|
|
152
|
+
}
|
|
153
|
+
return time;
|
|
154
|
+
}
|
|
155
|
+
// registerScheduled is static so it can be called before an instance is created during DBOS.launch.
|
|
156
|
+
// This means we can't use the instance as the external info key for associateFunctionWithInfo below
|
|
157
|
+
// or in getAssociatedInfo above...which means we can only have one scheduled receiver instance.
|
|
158
|
+
// However, since this is an internal receiver, it's safe to assume there is ever only one instnace.
|
|
159
|
+
static registerScheduled(func, config) {
|
|
160
|
+
const { regInfo } = __1.DBOS.associateFunctionWithInfo(SCHEDULER_EVENT_SERVICE_NAME, func, {
|
|
161
|
+
ctorOrProto: config.ctorOrProto,
|
|
162
|
+
className: config.className,
|
|
163
|
+
name: config.name ?? func.name,
|
|
164
|
+
});
|
|
165
|
+
const schedRegInfo = regInfo;
|
|
166
|
+
schedRegInfo.crontab = config.crontab;
|
|
167
|
+
schedRegInfo.mode = config.mode;
|
|
168
|
+
schedRegInfo.queueName = config.queueName;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
exports.ScheduledReceiver = ScheduledReceiver;
|
|
172
|
+
//# sourceMappingURL=scheduler_decorator.js.map
|