@openfn/ws-worker 1.4.1 → 1.5.1
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 +13 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +104 -37
- package/dist/start.js +127 -43
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# ws-worker
|
|
2
2
|
|
|
3
|
+
## 1.5.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- a08fb47: Update CLI docs
|
|
8
|
+
Add WORKER_MAX_SOCKET_TIMEOUT_SECONDS
|
|
9
|
+
|
|
10
|
+
## 1.5.0
|
|
11
|
+
|
|
12
|
+
### Minor Changes
|
|
13
|
+
|
|
14
|
+
- f363254: Allow a payload limit to be set for large dataclips and logs (payload_limit_mb)
|
|
15
|
+
|
|
3
16
|
## 1.4.1
|
|
4
17
|
|
|
5
18
|
### Patch Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -41,6 +41,7 @@ interface Channel extends Channel$1 {
|
|
|
41
41
|
|
|
42
42
|
declare type WorkerRunOptions = ExecuteOptions & {
|
|
43
43
|
outputDataclips?: boolean;
|
|
44
|
+
payloadLimitMb?: number;
|
|
44
45
|
};
|
|
45
46
|
|
|
46
47
|
declare type Context = {
|
|
@@ -64,6 +65,8 @@ declare type ServerOptions = {
|
|
|
64
65
|
min?: number;
|
|
65
66
|
max?: number;
|
|
66
67
|
};
|
|
68
|
+
socketTimeoutSeconds?: number;
|
|
69
|
+
payloadLimitMb?: number;
|
|
67
70
|
};
|
|
68
71
|
interface ServerApp extends Koa {
|
|
69
72
|
id: string;
|
package/dist/index.js
CHANGED
|
@@ -29,7 +29,7 @@ var name, version, description, main, type, scripts, bin, author, license, depen
|
|
|
29
29
|
var init_package = __esm({
|
|
30
30
|
"package.json"() {
|
|
31
31
|
name = "@openfn/ws-worker";
|
|
32
|
-
version = "1.
|
|
32
|
+
version = "1.5.1";
|
|
33
33
|
description = "A Websocket Worker to connect Lightning to a Runtime Engine";
|
|
34
34
|
main = "dist/index.js";
|
|
35
35
|
type = "module";
|
|
@@ -167,7 +167,7 @@ var waitForRuns = (app, logger) => new Promise((resolve) => {
|
|
|
167
167
|
log();
|
|
168
168
|
app.events.on(INTERNAL_RUN_COMPLETE, onRunComplete);
|
|
169
169
|
} else {
|
|
170
|
-
logger.debug("No active
|
|
170
|
+
logger.debug("No active runs detected, closing immediately");
|
|
171
171
|
resolve();
|
|
172
172
|
}
|
|
173
173
|
});
|
|
@@ -242,7 +242,10 @@ var claim = (app, logger = mockLogger, options = {}) => {
|
|
|
242
242
|
}
|
|
243
243
|
logger.debug("requesting run...");
|
|
244
244
|
app.queueChannel.push(CLAIM, { demand: 1 }).receive("ok", ({ runs }) => {
|
|
245
|
-
logger.debug(
|
|
245
|
+
logger.debug(
|
|
246
|
+
`claimed ${runs.length} runs: `,
|
|
247
|
+
runs.map((r) => r.id).join(",")
|
|
248
|
+
);
|
|
246
249
|
if (!runs?.length) {
|
|
247
250
|
return reject(new Error("No runs returned"));
|
|
248
251
|
}
|
|
@@ -265,10 +268,10 @@ var claim = (app, logger = mockLogger, options = {}) => {
|
|
|
265
268
|
app.execute(run);
|
|
266
269
|
resolve();
|
|
267
270
|
});
|
|
268
|
-
}).receive("error", () => {
|
|
269
|
-
logger.
|
|
271
|
+
}).receive("error", (e) => {
|
|
272
|
+
logger.error("Error on claim", e);
|
|
270
273
|
}).receive("timeout", () => {
|
|
271
|
-
logger.
|
|
274
|
+
logger.error("TIMEOUT on claim. Runs may be lost.");
|
|
272
275
|
reject(new Error("timeout"));
|
|
273
276
|
});
|
|
274
277
|
});
|
|
@@ -332,13 +335,19 @@ var convert_lightning_plan_default = (run) => {
|
|
|
332
335
|
const runtimeOpts = {};
|
|
333
336
|
const engineOpts = {};
|
|
334
337
|
if (run.options) {
|
|
335
|
-
if (run.options
|
|
338
|
+
if ("run_timeout_ms" in run.options) {
|
|
336
339
|
engineOpts.runTimeoutMs = run.options.run_timeout_ms;
|
|
337
340
|
}
|
|
338
|
-
if (run.options
|
|
341
|
+
if ("payload_limit_mb" in run.options) {
|
|
342
|
+
engineOpts.payloadLimitMb = run.options.payload_limit_mb;
|
|
343
|
+
}
|
|
344
|
+
if ("run_memory_limit_mb" in run.options) {
|
|
345
|
+
engineOpts.memoryLimitMb = run.options.run_memory_limit_mb;
|
|
346
|
+
}
|
|
347
|
+
if ("sanitize" in run.options) {
|
|
339
348
|
engineOpts.sanitize = run.options.sanitize;
|
|
340
349
|
}
|
|
341
|
-
if (run.options
|
|
350
|
+
if ("output_dataclips" in run.options) {
|
|
342
351
|
engineOpts.outputDataclips = run.options.output_dataclips;
|
|
343
352
|
}
|
|
344
353
|
}
|
|
@@ -527,7 +536,7 @@ ${prefix("worker")}${versions.worker || "unknown"}`;
|
|
|
527
536
|
// src/events/run-start.ts
|
|
528
537
|
init_package();
|
|
529
538
|
async function onRunStart(context, event) {
|
|
530
|
-
const { channel, state } = context;
|
|
539
|
+
const { channel, state, options = {} } = context;
|
|
531
540
|
const time = (timestamp() - BigInt(1e7)).toString();
|
|
532
541
|
const versionLogContext = {
|
|
533
542
|
...context,
|
|
@@ -541,6 +550,14 @@ async function onRunStart(context, event) {
|
|
|
541
550
|
...event.versions
|
|
542
551
|
};
|
|
543
552
|
await sendEvent(channel, RUN_START, { versions });
|
|
553
|
+
if ("payloadLimitMb" in options) {
|
|
554
|
+
await onJobLog(versionLogContext, {
|
|
555
|
+
time,
|
|
556
|
+
message: [`Payload limit: ${options.payloadLimitMb}mb`],
|
|
557
|
+
level: "info",
|
|
558
|
+
name: "RTE"
|
|
559
|
+
});
|
|
560
|
+
}
|
|
544
561
|
const versionMessage = versions_default(versions);
|
|
545
562
|
await onJobLog(versionLogContext, {
|
|
546
563
|
time,
|
|
@@ -552,6 +569,7 @@ async function onRunStart(context, event) {
|
|
|
552
569
|
|
|
553
570
|
// src/events/step-complete.ts
|
|
554
571
|
import crypto3 from "node:crypto";
|
|
572
|
+
import { timestamp as timestamp2 } from "@openfn/logger";
|
|
555
573
|
|
|
556
574
|
// src/api/reasons.ts
|
|
557
575
|
var calculateJobExitReason = (jobId, state = { data: {} }, error) => {
|
|
@@ -586,8 +604,25 @@ var calculateRunExitReason = (state) => {
|
|
|
586
604
|
return { reason: "success", error_type: null, error_message: null };
|
|
587
605
|
};
|
|
588
606
|
|
|
607
|
+
// src/util/ensure-payload-size.ts
|
|
608
|
+
var ensure_payload_size_default = (payload, limit_mb) => {
|
|
609
|
+
if (!isNaN(limit_mb)) {
|
|
610
|
+
const limit = limit_mb;
|
|
611
|
+
const size_bytes = Buffer.byteLength(payload, "utf8");
|
|
612
|
+
const size_mb = size_bytes / 1024 / 1024;
|
|
613
|
+
if (size_mb > limit) {
|
|
614
|
+
const e = new Error();
|
|
615
|
+
e.severity = "kill";
|
|
616
|
+
e.name = "PAYLOAD_TOO_LARGE";
|
|
617
|
+
e.message = `The payload exceeded the size limit of ${limit}mb`;
|
|
618
|
+
throw e;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
};
|
|
622
|
+
|
|
589
623
|
// src/events/step-complete.ts
|
|
590
|
-
function onStepComplete(
|
|
624
|
+
async function onStepComplete(context, event, error) {
|
|
625
|
+
const { channel, state, options } = context;
|
|
591
626
|
const dataclipId = crypto3.randomUUID();
|
|
592
627
|
const step_id = state.activeStep;
|
|
593
628
|
const job_id = state.activeJob;
|
|
@@ -602,26 +637,35 @@ function onStepComplete({ channel, state, options }, event, error) {
|
|
|
602
637
|
event.next?.forEach((nextJobId) => {
|
|
603
638
|
state.inputDataclips[nextJobId] = dataclipId;
|
|
604
639
|
});
|
|
605
|
-
const { reason, error_message, error_type } = calculateJobExitReason(
|
|
606
|
-
job_id,
|
|
607
|
-
event.state,
|
|
608
|
-
error
|
|
609
|
-
);
|
|
610
|
-
state.reasons[job_id] = { reason, error_message, error_type };
|
|
611
640
|
const evt = {
|
|
612
641
|
step_id,
|
|
613
642
|
job_id,
|
|
614
|
-
output_dataclip_id: dataclipId,
|
|
615
|
-
reason,
|
|
616
|
-
error_message,
|
|
617
|
-
error_type,
|
|
618
643
|
mem: event.mem,
|
|
619
644
|
duration: event.duration,
|
|
620
645
|
thread_id: event.threadId
|
|
621
646
|
};
|
|
622
|
-
|
|
623
|
-
|
|
647
|
+
try {
|
|
648
|
+
if (!options || options.outputDataclips !== false) {
|
|
649
|
+
const payload = stringify_default(outputState);
|
|
650
|
+
ensure_payload_size_default(payload, options?.payloadLimitMb);
|
|
651
|
+
evt.output_dataclip = payload;
|
|
652
|
+
}
|
|
653
|
+
evt.output_dataclip_id = dataclipId;
|
|
654
|
+
} catch (e) {
|
|
655
|
+
evt.output_dataclip_error = "DATACLIP_TOO_LARGE";
|
|
656
|
+
const time = (timestamp2() - BigInt(1e7)).toString();
|
|
657
|
+
await onJobLog(context, {
|
|
658
|
+
time,
|
|
659
|
+
message: [
|
|
660
|
+
"Dataclip too large. This dataclip will not be sent back to lighting."
|
|
661
|
+
],
|
|
662
|
+
level: "info",
|
|
663
|
+
name: "R/T"
|
|
664
|
+
});
|
|
624
665
|
}
|
|
666
|
+
const reason = calculateJobExitReason(job_id, event.state, error);
|
|
667
|
+
state.reasons[job_id] = reason;
|
|
668
|
+
Object.assign(evt, reason);
|
|
625
669
|
return sendEvent(channel, STEP_COMPLETE, evt);
|
|
626
670
|
}
|
|
627
671
|
|
|
@@ -640,9 +684,9 @@ async function onStepStart(context, event) {
|
|
|
640
684
|
}
|
|
641
685
|
|
|
642
686
|
// src/util/log-final-reason.ts
|
|
643
|
-
import { timestamp as
|
|
687
|
+
import { timestamp as timestamp3 } from "@openfn/logger";
|
|
644
688
|
var log_final_reason_default = async (context, reason) => {
|
|
645
|
-
const time = (
|
|
689
|
+
const time = (timestamp3() - BigInt(1e7)).toString();
|
|
646
690
|
let message = `Run complete with status: ${reason.reason}`;
|
|
647
691
|
if (reason.reason !== "success") {
|
|
648
692
|
message += `
|
|
@@ -792,11 +836,25 @@ function onJobError(context, event) {
|
|
|
792
836
|
return onStepComplete(context, event, event.error);
|
|
793
837
|
}
|
|
794
838
|
}
|
|
795
|
-
function onJobLog({ channel, state }, event) {
|
|
839
|
+
function onJobLog({ channel, state, options }, event) {
|
|
796
840
|
const timeInMicroseconds = BigInt(event.time) / BigInt(1e3);
|
|
841
|
+
let message = event.message;
|
|
842
|
+
try {
|
|
843
|
+
if (typeof event.message === "string") {
|
|
844
|
+
ensure_payload_size_default(event.message, options?.payloadLimitMb);
|
|
845
|
+
message = JSON.parse(message);
|
|
846
|
+
} else if (event.message) {
|
|
847
|
+
const payload = stringify_default(event.message);
|
|
848
|
+
ensure_payload_size_default(payload, options?.payloadLimitMb);
|
|
849
|
+
}
|
|
850
|
+
} catch (e) {
|
|
851
|
+
message = [
|
|
852
|
+
`(Log message redacted: exceeds ${options.payloadLimitMb}mb memory limit)`
|
|
853
|
+
];
|
|
854
|
+
}
|
|
797
855
|
const log = {
|
|
798
856
|
run_id: state.plan.id,
|
|
799
|
-
message
|
|
857
|
+
message,
|
|
800
858
|
source: event.name,
|
|
801
859
|
level: event.level,
|
|
802
860
|
timestamp: timeInMicroseconds.toString()
|
|
@@ -887,7 +945,7 @@ var generateWorkerToken = async (secret, workerId, logger) => {
|
|
|
887
945
|
var worker_token_default = generateWorkerToken;
|
|
888
946
|
|
|
889
947
|
// src/channels/worker-queue.ts
|
|
890
|
-
var connectToWorkerQueue = (endpoint, serverId, secret, logger, SocketConstructor = PhxSocket) => {
|
|
948
|
+
var connectToWorkerQueue = (endpoint, serverId, secret, timeout = 10, logger, SocketConstructor = PhxSocket) => {
|
|
891
949
|
const events = new EventEmitter();
|
|
892
950
|
worker_token_default(secret, serverId, logger).then(async (token) => {
|
|
893
951
|
const pkg = await Promise.resolve().then(() => (init_package(), package_exports));
|
|
@@ -898,7 +956,8 @@ var connectToWorkerQueue = (endpoint, serverId, secret, logger, SocketConstructo
|
|
|
898
956
|
};
|
|
899
957
|
const socket = new SocketConstructor(endpoint, {
|
|
900
958
|
params,
|
|
901
|
-
transport: WebSocket
|
|
959
|
+
transport: WebSocket,
|
|
960
|
+
timeout: timeout * 1e3
|
|
902
961
|
});
|
|
903
962
|
let didOpen = false;
|
|
904
963
|
socket.onOpen(() => {
|
|
@@ -930,7 +989,7 @@ var connectToWorkerQueue = (endpoint, serverId, secret, logger, SocketConstructo
|
|
|
930
989
|
var worker_queue_default = connectToWorkerQueue;
|
|
931
990
|
|
|
932
991
|
// src/server.ts
|
|
933
|
-
var DEFAULT_PORT =
|
|
992
|
+
var DEFAULT_PORT = 2222;
|
|
934
993
|
var MIN_BACKOFF = 1e3;
|
|
935
994
|
var MAX_BACKOFF = 1e3 * 30;
|
|
936
995
|
function connect(app, logger, options = {}) {
|
|
@@ -952,13 +1011,12 @@ function connect(app, logger, options = {}) {
|
|
|
952
1011
|
options.maxWorkflows
|
|
953
1012
|
);
|
|
954
1013
|
} else {
|
|
1014
|
+
const port = app.server?.address().port;
|
|
955
1015
|
logger.break();
|
|
956
|
-
logger.warn("
|
|
1016
|
+
logger.warn("Noloop active: workloop has not started");
|
|
957
1017
|
logger.info("This server will not auto-pull work from lightning.");
|
|
958
1018
|
logger.info("You can manually claim by posting to /claim, eg:");
|
|
959
|
-
logger.info(
|
|
960
|
-
` curl -X POST http://locahost:${options.port || DEFAULT_PORT}/claim`
|
|
961
|
-
);
|
|
1019
|
+
logger.info(` curl -X POST http://localhost:${port}/claim`);
|
|
962
1020
|
logger.break();
|
|
963
1021
|
}
|
|
964
1022
|
};
|
|
@@ -982,7 +1040,13 @@ function connect(app, logger, options = {}) {
|
|
|
982
1040
|
);
|
|
983
1041
|
logger.debug(e);
|
|
984
1042
|
};
|
|
985
|
-
worker_queue_default(
|
|
1043
|
+
worker_queue_default(
|
|
1044
|
+
options.lightning,
|
|
1045
|
+
app.id,
|
|
1046
|
+
options.secret,
|
|
1047
|
+
options.socketTimeoutSeconds,
|
|
1048
|
+
logger
|
|
1049
|
+
).on("connect", onConnect).on("disconnect", onDisconnect).on("error", onError);
|
|
986
1050
|
}
|
|
987
1051
|
function createServer(engine, options = {}) {
|
|
988
1052
|
const logger = options.logger || createMockLogger2();
|
|
@@ -1005,16 +1069,19 @@ function createServer(engine, options = {}) {
|
|
|
1005
1069
|
process.send?.("READY");
|
|
1006
1070
|
router.get("/livez", healthcheck_default);
|
|
1007
1071
|
router.get("/", healthcheck_default);
|
|
1008
|
-
app.options = options
|
|
1072
|
+
app.options = options;
|
|
1009
1073
|
app.execute = async ({ id, token }) => {
|
|
1010
1074
|
if (app.socket) {
|
|
1011
1075
|
app.workflows[id] = true;
|
|
1012
1076
|
const {
|
|
1013
1077
|
channel: runChannel,
|
|
1014
1078
|
plan,
|
|
1015
|
-
options: options2,
|
|
1079
|
+
options: options2 = {},
|
|
1016
1080
|
input
|
|
1017
1081
|
} = await run_default(app.socket, token, id, logger);
|
|
1082
|
+
if (!("payloadLimitMb" in options2)) {
|
|
1083
|
+
options2.payloadLimitMb = app.options.payloadLimitMb;
|
|
1084
|
+
}
|
|
1018
1085
|
const onFinish = () => {
|
|
1019
1086
|
logger.debug(`workflow ${id} complete: releasing worker`);
|
|
1020
1087
|
delete app.workflows[id];
|
package/dist/start.js
CHANGED
|
@@ -37,7 +37,7 @@ var name, version, description, main, type, scripts, bin, author, license, depen
|
|
|
37
37
|
var init_package = __esm({
|
|
38
38
|
"package.json"() {
|
|
39
39
|
name = "@openfn/ws-worker";
|
|
40
|
-
version = "1.
|
|
40
|
+
version = "1.5.1";
|
|
41
41
|
description = "A Websocket Worker to connect Lightning to a Runtime Engine";
|
|
42
42
|
main = "dist/index.js";
|
|
43
43
|
type = "module";
|
|
@@ -306,7 +306,7 @@ var waitForRuns = (app, logger2) => new Promise((resolve5) => {
|
|
|
306
306
|
log();
|
|
307
307
|
app.events.on(INTERNAL_RUN_COMPLETE, onRunComplete);
|
|
308
308
|
} else {
|
|
309
|
-
logger2.debug("No active
|
|
309
|
+
logger2.debug("No active runs detected, closing immediately");
|
|
310
310
|
resolve5();
|
|
311
311
|
}
|
|
312
312
|
});
|
|
@@ -381,7 +381,10 @@ var claim = (app, logger2 = mockLogger, options = {}) => {
|
|
|
381
381
|
}
|
|
382
382
|
logger2.debug("requesting run...");
|
|
383
383
|
app.queueChannel.push(CLAIM, { demand: 1 }).receive("ok", ({ runs }) => {
|
|
384
|
-
logger2.debug(
|
|
384
|
+
logger2.debug(
|
|
385
|
+
`claimed ${runs.length} runs: `,
|
|
386
|
+
runs.map((r) => r.id).join(",")
|
|
387
|
+
);
|
|
385
388
|
if (!runs?.length) {
|
|
386
389
|
return reject(new Error("No runs returned"));
|
|
387
390
|
}
|
|
@@ -404,10 +407,10 @@ var claim = (app, logger2 = mockLogger, options = {}) => {
|
|
|
404
407
|
app.execute(run2);
|
|
405
408
|
resolve5();
|
|
406
409
|
});
|
|
407
|
-
}).receive("error", () => {
|
|
408
|
-
logger2.
|
|
410
|
+
}).receive("error", (e) => {
|
|
411
|
+
logger2.error("Error on claim", e);
|
|
409
412
|
}).receive("timeout", () => {
|
|
410
|
-
logger2.
|
|
413
|
+
logger2.error("TIMEOUT on claim. Runs may be lost.");
|
|
411
414
|
reject(new Error("timeout"));
|
|
412
415
|
});
|
|
413
416
|
});
|
|
@@ -471,13 +474,19 @@ var convert_lightning_plan_default = (run2) => {
|
|
|
471
474
|
const runtimeOpts = {};
|
|
472
475
|
const engineOpts = {};
|
|
473
476
|
if (run2.options) {
|
|
474
|
-
if (run2.options
|
|
477
|
+
if ("run_timeout_ms" in run2.options) {
|
|
475
478
|
engineOpts.runTimeoutMs = run2.options.run_timeout_ms;
|
|
476
479
|
}
|
|
477
|
-
if (run2.options
|
|
480
|
+
if ("payload_limit_mb" in run2.options) {
|
|
481
|
+
engineOpts.payloadLimitMb = run2.options.payload_limit_mb;
|
|
482
|
+
}
|
|
483
|
+
if ("run_memory_limit_mb" in run2.options) {
|
|
484
|
+
engineOpts.memoryLimitMb = run2.options.run_memory_limit_mb;
|
|
485
|
+
}
|
|
486
|
+
if ("sanitize" in run2.options) {
|
|
478
487
|
engineOpts.sanitize = run2.options.sanitize;
|
|
479
488
|
}
|
|
480
|
-
if (run2.options
|
|
489
|
+
if ("output_dataclips" in run2.options) {
|
|
481
490
|
engineOpts.outputDataclips = run2.options.output_dataclips;
|
|
482
491
|
}
|
|
483
492
|
}
|
|
@@ -666,7 +675,7 @@ ${prefix("worker")}${versions.worker || "unknown"}`;
|
|
|
666
675
|
// src/events/run-start.ts
|
|
667
676
|
init_package();
|
|
668
677
|
async function onRunStart(context, event) {
|
|
669
|
-
const { channel, state } = context;
|
|
678
|
+
const { channel, state, options = {} } = context;
|
|
670
679
|
const time = (timestamp() - BigInt(1e7)).toString();
|
|
671
680
|
const versionLogContext = {
|
|
672
681
|
...context,
|
|
@@ -680,6 +689,14 @@ async function onRunStart(context, event) {
|
|
|
680
689
|
...event.versions
|
|
681
690
|
};
|
|
682
691
|
await sendEvent(channel, RUN_START, { versions });
|
|
692
|
+
if ("payloadLimitMb" in options) {
|
|
693
|
+
await onJobLog(versionLogContext, {
|
|
694
|
+
time,
|
|
695
|
+
message: [`Payload limit: ${options.payloadLimitMb}mb`],
|
|
696
|
+
level: "info",
|
|
697
|
+
name: "RTE"
|
|
698
|
+
});
|
|
699
|
+
}
|
|
683
700
|
const versionMessage = versions_default(versions);
|
|
684
701
|
await onJobLog(versionLogContext, {
|
|
685
702
|
time,
|
|
@@ -691,6 +708,7 @@ async function onRunStart(context, event) {
|
|
|
691
708
|
|
|
692
709
|
// src/events/step-complete.ts
|
|
693
710
|
import crypto4 from "node:crypto";
|
|
711
|
+
import { timestamp as timestamp2 } from "@openfn/logger";
|
|
694
712
|
|
|
695
713
|
// src/api/reasons.ts
|
|
696
714
|
var calculateJobExitReason = (jobId, state = { data: {} }, error) => {
|
|
@@ -725,8 +743,25 @@ var calculateRunExitReason = (state) => {
|
|
|
725
743
|
return { reason: "success", error_type: null, error_message: null };
|
|
726
744
|
};
|
|
727
745
|
|
|
746
|
+
// src/util/ensure-payload-size.ts
|
|
747
|
+
var ensure_payload_size_default = (payload, limit_mb) => {
|
|
748
|
+
if (!isNaN(limit_mb)) {
|
|
749
|
+
const limit = limit_mb;
|
|
750
|
+
const size_bytes = Buffer.byteLength(payload, "utf8");
|
|
751
|
+
const size_mb = size_bytes / 1024 / 1024;
|
|
752
|
+
if (size_mb > limit) {
|
|
753
|
+
const e = new Error();
|
|
754
|
+
e.severity = "kill";
|
|
755
|
+
e.name = "PAYLOAD_TOO_LARGE";
|
|
756
|
+
e.message = `The payload exceeded the size limit of ${limit}mb`;
|
|
757
|
+
throw e;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
|
|
728
762
|
// src/events/step-complete.ts
|
|
729
|
-
function onStepComplete(
|
|
763
|
+
async function onStepComplete(context, event, error) {
|
|
764
|
+
const { channel, state, options } = context;
|
|
730
765
|
const dataclipId = crypto4.randomUUID();
|
|
731
766
|
const step_id = state.activeStep;
|
|
732
767
|
const job_id = state.activeJob;
|
|
@@ -741,26 +776,35 @@ function onStepComplete({ channel, state, options }, event, error) {
|
|
|
741
776
|
event.next?.forEach((nextJobId) => {
|
|
742
777
|
state.inputDataclips[nextJobId] = dataclipId;
|
|
743
778
|
});
|
|
744
|
-
const { reason, error_message, error_type } = calculateJobExitReason(
|
|
745
|
-
job_id,
|
|
746
|
-
event.state,
|
|
747
|
-
error
|
|
748
|
-
);
|
|
749
|
-
state.reasons[job_id] = { reason, error_message, error_type };
|
|
750
779
|
const evt = {
|
|
751
780
|
step_id,
|
|
752
781
|
job_id,
|
|
753
|
-
output_dataclip_id: dataclipId,
|
|
754
|
-
reason,
|
|
755
|
-
error_message,
|
|
756
|
-
error_type,
|
|
757
782
|
mem: event.mem,
|
|
758
783
|
duration: event.duration,
|
|
759
784
|
thread_id: event.threadId
|
|
760
785
|
};
|
|
761
|
-
|
|
762
|
-
|
|
786
|
+
try {
|
|
787
|
+
if (!options || options.outputDataclips !== false) {
|
|
788
|
+
const payload = stringify_default(outputState);
|
|
789
|
+
ensure_payload_size_default(payload, options?.payloadLimitMb);
|
|
790
|
+
evt.output_dataclip = payload;
|
|
791
|
+
}
|
|
792
|
+
evt.output_dataclip_id = dataclipId;
|
|
793
|
+
} catch (e) {
|
|
794
|
+
evt.output_dataclip_error = "DATACLIP_TOO_LARGE";
|
|
795
|
+
const time = (timestamp2() - BigInt(1e7)).toString();
|
|
796
|
+
await onJobLog(context, {
|
|
797
|
+
time,
|
|
798
|
+
message: [
|
|
799
|
+
"Dataclip too large. This dataclip will not be sent back to lighting."
|
|
800
|
+
],
|
|
801
|
+
level: "info",
|
|
802
|
+
name: "R/T"
|
|
803
|
+
});
|
|
763
804
|
}
|
|
805
|
+
const reason = calculateJobExitReason(job_id, event.state, error);
|
|
806
|
+
state.reasons[job_id] = reason;
|
|
807
|
+
Object.assign(evt, reason);
|
|
764
808
|
return sendEvent(channel, STEP_COMPLETE, evt);
|
|
765
809
|
}
|
|
766
810
|
|
|
@@ -779,9 +823,9 @@ async function onStepStart(context, event) {
|
|
|
779
823
|
}
|
|
780
824
|
|
|
781
825
|
// src/util/log-final-reason.ts
|
|
782
|
-
import { timestamp as
|
|
826
|
+
import { timestamp as timestamp3 } from "@openfn/logger";
|
|
783
827
|
var log_final_reason_default = async (context, reason) => {
|
|
784
|
-
const time = (
|
|
828
|
+
const time = (timestamp3() - BigInt(1e7)).toString();
|
|
785
829
|
let message = `Run complete with status: ${reason.reason}`;
|
|
786
830
|
if (reason.reason !== "success") {
|
|
787
831
|
message += `
|
|
@@ -931,11 +975,25 @@ function onJobError(context, event) {
|
|
|
931
975
|
return onStepComplete(context, event, event.error);
|
|
932
976
|
}
|
|
933
977
|
}
|
|
934
|
-
function onJobLog({ channel, state }, event) {
|
|
978
|
+
function onJobLog({ channel, state, options }, event) {
|
|
935
979
|
const timeInMicroseconds = BigInt(event.time) / BigInt(1e3);
|
|
980
|
+
let message = event.message;
|
|
981
|
+
try {
|
|
982
|
+
if (typeof event.message === "string") {
|
|
983
|
+
ensure_payload_size_default(event.message, options?.payloadLimitMb);
|
|
984
|
+
message = JSON.parse(message);
|
|
985
|
+
} else if (event.message) {
|
|
986
|
+
const payload = stringify_default(event.message);
|
|
987
|
+
ensure_payload_size_default(payload, options?.payloadLimitMb);
|
|
988
|
+
}
|
|
989
|
+
} catch (e) {
|
|
990
|
+
message = [
|
|
991
|
+
`(Log message redacted: exceeds ${options.payloadLimitMb}mb memory limit)`
|
|
992
|
+
];
|
|
993
|
+
}
|
|
936
994
|
const log = {
|
|
937
995
|
run_id: state.plan.id,
|
|
938
|
-
message
|
|
996
|
+
message,
|
|
939
997
|
source: event.name,
|
|
940
998
|
level: event.level,
|
|
941
999
|
timestamp: timeInMicroseconds.toString()
|
|
@@ -1026,7 +1084,7 @@ var generateWorkerToken = async (secret, workerId, logger2) => {
|
|
|
1026
1084
|
var worker_token_default = generateWorkerToken;
|
|
1027
1085
|
|
|
1028
1086
|
// src/channels/worker-queue.ts
|
|
1029
|
-
var connectToWorkerQueue = (endpoint, serverId, secret, logger2, SocketConstructor = PhxSocket) => {
|
|
1087
|
+
var connectToWorkerQueue = (endpoint, serverId, secret, timeout = 10, logger2, SocketConstructor = PhxSocket) => {
|
|
1030
1088
|
const events = new EventEmitter2();
|
|
1031
1089
|
worker_token_default(secret, serverId, logger2).then(async (token) => {
|
|
1032
1090
|
const pkg = await Promise.resolve().then(() => (init_package(), package_exports));
|
|
@@ -1037,7 +1095,8 @@ var connectToWorkerQueue = (endpoint, serverId, secret, logger2, SocketConstruct
|
|
|
1037
1095
|
};
|
|
1038
1096
|
const socket = new SocketConstructor(endpoint, {
|
|
1039
1097
|
params,
|
|
1040
|
-
transport: WebSocket
|
|
1098
|
+
transport: WebSocket,
|
|
1099
|
+
timeout: timeout * 1e3
|
|
1041
1100
|
});
|
|
1042
1101
|
let didOpen = false;
|
|
1043
1102
|
socket.onOpen(() => {
|
|
@@ -1069,7 +1128,7 @@ var connectToWorkerQueue = (endpoint, serverId, secret, logger2, SocketConstruct
|
|
|
1069
1128
|
var worker_queue_default = connectToWorkerQueue;
|
|
1070
1129
|
|
|
1071
1130
|
// src/server.ts
|
|
1072
|
-
var DEFAULT_PORT =
|
|
1131
|
+
var DEFAULT_PORT = 2222;
|
|
1073
1132
|
var MIN_BACKOFF = 1e3;
|
|
1074
1133
|
var MAX_BACKOFF = 1e3 * 30;
|
|
1075
1134
|
function connect(app, logger2, options = {}) {
|
|
@@ -1091,13 +1150,12 @@ function connect(app, logger2, options = {}) {
|
|
|
1091
1150
|
options.maxWorkflows
|
|
1092
1151
|
);
|
|
1093
1152
|
} else {
|
|
1153
|
+
const port = app.server?.address().port;
|
|
1094
1154
|
logger2.break();
|
|
1095
|
-
logger2.warn("
|
|
1155
|
+
logger2.warn("Noloop active: workloop has not started");
|
|
1096
1156
|
logger2.info("This server will not auto-pull work from lightning.");
|
|
1097
1157
|
logger2.info("You can manually claim by posting to /claim, eg:");
|
|
1098
|
-
logger2.info(
|
|
1099
|
-
` curl -X POST http://locahost:${options.port || DEFAULT_PORT}/claim`
|
|
1100
|
-
);
|
|
1158
|
+
logger2.info(` curl -X POST http://localhost:${port}/claim`);
|
|
1101
1159
|
logger2.break();
|
|
1102
1160
|
}
|
|
1103
1161
|
};
|
|
@@ -1121,7 +1179,13 @@ function connect(app, logger2, options = {}) {
|
|
|
1121
1179
|
);
|
|
1122
1180
|
logger2.debug(e);
|
|
1123
1181
|
};
|
|
1124
|
-
worker_queue_default(
|
|
1182
|
+
worker_queue_default(
|
|
1183
|
+
options.lightning,
|
|
1184
|
+
app.id,
|
|
1185
|
+
options.secret,
|
|
1186
|
+
options.socketTimeoutSeconds,
|
|
1187
|
+
logger2
|
|
1188
|
+
).on("connect", onConnect).on("disconnect", onDisconnect).on("error", onError);
|
|
1125
1189
|
}
|
|
1126
1190
|
function createServer(engine, options = {}) {
|
|
1127
1191
|
const logger2 = options.logger || createMockLogger2();
|
|
@@ -1144,16 +1208,19 @@ function createServer(engine, options = {}) {
|
|
|
1144
1208
|
process.send?.("READY");
|
|
1145
1209
|
router.get("/livez", healthcheck_default);
|
|
1146
1210
|
router.get("/", healthcheck_default);
|
|
1147
|
-
app.options = options
|
|
1211
|
+
app.options = options;
|
|
1148
1212
|
app.execute = async ({ id, token }) => {
|
|
1149
1213
|
if (app.socket) {
|
|
1150
1214
|
app.workflows[id] = true;
|
|
1151
1215
|
const {
|
|
1152
1216
|
channel: runChannel,
|
|
1153
1217
|
plan,
|
|
1154
|
-
options: options2,
|
|
1218
|
+
options: options2 = {},
|
|
1155
1219
|
input
|
|
1156
1220
|
} = await run_default(app.socket, token, id, logger2);
|
|
1221
|
+
if (!("payloadLimitMb" in options2)) {
|
|
1222
|
+
options2.payloadLimitMb = app.options.payloadLimitMb;
|
|
1223
|
+
}
|
|
1157
1224
|
const onFinish = () => {
|
|
1158
1225
|
logger2.debug(`workflow ${id} complete: releasing worker`);
|
|
1159
1226
|
delete app.workflows[id];
|
|
@@ -6066,6 +6133,9 @@ var Yargs = YargsFactory(esm_default);
|
|
|
6066
6133
|
var yargs_default = Yargs;
|
|
6067
6134
|
|
|
6068
6135
|
// src/util/cli.ts
|
|
6136
|
+
var DEFAULT_PORT2 = 2222;
|
|
6137
|
+
var DEFAULT_WORKER_CAPACITY = 5;
|
|
6138
|
+
var DEFAULT_SOCKET_TIMEOUT_SECONDS = 10;
|
|
6069
6139
|
function setArg(argValue, envValue, defaultValue) {
|
|
6070
6140
|
if (Array.isArray(defaultValue) && !argValue && typeof envValue === "string") {
|
|
6071
6141
|
return envValue.split(",");
|
|
@@ -6082,16 +6152,18 @@ function parseArgs(argv) {
|
|
|
6082
6152
|
WORKER_LIGHTNING_PUBLIC_KEY,
|
|
6083
6153
|
WORKER_LIGHTNING_SERVICE_URL,
|
|
6084
6154
|
WORKER_LOG_LEVEL,
|
|
6155
|
+
WORKER_MAX_PAYLOAD_MB,
|
|
6085
6156
|
WORKER_MAX_RUN_DURATION_SECONDS,
|
|
6086
6157
|
WORKER_MAX_RUN_MEMORY_MB,
|
|
6087
6158
|
WORKER_PORT,
|
|
6088
6159
|
WORKER_REPO_DIR,
|
|
6089
6160
|
WORKER_SECRET,
|
|
6090
|
-
WORKER_STATE_PROPS_TO_REMOVE
|
|
6161
|
+
WORKER_STATE_PROPS_TO_REMOVE,
|
|
6162
|
+
WORKER_SOCKET_TIMEOUT_SECONDS
|
|
6091
6163
|
} = process.env;
|
|
6092
6164
|
const parser2 = yargs_default(hideBin(argv)).command("server", "Start a ws-worker server").option("port", {
|
|
6093
6165
|
alias: "p",
|
|
6094
|
-
description:
|
|
6166
|
+
description: `Port to run the server on. Default ${DEFAULT_PORT2}. Env: WORKER_PORT`,
|
|
6095
6167
|
type: "number"
|
|
6096
6168
|
}).option("lightning", {
|
|
6097
6169
|
alias: ["l", "lightning-service-url"],
|
|
@@ -6102,6 +6174,8 @@ function parseArgs(argv) {
|
|
|
6102
6174
|
}).option("secret", {
|
|
6103
6175
|
alias: "s",
|
|
6104
6176
|
description: "Worker secret. (comes from WORKER_SECRET by default). Env: WORKER_SECRET"
|
|
6177
|
+
}).option("socket-timeout", {
|
|
6178
|
+
description: `Timeout for websockets to Lighting, in seconds. Defaults to 10.`
|
|
6105
6179
|
}).option("lightning-public-key", {
|
|
6106
6180
|
description: "Base64-encoded public key. Used to verify run tokens. Env: WORKER_LIGHTNING_PUBLIC_KEY"
|
|
6107
6181
|
}).option("log", {
|
|
@@ -6117,7 +6191,7 @@ function parseArgs(argv) {
|
|
|
6117
6191
|
}).option("backoff", {
|
|
6118
6192
|
description: "Claim backoff rules: min/max (in seconds). Env: WORKER_BACKOFF"
|
|
6119
6193
|
}).option("capacity", {
|
|
6120
|
-
description:
|
|
6194
|
+
description: `max concurrent workers. Default ${DEFAULT_WORKER_CAPACITY}. Env: WORKER_CAPACITY`,
|
|
6121
6195
|
type: "number"
|
|
6122
6196
|
}).option("state-props-to-remove", {
|
|
6123
6197
|
description: "A list of properties to remove from the final state returned by a job. Env: WORKER_STATE_PROPS_TO_REMOVE",
|
|
@@ -6125,6 +6199,9 @@ function parseArgs(argv) {
|
|
|
6125
6199
|
}).option("run-memory", {
|
|
6126
6200
|
description: "Maximum memory allocated to a single run, in mb. Env: WORKER_MAX_RUN_MEMORY_MB",
|
|
6127
6201
|
type: "number"
|
|
6202
|
+
}).option("payload-memory", {
|
|
6203
|
+
description: "Maximum memory allocated to a single run, in mb. Env: WORKER_MAX_PAYLOAD_MB",
|
|
6204
|
+
type: "number"
|
|
6128
6205
|
}).option("max-run-duration-seconds", {
|
|
6129
6206
|
alias: "t",
|
|
6130
6207
|
description: "Default run timeout for the server, in seconds. Env: WORKER_MAX_RUN_DURATION_SECONDS",
|
|
@@ -6133,7 +6210,7 @@ function parseArgs(argv) {
|
|
|
6133
6210
|
const args2 = parser2.parse();
|
|
6134
6211
|
return {
|
|
6135
6212
|
...args2,
|
|
6136
|
-
port: setArg(args2.port, WORKER_PORT,
|
|
6213
|
+
port: setArg(args2.port, WORKER_PORT, DEFAULT_PORT2),
|
|
6137
6214
|
lightning: setArg(
|
|
6138
6215
|
args2.lightning,
|
|
6139
6216
|
WORKER_LIGHTNING_SERVICE_URL,
|
|
@@ -6147,17 +6224,23 @@ function parseArgs(argv) {
|
|
|
6147
6224
|
),
|
|
6148
6225
|
log: setArg(args2.log, WORKER_LOG_LEVEL, "debug"),
|
|
6149
6226
|
backoff: setArg(args2.backoff, WORKER_BACKOFF, "1/10"),
|
|
6150
|
-
capacity: setArg(args2.capacity, WORKER_CAPACITY,
|
|
6227
|
+
capacity: setArg(args2.capacity, WORKER_CAPACITY, DEFAULT_WORKER_CAPACITY),
|
|
6151
6228
|
statePropsToRemove: setArg(
|
|
6152
6229
|
args2.statePropsToRemove,
|
|
6153
6230
|
WORKER_STATE_PROPS_TO_REMOVE,
|
|
6154
6231
|
["configuration", "response"]
|
|
6155
6232
|
),
|
|
6156
6233
|
runMemory: setArg(args2.runMemory, WORKER_MAX_RUN_MEMORY_MB, 500),
|
|
6234
|
+
payloadMemory: setArg(args2.payloadMemory, WORKER_MAX_PAYLOAD_MB, 10),
|
|
6157
6235
|
maxRunDurationSeconds: setArg(
|
|
6158
6236
|
args2.maxRunDurationSeconds,
|
|
6159
6237
|
WORKER_MAX_RUN_DURATION_SECONDS,
|
|
6160
6238
|
300
|
|
6239
|
+
),
|
|
6240
|
+
socketTimeoutSeconds: setArg(
|
|
6241
|
+
args2.socketTimeoutSeconds,
|
|
6242
|
+
WORKER_SOCKET_TIMEOUT_SECONDS,
|
|
6243
|
+
DEFAULT_SOCKET_TIMEOUT_SECONDS
|
|
6161
6244
|
)
|
|
6162
6245
|
};
|
|
6163
6246
|
}
|
|
@@ -6187,7 +6270,8 @@ function engineReady(engine) {
|
|
|
6187
6270
|
min: minBackoff,
|
|
6188
6271
|
max: maxBackoff
|
|
6189
6272
|
},
|
|
6190
|
-
maxWorkflows: args.capacity
|
|
6273
|
+
maxWorkflows: args.capacity,
|
|
6274
|
+
payloadLimitMb: args.payloadMemory
|
|
6191
6275
|
};
|
|
6192
6276
|
if (args.lightningPublicKey) {
|
|
6193
6277
|
logger.info(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openfn/ws-worker",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.1",
|
|
4
4
|
"description": "A Websocket Worker to connect Lightning to a Runtime Engine",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
"ws": "^8.14.1",
|
|
25
25
|
"@openfn/engine-multi": "1.2.1",
|
|
26
26
|
"@openfn/lexicon": "^1.0.2",
|
|
27
|
-
"@openfn/
|
|
28
|
-
"@openfn/
|
|
27
|
+
"@openfn/logger": "1.0.1",
|
|
28
|
+
"@openfn/runtime": "1.4.1"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@types/koa": "^2.13.5",
|