@openfn/ws-worker 0.1.4 → 0.1.6
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/CHANGELOG.md +15 -0
- package/dist/index.d.ts +47 -2
- package/dist/index.js +71 -51
- package/dist/start.js +90 -57
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# ws-worker
|
|
2
2
|
|
|
3
|
+
## 0.1.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 5037c68: Support maxWorkers flag
|
|
8
|
+
Enforce minBackoff before claiming the next job
|
|
9
|
+
- Updated dependencies [ac7b0ca]
|
|
10
|
+
- @openfn/engine-multi@0.1.4
|
|
11
|
+
|
|
12
|
+
## 0.1.5
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- Test release
|
|
17
|
+
|
|
3
18
|
## 0.1.4
|
|
4
19
|
|
|
5
20
|
### Patch Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,50 @@
|
|
|
1
1
|
import Koa from 'koa';
|
|
2
|
-
import { Logger } from '@openfn/logger';
|
|
2
|
+
import { SanitizePolicies, Logger } from '@openfn/logger';
|
|
3
3
|
import { RuntimeEngine } from '@openfn/engine-multi';
|
|
4
|
+
import { Channel as Channel$1 } from 'phoenix';
|
|
5
|
+
import { ExecutionPlan } from '@openfn/runtime';
|
|
6
|
+
|
|
7
|
+
type AttemptOptions = {
|
|
8
|
+
timeout?: number;
|
|
9
|
+
sanitize?: SanitizePolicies;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
type ReceiveHook = {
|
|
13
|
+
receive: (
|
|
14
|
+
status: 'ok' | 'timeout' | 'error',
|
|
15
|
+
callback: (payload?: any) => void
|
|
16
|
+
) => ReceiveHook;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// export declare class Socket extends PhxSocket {
|
|
20
|
+
// constructor(endpoint: string, options: { params: any });
|
|
21
|
+
// onOpen(callback: () => void): void;
|
|
22
|
+
// connect(): void;
|
|
23
|
+
// channel(channelName: string, params: any): Channel;
|
|
24
|
+
// }
|
|
25
|
+
|
|
26
|
+
interface Channel extends Channel$1 {
|
|
27
|
+
// on: (event: string, fn: (evt: any) => void) => void;
|
|
28
|
+
|
|
29
|
+
// TODO it would be super nice to infer the event from the payload
|
|
30
|
+
push: <P = any>(event: string, payload?: P) => ReceiveHook;
|
|
31
|
+
// join: () => ReceiveHook;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
declare type AttemptState = {
|
|
35
|
+
activeRun?: string;
|
|
36
|
+
activeJob?: string;
|
|
37
|
+
plan: ExecutionPlan;
|
|
38
|
+
options: AttemptOptions;
|
|
39
|
+
dataclips: Record<string, any>;
|
|
40
|
+
lastDataclipId?: string;
|
|
41
|
+
};
|
|
42
|
+
declare type Context = {
|
|
43
|
+
channel: Channel;
|
|
44
|
+
state: AttemptState;
|
|
45
|
+
logger: Logger;
|
|
46
|
+
onComplete: (result: any) => void;
|
|
47
|
+
};
|
|
4
48
|
|
|
5
49
|
declare type CLAIM_ATTEMPT = {
|
|
6
50
|
id: string;
|
|
@@ -22,7 +66,8 @@ declare type ServerOptions = {
|
|
|
22
66
|
interface ServerApp extends Koa {
|
|
23
67
|
id: string;
|
|
24
68
|
socket: any;
|
|
25
|
-
channel:
|
|
69
|
+
channel: Channel;
|
|
70
|
+
workflows: Record<string, true | Context>;
|
|
26
71
|
execute: ({ id, token }: CLAIM_ATTEMPT) => Promise<void>;
|
|
27
72
|
destroy: () => void;
|
|
28
73
|
killWorkloop: () => void;
|
package/dist/index.js
CHANGED
|
@@ -63,17 +63,21 @@ var RUN_COMPLETE = "run:complete";
|
|
|
63
63
|
|
|
64
64
|
// src/api/claim.ts
|
|
65
65
|
var mockLogger = createMockLogger();
|
|
66
|
-
var claim = (
|
|
66
|
+
var claim = (app, logger = mockLogger, maxWorkers = 5) => {
|
|
67
67
|
return new Promise((resolve, reject) => {
|
|
68
|
+
const activeWorkers = Object.keys(app.workflows).length;
|
|
69
|
+
if (activeWorkers >= maxWorkers) {
|
|
70
|
+
return reject(new Error("Server at capacity"));
|
|
71
|
+
}
|
|
68
72
|
logger.debug("requesting attempt...");
|
|
69
|
-
channel.push(CLAIM, { demand: 1 }).receive("ok", ({ attempts }) => {
|
|
73
|
+
app.channel.push(CLAIM, { demand: 1 }).receive("ok", ({ attempts }) => {
|
|
70
74
|
logger.debug(`pulled ${attempts.length} attempts`);
|
|
71
75
|
if (!attempts?.length) {
|
|
72
|
-
return reject(new Error("
|
|
76
|
+
return reject(new Error("No attempts returned"));
|
|
73
77
|
}
|
|
74
78
|
attempts.forEach((attempt) => {
|
|
75
79
|
logger.debug("starting attempt", attempt.id);
|
|
76
|
-
|
|
80
|
+
app.execute(attempt);
|
|
77
81
|
resolve();
|
|
78
82
|
});
|
|
79
83
|
});
|
|
@@ -82,18 +86,18 @@ var claim = (channel, execute2, logger = mockLogger) => {
|
|
|
82
86
|
var claim_default = claim;
|
|
83
87
|
|
|
84
88
|
// src/api/workloop.ts
|
|
85
|
-
var startWorkloop = (
|
|
89
|
+
var startWorkloop = (app, logger, minBackoff, maxBackoff, maxWorkers) => {
|
|
86
90
|
let promise;
|
|
87
91
|
let cancelled = false;
|
|
88
92
|
const workLoop = () => {
|
|
89
93
|
if (!cancelled) {
|
|
90
|
-
promise = try_with_backoff_default(() => claim_default(
|
|
94
|
+
promise = try_with_backoff_default(() => claim_default(app, logger, maxWorkers), {
|
|
91
95
|
min: minBackoff,
|
|
92
96
|
max: maxBackoff
|
|
93
97
|
});
|
|
94
98
|
promise.then(() => {
|
|
95
99
|
if (!cancelled) {
|
|
96
|
-
workLoop
|
|
100
|
+
setTimeout(workLoop, minBackoff);
|
|
97
101
|
}
|
|
98
102
|
});
|
|
99
103
|
}
|
|
@@ -215,59 +219,62 @@ var eventMap = {
|
|
|
215
219
|
log: ATTEMPT_LOG,
|
|
216
220
|
"workflow-complete": ATTEMPT_COMPLETE
|
|
217
221
|
};
|
|
218
|
-
function execute(channel, engine, logger, plan, options = {}) {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
222
|
+
function execute(channel, engine, logger, plan, options = {}, onComplete = (_result) => {
|
|
223
|
+
}) {
|
|
224
|
+
logger.info("execute...");
|
|
225
|
+
const state = {
|
|
226
|
+
plan,
|
|
227
|
+
lastDataclipId: plan.initialState,
|
|
228
|
+
dataclips: {},
|
|
229
|
+
options
|
|
230
|
+
};
|
|
231
|
+
const context = { channel, state, logger, onComplete };
|
|
232
|
+
const addEvent = (eventName, handler) => {
|
|
233
|
+
const wrappedFn = async (event) => {
|
|
234
|
+
const lightningEvent = eventMap[eventName];
|
|
235
|
+
try {
|
|
236
|
+
await handler(context, event);
|
|
237
|
+
logger.info(`${plan.id} :: ${lightningEvent} :: OK`);
|
|
238
|
+
} catch (e) {
|
|
239
|
+
logger.error(
|
|
240
|
+
`${plan.id} :: ${lightningEvent} :: ERR: ${e.message || e.toString()}`
|
|
241
|
+
);
|
|
242
|
+
logger.error(e);
|
|
243
|
+
}
|
|
226
244
|
};
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
const wrappedFn = async (event) => {
|
|
230
|
-
const lightningEvent = eventMap[eventName];
|
|
231
|
-
try {
|
|
232
|
-
await handler(context, event);
|
|
233
|
-
logger.info(`${plan.id} :: ${lightningEvent} :: OK`);
|
|
234
|
-
} catch (e) {
|
|
235
|
-
logger.error(
|
|
236
|
-
`${plan.id} :: ${lightningEvent} :: ERR: ${e.message || e.toString()}`
|
|
237
|
-
);
|
|
238
|
-
logger.error(e);
|
|
239
|
-
}
|
|
240
|
-
};
|
|
241
|
-
return {
|
|
242
|
-
[eventName]: wrappedFn
|
|
243
|
-
};
|
|
244
|
-
};
|
|
245
|
-
const listeners = Object.assign(
|
|
246
|
-
{},
|
|
247
|
-
addEvent("workflow-start", onWorkflowStart),
|
|
248
|
-
addEvent("job-start", onJobStart),
|
|
249
|
-
addEvent("job-complete", onJobComplete),
|
|
250
|
-
addEvent("workflow-log", onJobLog),
|
|
251
|
-
addEvent("workflow-complete", onWorkflowComplete),
|
|
252
|
-
addEvent("workflow-error", onWorkflowError)
|
|
253
|
-
);
|
|
254
|
-
engine.listen(plan.id, listeners);
|
|
255
|
-
const resolvers = {
|
|
256
|
-
credential: (id) => loadCredential(channel, id)
|
|
245
|
+
return {
|
|
246
|
+
[eventName]: wrappedFn
|
|
257
247
|
};
|
|
248
|
+
};
|
|
249
|
+
const listeners = Object.assign(
|
|
250
|
+
{},
|
|
251
|
+
addEvent("workflow-start", onWorkflowStart),
|
|
252
|
+
addEvent("job-start", onJobStart),
|
|
253
|
+
addEvent("job-complete", onJobComplete),
|
|
254
|
+
addEvent("workflow-log", onJobLog),
|
|
255
|
+
addEvent("workflow-complete", onWorkflowComplete),
|
|
256
|
+
addEvent("workflow-error", onWorkflowError)
|
|
257
|
+
);
|
|
258
|
+
engine.listen(plan.id, listeners);
|
|
259
|
+
const resolvers = {
|
|
260
|
+
credential: (id) => loadCredential(channel, id)
|
|
261
|
+
};
|
|
262
|
+
Promise.resolve().then(async () => {
|
|
258
263
|
if (typeof plan.initialState === "string") {
|
|
259
264
|
logger.debug("loading dataclip", plan.initialState);
|
|
260
265
|
plan.initialState = await loadDataclip(channel, plan.initialState);
|
|
261
266
|
logger.success("dataclip loaded");
|
|
262
267
|
logger.debug(plan.initialState);
|
|
263
268
|
}
|
|
269
|
+
return plan;
|
|
270
|
+
}).then(() => {
|
|
264
271
|
try {
|
|
265
272
|
engine.execute(plan, { resolvers, ...options });
|
|
266
273
|
} catch (e) {
|
|
267
274
|
onWorkflowError(context, { workflowId: plan.id, message: e.message });
|
|
268
|
-
reject(e);
|
|
269
275
|
}
|
|
270
276
|
});
|
|
277
|
+
return context;
|
|
271
278
|
}
|
|
272
279
|
var sendEvent = (channel, event, payload) => new Promise((resolve, reject) => {
|
|
273
280
|
channel.push(event, payload).receive("error", reject).receive("timeout", () => reject(new Error("timeout"))).receive("ok", resolve);
|
|
@@ -443,11 +450,11 @@ function connect(app, engine, logger, options = {}) {
|
|
|
443
450
|
}
|
|
444
451
|
logger.info("Starting workloop");
|
|
445
452
|
app.killWorkloop = workloop_default(
|
|
446
|
-
|
|
447
|
-
app.execute,
|
|
453
|
+
app,
|
|
448
454
|
logger,
|
|
449
455
|
options.backoff?.min || MIN_BACKOFF,
|
|
450
|
-
options.backoff?.max || MAX_BACKOFF
|
|
456
|
+
options.backoff?.max || MAX_BACKOFF,
|
|
457
|
+
options.maxWorkflows
|
|
451
458
|
);
|
|
452
459
|
} else {
|
|
453
460
|
logger.break();
|
|
@@ -484,23 +491,36 @@ function createServer(engine, options = {}) {
|
|
|
484
491
|
logger.debug(str);
|
|
485
492
|
})
|
|
486
493
|
);
|
|
494
|
+
app.workflows = {};
|
|
487
495
|
const server = app.listen(port);
|
|
488
496
|
logger.success(`ws-worker ${app.id} listening on ${port}`);
|
|
489
497
|
app.execute = async ({ id, token }) => {
|
|
490
498
|
if (app.socket) {
|
|
499
|
+
app.workflows[id] = true;
|
|
491
500
|
const {
|
|
492
501
|
channel: attemptChannel,
|
|
493
502
|
plan,
|
|
494
503
|
options: options2
|
|
495
504
|
} = await attempt_default(app.socket, token, id, logger);
|
|
496
|
-
|
|
505
|
+
const onComplete = () => {
|
|
506
|
+
delete app.workflows[id];
|
|
507
|
+
};
|
|
508
|
+
const context = execute(
|
|
509
|
+
attemptChannel,
|
|
510
|
+
engine,
|
|
511
|
+
logger,
|
|
512
|
+
plan,
|
|
513
|
+
options2,
|
|
514
|
+
onComplete
|
|
515
|
+
);
|
|
516
|
+
app.workflows[id] = context;
|
|
497
517
|
} else {
|
|
498
518
|
logger.error("No lightning socket established");
|
|
499
519
|
}
|
|
500
520
|
};
|
|
501
521
|
router.post("/claim", async (ctx) => {
|
|
502
522
|
logger.info("triggering claim from POST request");
|
|
503
|
-
return claim_default(app
|
|
523
|
+
return claim_default(app, logger, options.maxWorkflows).then(() => {
|
|
504
524
|
logger.info("claim complete: 1 attempt claimed");
|
|
505
525
|
ctx.body = "complete";
|
|
506
526
|
ctx.status = 200;
|
package/dist/start.js
CHANGED
|
@@ -4924,12 +4924,20 @@ async function createMock() {
|
|
|
4924
4924
|
dispatch("job-start", { workflowId, jobId, runId });
|
|
4925
4925
|
info("Running job " + jobId);
|
|
4926
4926
|
let nextState = initialState;
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
info("Parsing expression as JSON state");
|
|
4930
|
-
info(nextState);
|
|
4931
|
-
} catch (e) {
|
|
4927
|
+
if (expression?.startsWith?.("wait@")) {
|
|
4928
|
+
const [_, delay] = expression.split("@");
|
|
4932
4929
|
nextState = initialState;
|
|
4930
|
+
await new Promise((resolve5) => {
|
|
4931
|
+
setTimeout(() => resolve5(), parseInt(delay));
|
|
4932
|
+
});
|
|
4933
|
+
} else {
|
|
4934
|
+
try {
|
|
4935
|
+
nextState = JSON.parse(expression);
|
|
4936
|
+
info("Parsing expression as JSON state");
|
|
4937
|
+
info(nextState);
|
|
4938
|
+
} catch (e) {
|
|
4939
|
+
nextState = initialState;
|
|
4940
|
+
}
|
|
4933
4941
|
}
|
|
4934
4942
|
dispatch("job-complete", { workflowId, jobId, state: nextState, runId });
|
|
4935
4943
|
return nextState;
|
|
@@ -5037,17 +5045,21 @@ var RUN_COMPLETE = "run:complete";
|
|
|
5037
5045
|
|
|
5038
5046
|
// src/api/claim.ts
|
|
5039
5047
|
var mockLogger = createMockLogger();
|
|
5040
|
-
var claim = (
|
|
5048
|
+
var claim = (app, logger2 = mockLogger, maxWorkers = 5) => {
|
|
5041
5049
|
return new Promise((resolve5, reject) => {
|
|
5050
|
+
const activeWorkers = Object.keys(app.workflows).length;
|
|
5051
|
+
if (activeWorkers >= maxWorkers) {
|
|
5052
|
+
return reject(new Error("Server at capacity"));
|
|
5053
|
+
}
|
|
5042
5054
|
logger2.debug("requesting attempt...");
|
|
5043
|
-
channel.push(CLAIM, { demand: 1 }).receive("ok", ({ attempts }) => {
|
|
5055
|
+
app.channel.push(CLAIM, { demand: 1 }).receive("ok", ({ attempts }) => {
|
|
5044
5056
|
logger2.debug(`pulled ${attempts.length} attempts`);
|
|
5045
5057
|
if (!attempts?.length) {
|
|
5046
|
-
return reject(new Error("
|
|
5058
|
+
return reject(new Error("No attempts returned"));
|
|
5047
5059
|
}
|
|
5048
5060
|
attempts.forEach((attempt) => {
|
|
5049
5061
|
logger2.debug("starting attempt", attempt.id);
|
|
5050
|
-
|
|
5062
|
+
app.execute(attempt);
|
|
5051
5063
|
resolve5();
|
|
5052
5064
|
});
|
|
5053
5065
|
});
|
|
@@ -5056,18 +5068,18 @@ var claim = (channel, execute2, logger2 = mockLogger) => {
|
|
|
5056
5068
|
var claim_default = claim;
|
|
5057
5069
|
|
|
5058
5070
|
// src/api/workloop.ts
|
|
5059
|
-
var startWorkloop = (
|
|
5071
|
+
var startWorkloop = (app, logger2, minBackoff2, maxBackoff2, maxWorkers) => {
|
|
5060
5072
|
let promise;
|
|
5061
5073
|
let cancelled = false;
|
|
5062
5074
|
const workLoop = () => {
|
|
5063
5075
|
if (!cancelled) {
|
|
5064
|
-
promise = try_with_backoff_default(() => claim_default(
|
|
5076
|
+
promise = try_with_backoff_default(() => claim_default(app, logger2, maxWorkers), {
|
|
5065
5077
|
min: minBackoff2,
|
|
5066
5078
|
max: maxBackoff2
|
|
5067
5079
|
});
|
|
5068
5080
|
promise.then(() => {
|
|
5069
5081
|
if (!cancelled) {
|
|
5070
|
-
workLoop
|
|
5082
|
+
setTimeout(workLoop, minBackoff2);
|
|
5071
5083
|
}
|
|
5072
5084
|
});
|
|
5073
5085
|
}
|
|
@@ -5189,59 +5201,62 @@ var eventMap = {
|
|
|
5189
5201
|
log: ATTEMPT_LOG,
|
|
5190
5202
|
"workflow-complete": ATTEMPT_COMPLETE
|
|
5191
5203
|
};
|
|
5192
|
-
function execute(channel, engine, logger2, plan, options = {}) {
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
};
|
|
5215
|
-
return {
|
|
5216
|
-
[eventName]: wrappedFn
|
|
5217
|
-
};
|
|
5204
|
+
function execute(channel, engine, logger2, plan, options = {}, onComplete = (_result) => {
|
|
5205
|
+
}) {
|
|
5206
|
+
logger2.info("execute...");
|
|
5207
|
+
const state = {
|
|
5208
|
+
plan,
|
|
5209
|
+
lastDataclipId: plan.initialState,
|
|
5210
|
+
dataclips: {},
|
|
5211
|
+
options
|
|
5212
|
+
};
|
|
5213
|
+
const context = { channel, state, logger: logger2, onComplete };
|
|
5214
|
+
const addEvent = (eventName, handler) => {
|
|
5215
|
+
const wrappedFn = async (event) => {
|
|
5216
|
+
const lightningEvent = eventMap[eventName];
|
|
5217
|
+
try {
|
|
5218
|
+
await handler(context, event);
|
|
5219
|
+
logger2.info(`${plan.id} :: ${lightningEvent} :: OK`);
|
|
5220
|
+
} catch (e) {
|
|
5221
|
+
logger2.error(
|
|
5222
|
+
`${plan.id} :: ${lightningEvent} :: ERR: ${e.message || e.toString()}`
|
|
5223
|
+
);
|
|
5224
|
+
logger2.error(e);
|
|
5225
|
+
}
|
|
5218
5226
|
};
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
addEvent("workflow-start", onWorkflowStart),
|
|
5222
|
-
addEvent("job-start", onJobStart),
|
|
5223
|
-
addEvent("job-complete", onJobComplete),
|
|
5224
|
-
addEvent("workflow-log", onJobLog),
|
|
5225
|
-
addEvent("workflow-complete", onWorkflowComplete),
|
|
5226
|
-
addEvent("workflow-error", onWorkflowError)
|
|
5227
|
-
);
|
|
5228
|
-
engine.listen(plan.id, listeners);
|
|
5229
|
-
const resolvers = {
|
|
5230
|
-
credential: (id) => loadCredential(channel, id)
|
|
5227
|
+
return {
|
|
5228
|
+
[eventName]: wrappedFn
|
|
5231
5229
|
};
|
|
5230
|
+
};
|
|
5231
|
+
const listeners = Object.assign(
|
|
5232
|
+
{},
|
|
5233
|
+
addEvent("workflow-start", onWorkflowStart),
|
|
5234
|
+
addEvent("job-start", onJobStart),
|
|
5235
|
+
addEvent("job-complete", onJobComplete),
|
|
5236
|
+
addEvent("workflow-log", onJobLog),
|
|
5237
|
+
addEvent("workflow-complete", onWorkflowComplete),
|
|
5238
|
+
addEvent("workflow-error", onWorkflowError)
|
|
5239
|
+
);
|
|
5240
|
+
engine.listen(plan.id, listeners);
|
|
5241
|
+
const resolvers = {
|
|
5242
|
+
credential: (id) => loadCredential(channel, id)
|
|
5243
|
+
};
|
|
5244
|
+
Promise.resolve().then(async () => {
|
|
5232
5245
|
if (typeof plan.initialState === "string") {
|
|
5233
5246
|
logger2.debug("loading dataclip", plan.initialState);
|
|
5234
5247
|
plan.initialState = await loadDataclip(channel, plan.initialState);
|
|
5235
5248
|
logger2.success("dataclip loaded");
|
|
5236
5249
|
logger2.debug(plan.initialState);
|
|
5237
5250
|
}
|
|
5251
|
+
return plan;
|
|
5252
|
+
}).then(() => {
|
|
5238
5253
|
try {
|
|
5239
5254
|
engine.execute(plan, { resolvers, ...options });
|
|
5240
5255
|
} catch (e) {
|
|
5241
5256
|
onWorkflowError(context, { workflowId: plan.id, message: e.message });
|
|
5242
|
-
reject(e);
|
|
5243
5257
|
}
|
|
5244
5258
|
});
|
|
5259
|
+
return context;
|
|
5245
5260
|
}
|
|
5246
5261
|
var sendEvent = (channel, event, payload) => new Promise((resolve5, reject) => {
|
|
5247
5262
|
channel.push(event, payload).receive("error", reject).receive("timeout", () => reject(new Error("timeout"))).receive("ok", resolve5);
|
|
@@ -5417,11 +5432,11 @@ function connect(app, engine, logger2, options = {}) {
|
|
|
5417
5432
|
}
|
|
5418
5433
|
logger2.info("Starting workloop");
|
|
5419
5434
|
app.killWorkloop = workloop_default(
|
|
5420
|
-
|
|
5421
|
-
app.execute,
|
|
5435
|
+
app,
|
|
5422
5436
|
logger2,
|
|
5423
5437
|
options.backoff?.min || MIN_BACKOFF,
|
|
5424
|
-
options.backoff?.max || MAX_BACKOFF
|
|
5438
|
+
options.backoff?.max || MAX_BACKOFF,
|
|
5439
|
+
options.maxWorkflows
|
|
5425
5440
|
);
|
|
5426
5441
|
} else {
|
|
5427
5442
|
logger2.break();
|
|
@@ -5458,23 +5473,36 @@ function createServer(engine, options = {}) {
|
|
|
5458
5473
|
logger2.debug(str);
|
|
5459
5474
|
})
|
|
5460
5475
|
);
|
|
5476
|
+
app.workflows = {};
|
|
5461
5477
|
const server = app.listen(port);
|
|
5462
5478
|
logger2.success(`ws-worker ${app.id} listening on ${port}`);
|
|
5463
5479
|
app.execute = async ({ id, token }) => {
|
|
5464
5480
|
if (app.socket) {
|
|
5481
|
+
app.workflows[id] = true;
|
|
5465
5482
|
const {
|
|
5466
5483
|
channel: attemptChannel,
|
|
5467
5484
|
plan,
|
|
5468
5485
|
options: options2
|
|
5469
5486
|
} = await attempt_default(app.socket, token, id, logger2);
|
|
5470
|
-
|
|
5487
|
+
const onComplete = () => {
|
|
5488
|
+
delete app.workflows[id];
|
|
5489
|
+
};
|
|
5490
|
+
const context = execute(
|
|
5491
|
+
attemptChannel,
|
|
5492
|
+
engine,
|
|
5493
|
+
logger2,
|
|
5494
|
+
plan,
|
|
5495
|
+
options2,
|
|
5496
|
+
onComplete
|
|
5497
|
+
);
|
|
5498
|
+
app.workflows[id] = context;
|
|
5471
5499
|
} else {
|
|
5472
5500
|
logger2.error("No lightning socket established");
|
|
5473
5501
|
}
|
|
5474
5502
|
};
|
|
5475
5503
|
router.post("/claim", async (ctx) => {
|
|
5476
5504
|
logger2.info("triggering claim from POST request");
|
|
5477
|
-
return claim_default(app
|
|
5505
|
+
return claim_default(app, logger2, options.maxWorkflows).then(() => {
|
|
5478
5506
|
logger2.info("claim complete: 1 attempt claimed");
|
|
5479
5507
|
ctx.body = "complete";
|
|
5480
5508
|
ctx.status = 200;
|
|
@@ -5535,6 +5563,10 @@ var args = yargs_default(hideBin(process.argv)).command("server", "Start a ws-wo
|
|
|
5535
5563
|
}).option("backoff", {
|
|
5536
5564
|
description: "Claim backoff rules: min/max (s)",
|
|
5537
5565
|
default: "1/10"
|
|
5566
|
+
}).option("capacity", {
|
|
5567
|
+
description: "max concurrent workers",
|
|
5568
|
+
default: 5,
|
|
5569
|
+
type: "number"
|
|
5538
5570
|
}).parse();
|
|
5539
5571
|
var logger = createLogger("SRV", { level: args.log });
|
|
5540
5572
|
if (args.lightning === "mock") {
|
|
@@ -5562,7 +5594,8 @@ function engineReady(engine) {
|
|
|
5562
5594
|
backoff: {
|
|
5563
5595
|
min: minBackoff,
|
|
5564
5596
|
max: maxBackoff
|
|
5565
|
-
}
|
|
5597
|
+
},
|
|
5598
|
+
maxWorkflows: args.capacity
|
|
5566
5599
|
});
|
|
5567
5600
|
}
|
|
5568
5601
|
if (args.mock) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openfn/ws-worker",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "A Websocket Worker to connect Lightning to a Runtime Engine",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"koa-logger": "^3.2.1",
|
|
22
22
|
"phoenix": "^1.7.7",
|
|
23
23
|
"ws": "^8.14.1",
|
|
24
|
-
"@openfn/engine-multi": "0.1.
|
|
24
|
+
"@openfn/engine-multi": "0.1.4",
|
|
25
25
|
"@openfn/logger": "0.0.18",
|
|
26
26
|
"@openfn/runtime": "0.0.32"
|
|
27
27
|
},
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"tsup": "^6.2.3",
|
|
41
41
|
"typescript": "^4.6.4",
|
|
42
42
|
"yargs": "^17.6.2",
|
|
43
|
-
"@openfn/lightning-mock": "1.0.
|
|
43
|
+
"@openfn/lightning-mock": "1.0.5"
|
|
44
44
|
},
|
|
45
45
|
"files": [
|
|
46
46
|
"dist",
|