@balena/pinejs 16.2.0-build-joshbwlng-tasks-56c035f7e142216b5cf56fc7d861ce4b51157a41-1 → 16.2.0-build-joshbwlng-tasks-009b08b1f157611c22f2425c25f905d5a59aaabe-1
Sign up to get free protection for your applications and to get access to all the features.
- package/.pinejs-cache.json +1 -1
- package/.versionbot/CHANGELOG.yml +10 -2
- package/CHANGELOG.md +2 -1
- package/out/tasks/index.d.ts +1 -1
- package/out/tasks/index.js +51 -14
- package/out/tasks/index.js.map +1 -1
- package/out/tasks/model.sbvr +1 -1
- package/out/tasks/types.d.ts +1 -1
- package/out/tasks/types.js +2 -2
- package/out/tasks/types.js.map +1 -1
- package/out/tasks/worker.js +44 -32
- package/out/tasks/worker.js.map +1 -1
- package/package.json +2 -2
- package/src/tasks/index.ts +89 -17
- package/src/tasks/model.sbvr +1 -1
- package/src/tasks/types.ts +2 -2
- package/src/tasks/worker.ts +45 -34
@@ -1,6 +1,14 @@
|
|
1
1
|
- commits:
|
2
|
+
- subject: Cleanup
|
3
|
+
hash: 009b08b1f157611c22f2425c25f905d5a59aaabe
|
4
|
+
body: ""
|
5
|
+
footer:
|
6
|
+
Change-type: patch
|
7
|
+
change-type: patch
|
8
|
+
author: Josh Bowling
|
9
|
+
nested: []
|
2
10
|
- subject: Add async tasks
|
3
|
-
hash:
|
11
|
+
hash: 1297814070f10e99d7bd475c28c365cc03081096
|
4
12
|
body: ""
|
5
13
|
footer:
|
6
14
|
Change-type: minor
|
@@ -9,7 +17,7 @@
|
|
9
17
|
nested: []
|
10
18
|
version: 16.2.0
|
11
19
|
title: ""
|
12
|
-
date: 2024-04-
|
20
|
+
date: 2024-04-30T05:37:15.944Z
|
13
21
|
- commits:
|
14
22
|
- subject: Update @balena/odata-to-abstract-sql to 6.2.7
|
15
23
|
hash: 76b2f842af0b49ef2956db23a22758fc0d1e50a0
|
package/CHANGELOG.md
CHANGED
@@ -5,8 +5,9 @@ automatically by Versionist. DO NOT EDIT THIS FILE MANUALLY!
|
|
5
5
|
This project adheres to [Semantic Versioning](http://semver.org/).
|
6
6
|
|
7
7
|
# v16.2.0
|
8
|
-
## (2024-04-
|
8
|
+
## (2024-04-30)
|
9
9
|
|
10
|
+
* Cleanup [Josh Bowling]
|
10
11
|
* Add async tasks [Josh Bowling]
|
11
12
|
|
12
13
|
# v16.1.3
|
package/out/tasks/index.d.ts
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import type { Schema } from 'ajv';
|
2
2
|
import type * as Db from '../database-layer/db';
|
3
|
-
import
|
3
|
+
import * as sbvrUtils from '../sbvr-api/sbvr-utils';
|
4
4
|
import type { TaskHandler } from './types';
|
5
5
|
export * from './types';
|
6
6
|
export declare const config: {
|
package/out/tasks/index.js
CHANGED
@@ -32,6 +32,7 @@ const env_1 = require("../config-loader/env");
|
|
32
32
|
const errors_1 = require("../sbvr-api/errors");
|
33
33
|
const hooks_1 = require("../sbvr-api/hooks");
|
34
34
|
const sbvr_utils_1 = require("../sbvr-api/sbvr-utils");
|
35
|
+
const sbvrUtils = __importStar(require("../sbvr-api/sbvr-utils"));
|
35
36
|
const common_1 = require("./common");
|
36
37
|
const worker_1 = require("./worker");
|
37
38
|
__exportStar(require("./types"), exports);
|
@@ -48,20 +49,54 @@ exports.config = {
|
|
48
49
|
};
|
49
50
|
async function createTrigger(tx) {
|
50
51
|
await tx.executeSql(`
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
52
|
+
CREATE OR REPLACE FUNCTION notify_task_insert()
|
53
|
+
RETURNS TRIGGER AS $$
|
54
|
+
BEGIN
|
55
|
+
PERFORM pg_notify('${common_1.channel}', NEW.id::text);
|
56
|
+
RETURN NEW;
|
57
|
+
END;
|
58
|
+
$$ LANGUAGE plpgsql;
|
59
|
+
`);
|
59
60
|
await tx.executeSql(`
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
61
|
+
CREATE OR REPLACE TRIGGER task_insert_trigger
|
62
|
+
AFTER INSERT ON task
|
63
|
+
FOR EACH ROW WHEN (NEW.status = 'queued' AND NEW."is scheduled to execute on-time" IS NULL)
|
64
|
+
EXECUTE FUNCTION notify_task_insert();
|
65
|
+
`);
|
66
|
+
}
|
67
|
+
async function createIndexes(tx) {
|
68
|
+
await tx.executeSql(`
|
69
|
+
CREATE INDEX IF NOT EXISTS idx_task_poll ON task USING btree (
|
70
|
+
"is executed by-handler",
|
71
|
+
"is scheduled to execute on-time" ASC,
|
72
|
+
"priority" DESC,
|
73
|
+
"id" ASC
|
74
|
+
) WHERE status = 'queued';
|
75
|
+
`);
|
76
|
+
await tx.executeSql(`
|
77
|
+
CREATE INDEX IF NOT EXISTS idx_task_queued ON task (
|
78
|
+
"id",
|
79
|
+
"is created by-actor"
|
80
|
+
) WHERE status = 'queued';
|
81
|
+
`);
|
82
|
+
}
|
83
|
+
async function adjustPriority(actor, priority) {
|
84
|
+
const result = await sbvrUtils.db.executeSql(`SELECT "is created by-actor", COUNT("id") AS task_count
|
85
|
+
FROM (
|
86
|
+
SELECT "id", "is created by-actor"
|
87
|
+
FROM task
|
88
|
+
WHERE "status" = 'queued'
|
89
|
+
ORDER BY "id" DESC
|
90
|
+
LIMIT 100
|
91
|
+
) AS recent_tasks
|
92
|
+
GROUP BY "is created by-actor"
|
93
|
+
ORDER BY task_count DESC;
|
94
|
+
`);
|
95
|
+
if (result.rows.some((row) => parseInt(row.task_count, 10) >= 49 &&
|
96
|
+
row['is created by-actor'] !== actor)) {
|
97
|
+
return priority + 1;
|
98
|
+
}
|
99
|
+
return priority;
|
65
100
|
}
|
66
101
|
let worker = null;
|
67
102
|
async function setup(db, tx) {
|
@@ -69,6 +104,7 @@ async function setup(db, tx) {
|
|
69
104
|
return;
|
70
105
|
}
|
71
106
|
await createTrigger(tx);
|
107
|
+
await createIndexes(tx);
|
72
108
|
const client = new sbvr_utils_1.PinejsClient({
|
73
109
|
apiPrefix: `/${common_1.apiRoot}/`,
|
74
110
|
});
|
@@ -80,10 +116,11 @@ async function setup(db, tx) {
|
|
80
116
|
if (request.values.is_created_by__actor == null) {
|
81
117
|
throw new errors_1.BadRequestError('Creating tasks with missing actor on req is not allowed');
|
82
118
|
}
|
83
|
-
request.values.status = '
|
119
|
+
request.values.status = 'queued';
|
84
120
|
request.values.attempt_count = 0;
|
85
121
|
request.values.priority ??= 1;
|
86
122
|
request.values.attempt_limit ??= 1;
|
123
|
+
request.values.priority = await adjustPriority(request.values.is_created_by__actor, request.values.priority);
|
87
124
|
if (request.values.is_scheduled_with__cron_expression != null &&
|
88
125
|
request.values.is_scheduled_to_execute_on__time == null) {
|
89
126
|
try {
|
package/out/tasks/index.js.map
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tasks/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,wDAA0C;AAC1C,8CAAyD;AAEzD,+CAAqD;AACrD,6CAAgD;AAChD,uDAAsD;
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tasks/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,wDAA0C;AAC1C,8CAAyD;AAEzD,+CAAqD;AACrD,6CAAgD;AAChD,uDAAsD;AACtD,kEAAoD;AACpD,qCAAiD;AAEjD,qCAAkC;AAElC,0CAAwB;AAGxB,MAAM,SAAS,GAAW,OAAO,CAAC,cAAc,CAAC,CAAC;AAErC,QAAA,MAAM,GAAG;IACrB,MAAM,EAAE;QACP;YACC,SAAS,EAAE,gBAAO;YAClB,OAAO,EAAP,gBAAO;YACP,SAAS;YACT,gBAAgB,EAAE,OAAO;SACzB;KAC8B;CAChC,CAAC;AAIF,KAAK,UAAU,aAAa,CAAC,EAAS;IACrC,MAAM,EAAE,CAAC,UAAU,CAAC;;;;wBAIG,gBAAO;;;;EAI7B,CAAC,CAAC;IAGH,MAAM,EAAE,CAAC,UAAU,CAAC;;;;;EAKnB,CAAC,CAAC;AACJ,CAAC;AAGD,KAAK,UAAU,aAAa,CAAC,EAAS;IAErC,MAAM,EAAE,CAAC,UAAU,CAAC;;;;;;;EAOnB,CAAC,CAAC;IAGH,MAAM,EAAE,CAAC,UAAU,CAAC;;;;;EAKnB,CAAC,CAAC;AACJ,CAAC;AAOD,KAAK,UAAU,cAAc,CAC5B,KAAa,EACb,QAAgB;IAEhB,MAAM,MAAM,GAAG,MAAO,SAAS,CAAC,EAAE,CAAC,UAAU,CAC5C;;;;;;;;;;EAUA,CAOE,CAAC;IAGJ,IACC,MAAM,CAAC,IAAI,CAAC,IAAI,CACf,CAAC,GAAG,EAAE,EAAE,CACP,QAAQ,CAAC,GAAG,CAAC,UAAU,EAAE,EAAE,CAAC,IAAI,EAAE;QAClC,GAAG,CAAC,qBAAqB,CAAC,KAAK,KAAK,CACrC,EACA,CAAC;QACF,OAAO,QAAQ,GAAG,CAAC,CAAC;IACrB,CAAC;IACD,OAAO,QAAQ,CAAC;AACjB,CAAC;AAED,IAAI,MAAM,GAAkB,IAAI,CAAC;AAC1B,KAAK,UAAU,KAAK,CAAC,EAAe,EAAE,EAAS;IAErD,IAAI,EAAE,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;QAC9B,OAAO;IACR,CAAC;IAGD,MAAM,aAAa,CAAC,EAAE,CAAC,CAAC;IAGxB,MAAM,aAAa,CAAC,EAAE,CAAC,CAAC;IAExB,MAAM,MAAM,GAAG,IAAI,yBAAY,CAAC;QAC/B,SAAS,EAAE,IAAI,gBAAO,GAAG;KACzB,CAAC,CAAC;IACH,MAAM,GAAG,IAAI,eAAM,CAAC,MAAM,CAAC,CAAC;IAG5B,IAAA,mBAAW,EAAC,MAAM,EAAE,gBAAO,EAAE,MAAM,EAAE;QACpC,SAAS,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE;YAErC,OAAO,CAAC,MAAM,CAAC,oBAAoB;gBAClC,GAAG,CAAC,IAAI,EAAE,KAAK,IAAI,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC;YACtC,IAAI,OAAO,CAAC,MAAM,CAAC,oBAAoB,IAAI,IAAI,EAAE,CAAC;gBACjD,MAAM,IAAI,wBAAe,CACxB,yDAAyD,CACzD,CAAC;YACH,CAAC;YAGD,OAAO,CAAC,MAAM,CAAC,MAAM,GAAG,QAAQ,CAAC;YACjC,OAAO,CAAC,MAAM,CAAC,aAAa,GAAG,CAAC,CAAC;YACjC,OAAO,CAAC,MAAM,CAAC,QAAQ,KAAK,CAAC,CAAC;YAC9B,OAAO,CAAC,MAAM,CAAC,aAAa,KAAK,CAAC,CAAC;YAGnC,OAAO,CAAC,MAAM,CAAC,QAAQ,GAAG,MAAM,cAAc,CAC7C,OAAO,CAAC,MAAM,CAAC,oBAAoB,EACnC,OAAO,CAAC,MAAM,CAAC,QAAQ,CACvB,CAAC;YAGF,IACC,OAAO,CAAC,MAAM,CAAC,kCAAkC,IAAI,IAAI;gBACzD,OAAO,CAAC,MAAM,CAAC,gCAAgC,IAAI,IAAI,EACtD,CAAC;gBACF,IAAI,CAAC;oBACJ,OAAO,CAAC,MAAM,CAAC,gCAAgC,GAAG,UAAU;yBAC1D,eAAe,CAAC,OAAO,CAAC,MAAM,CAAC,kCAAkC,CAAC;yBAClE,IAAI,EAAE;yBACN,MAAM,EAAE;yBACR,WAAW,EAAE,CAAC;gBACjB,CAAC;gBAAC,OAAO,CAAC,EAAE,CAAC;oBACZ,MAAM,IAAI,wBAAe,CACxB,4BAA4B,OAAO,CAAC,MAAM,CAAC,kCAAkC,EAAE,CAC/E,CAAC;gBACH,CAAC;YACF,CAAC;YAGD,IAAI,OAAO,CAAC,MAAM,CAAC,gCAAgC,IAAI,IAAI,EAAE,CAAC;gBAC7D,MAAM,GAAG,GAAG,IAAI,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,OAAO,EAAE,GAAG,WAAQ,CAAC,eAAe,CAAC,CAAC;gBACtE,MAAM,SAAS,GAAG,IAAI,IAAI,CACzB,OAAO,CAAC,MAAM,CAAC,gCAAgC,CAC/C,CAAC;gBACF,IAAI,SAAS,GAAG,GAAG,EAAE,CAAC;oBACrB,MAAM,IAAI,wBAAe,CACxB,kDAAkD,WAAQ,CAAC,eAAe,6BAA6B,CACvG,CAAC;gBACH,CAAC;YACF,CAAC;YAGD,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,uBAAuB,CAAC;YAC3D,IAAI,WAAW,IAAI,IAAI,EAAE,CAAC;gBACzB,MAAM,IAAI,wBAAe,CAAC,wCAAwC,CAAC,CAAC;YACrE,CAAC;YACD,MAAM,OAAO,GAAG,MAAM,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;YAC9C,IAAI,OAAO,IAAI,IAAI,EAAE,CAAC;gBACrB,MAAM,IAAI,wBAAe,CACxB,8BAA8B,WAAW,cAAc,CACvD,CAAC;YACH,CAAC;YAGD,IAAI,OAAO,CAAC,QAAQ,IAAI,IAAI,EAAE,CAAC;gBAC9B,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,+BAA+B,CAAC,EAAE,CAAC;oBACvE,MAAM,IAAI,wBAAe,CACxB,0BAA0B,YAAG,CAAC,UAAU,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CACnE,CAAC;gBACH,CAAC;YACF,CAAC;QACF,CAAC;KACD,CAAC,CAAC;IACH,MAAM,CAAC,KAAK,EAAE,CAAC;AAChB,CAAC;AA/FD,sBA+FC;AAGD,SAAgB,cAAc,CAC7B,IAAY,EACZ,EAAqB,EACrB,MAAe;IAEf,IAAI,MAAM,IAAI,IAAI,EAAE,CAAC;QACpB,OAAO;IACR,CAAC;IAED,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;QACnC,MAAM,IAAI,KAAK,CAAC,2BAA2B,IAAI,sBAAsB,CAAC,CAAC;IACxE,CAAC;IACD,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG;QACvB,IAAI;QACJ,EAAE;QACF,QAAQ,EAAE,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC,YAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS;KAC1D,CAAC;AACH,CAAC;AAjBD,wCAiBC"}
|
package/out/tasks/model.sbvr
CHANGED
@@ -45,7 +45,7 @@ Fact type: task is scheduled to execute on time
|
|
45
45
|
Necessity: each task is scheduled to execute on at most one time
|
46
46
|
Fact type: task has status
|
47
47
|
Necessity: each task has exactly one status
|
48
|
-
Definition: "
|
48
|
+
Definition: "queued" or "cancelled" or "succeeded" or "failed"
|
49
49
|
Fact type: task started on time
|
50
50
|
Necessity: each task started on at most one time
|
51
51
|
Fact type: task ended on time
|
package/out/tasks/types.d.ts
CHANGED
@@ -2,7 +2,7 @@ import type { ValidateFunction } from 'ajv';
|
|
2
2
|
import type { AnyObject } from 'pinejs-client-core';
|
3
3
|
import type * as Db from '../database-layer/db';
|
4
4
|
import type { PinejsClient } from '../sbvr-api/sbvr-utils';
|
5
|
-
export declare const taskStatuses: readonly ["
|
5
|
+
export declare const taskStatuses: readonly ["queued", "cancelled", "succeeded", "failed"];
|
6
6
|
export type TaskStatus = (typeof taskStatuses)[number];
|
7
7
|
export interface Task {
|
8
8
|
id: number;
|
package/out/tasks/types.js
CHANGED
package/out/tasks/types.js.map
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/tasks/types.ts"],"names":[],"mappings":";;;AAKa,QAAA,YAAY,GAAG;IAC3B,
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/tasks/types.ts"],"names":[],"mappings":";;;AAKa,QAAA,YAAY,GAAG;IAC3B,QAAQ;IACR,WAAW;IACX,WAAW;IACX,QAAQ;CACC,CAAC"}
|
package/out/tasks/worker.js
CHANGED
@@ -25,6 +25,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
25
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
26
26
|
exports.Worker = void 0;
|
27
27
|
const env_1 = require("../config-loader/env");
|
28
|
+
const db_1 = require("../database-layer/db");
|
28
29
|
const permissions = __importStar(require("../sbvr-api/permissions"));
|
29
30
|
const sbvr_utils_1 = require("../sbvr-api/sbvr-utils");
|
30
31
|
const module_1 = require("../server-glue/module");
|
@@ -66,23 +67,34 @@ class Worker {
|
|
66
67
|
await this.finalize(tx, task, startedOnTime, 'failed', `Invalid parameter set: ${common_1.ajv.errorsText(handler.validate.errors)}`);
|
67
68
|
return;
|
68
69
|
}
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
70
|
+
let status = 'queued';
|
71
|
+
let error;
|
72
|
+
try {
|
73
|
+
await module_1.sbvrUtils.db.transaction(async (handlerTx) => {
|
74
|
+
const results = await handler.fn({
|
75
|
+
api: new sbvr_utils_1.PinejsClient({
|
76
|
+
passthrough: {
|
77
|
+
tx: handlerTx,
|
78
|
+
},
|
79
|
+
}),
|
80
|
+
params: task.is_executed_with__parameter_set ?? {},
|
81
|
+
tx: handlerTx,
|
82
|
+
});
|
83
|
+
status = results.status;
|
84
|
+
error = results.error;
|
85
|
+
if (results.status !== 'succeeded' && !handlerTx.isClosed()) {
|
86
|
+
await handlerTx.rollback();
|
87
|
+
}
|
88
|
+
});
|
89
|
+
}
|
90
|
+
catch (err) {
|
91
|
+
if (!(err instanceof db_1.TransactionClosedError)) {
|
92
|
+
throw err;
|
93
|
+
}
|
81
94
|
}
|
82
|
-
|
83
|
-
await
|
95
|
+
finally {
|
96
|
+
await this.finalize(tx, task, startedOnTime, status, error);
|
84
97
|
}
|
85
|
-
await this.finalize(tx, task, startedOnTime, result.status, result.error);
|
86
98
|
}
|
87
99
|
catch (err) {
|
88
100
|
console.error('Task execution failed:', err);
|
@@ -102,7 +114,7 @@ class Worker {
|
|
102
114
|
...(errorMessage != null && { error_message: errorMessage }),
|
103
115
|
};
|
104
116
|
if (status === 'failed' && attemptCount < task.attempt_limit) {
|
105
|
-
body.status = '
|
117
|
+
body.status = 'queued';
|
106
118
|
body.is_scheduled_to_execute_on__time =
|
107
119
|
this.getNextAttemptTime(attemptCount);
|
108
120
|
}
|
@@ -115,7 +127,7 @@ class Worker {
|
|
115
127
|
id: task.id,
|
116
128
|
body,
|
117
129
|
});
|
118
|
-
if (['failed', '
|
130
|
+
if (['failed', 'succeeded'].includes(body.status) &&
|
119
131
|
task.is_scheduled_with__cron_expression != null) {
|
120
132
|
await this.client.post({
|
121
133
|
resource: 'task',
|
@@ -148,21 +160,21 @@ class Worker {
|
|
148
160
|
return;
|
149
161
|
}
|
150
162
|
const result = await tx.executeSql(`SELECT ${selectColumns}
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
163
|
+
FROM task AS t
|
164
|
+
WHERE
|
165
|
+
t."is executed by-handler" IN (${binds}) AND
|
166
|
+
t."status" = 'queued' AND
|
167
|
+
t."attempt count" <= t."attempt limit" AND
|
168
|
+
(
|
169
|
+
t."is scheduled to execute on-time" IS NULL OR
|
170
|
+
t."is scheduled to execute on-time" <= CURRENT_TIMESTAMP + INTERVAL '${Math.ceil(this.interval / 1000)} second'
|
171
|
+
)
|
172
|
+
ORDER BY
|
173
|
+
t."is scheduled to execute on-time" ASC,
|
174
|
+
t."priority" DESC,
|
175
|
+
t."id" ASC
|
176
|
+
LIMIT ${Math.max(this.concurrency - this.executing, 0)}
|
177
|
+
FOR UPDATE SKIP LOCKED`, handlerNames);
|
166
178
|
if (result.rows.length === 0) {
|
167
179
|
return;
|
168
180
|
}
|
package/out/tasks/worker.js.map
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"worker.js","sourceRoot":"","sources":["../../src/tasks/worker.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AACA,8CAAyD;AAEzD,qEAAuD;AACvD,uDAAsD;AACtD,kDAAkD;AAClD,qCAAwC;AAIxC,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,CAAC;IACpC,EAAE,EAAE,IAAI;IACR,wBAAwB,EAAE,yBAAyB;IACnD,gCAAgC,EAAE,iCAAiC;IACnE,mCAAmC,EAAE,oCAAoC;IACzE,eAAe,EAAE,eAAe;IAChC,eAAe,EAAE,eAAe;IAChC,QAAQ,EAAE,UAAU;IACpB,qBAAqB,EAAE,sBAAsB;CAC7C,CAAC;KACA,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,MAAM,GAAG,SAAS,KAAK,GAAG,CAAC;KACjD,IAAI,CAAC,IAAI,CAAC,CAAC;AAKb,MAAa,MAAM;IAOlB,YAAY,MAAoB;QANzB,aAAQ,GAAgC,EAAE,CAAC;QAI1C,cAAS,GAAG,CAAC,CAAC;QAGrB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,WAAW,GAAG,WAAQ,CAAC,gBAAgB,CAAC;QAC7C,IAAI,CAAC,QAAQ,GAAG,WAAQ,CAAC,eAAe,CAAC;IAC1C,CAAC;IAGO,UAAU;QACjB,OAAO,CACN,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,WAAW,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,GAAG,CAAC,CAC1E,CAAC;IACH,CAAC;IAGO,KAAK,CAAC,OAAO,CAAC,EAAS,EAAE,IAAiB;QACjD,IAAI,CAAC,SAAS,EAAE,CAAC;QACjB,IAAI,CAAC;YAEJ,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;YAC5D,MAAM,aAAa,GAAG,IAAI,IAAI,EAAE,CAAC;YACjC,IAAI,OAAO,IAAI,IAAI,EAAE,CAAC;gBACrB,MAAM,IAAI,CAAC,QAAQ,CAClB,EAAE,EACF,IAAI,EACJ,aAAa,EACb,QAAQ,EACR,iCAAiC,CACjC,CAAC;gBACF,OAAO;YACR,CAAC;YAKD,IACC,OAAO,CAAC,QAAQ,IAAI,IAAI;gBACxB,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,+BAA+B,CAAC,EACtD,CAAC;gBACF,MAAM,IAAI,CAAC,QAAQ,CAClB,EAAE,EACF,IAAI,EACJ,aAAa,EACb,QAAQ,EACR,0BAA0B,YAAG,CAAC,UAAU,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CACnE,CAAC;gBACF,OAAO;YACR,CAAC;YAGD,MAAM,
|
1
|
+
{"version":3,"file":"worker.js","sourceRoot":"","sources":["../../src/tasks/worker.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@balena/pinejs",
|
3
|
-
"version": "16.2.0-build-joshbwlng-tasks-
|
3
|
+
"version": "16.2.0-build-joshbwlng-tasks-009b08b1f157611c22f2425c25f905d5a59aaabe-1",
|
4
4
|
"main": "out/server-glue/module",
|
5
5
|
"type": "commonjs",
|
6
6
|
"repository": "git@github.com:balena-io/pinejs.git",
|
@@ -147,6 +147,6 @@
|
|
147
147
|
"recursive": true
|
148
148
|
},
|
149
149
|
"versionist": {
|
150
|
-
"publishedAt": "2024-04-
|
150
|
+
"publishedAt": "2024-04-30T05:37:16.724Z"
|
151
151
|
}
|
152
152
|
}
|
package/src/tasks/index.ts
CHANGED
@@ -5,7 +5,7 @@ import type * as Db from '../database-layer/db';
|
|
5
5
|
import { BadRequestError } from '../sbvr-api/errors';
|
6
6
|
import { addPureHook } from '../sbvr-api/hooks';
|
7
7
|
import { PinejsClient } from '../sbvr-api/sbvr-utils';
|
8
|
-
import
|
8
|
+
import * as sbvrUtils from '../sbvr-api/sbvr-utils';
|
9
9
|
import { ajv, apiRoot, channel } from './common';
|
10
10
|
import type { TaskHandler } from './types';
|
11
11
|
import { Worker } from './worker';
|
@@ -30,22 +30,85 @@ export const config = {
|
|
30
30
|
// Only poll or execute on triggers if worker is not already at max concurrency
|
31
31
|
async function createTrigger(tx: Db.Tx): Promise<void> {
|
32
32
|
await tx.executeSql(`
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
// Only trigger if task is
|
33
|
+
CREATE OR REPLACE FUNCTION notify_task_insert()
|
34
|
+
RETURNS TRIGGER AS $$
|
35
|
+
BEGIN
|
36
|
+
PERFORM pg_notify('${channel}', NEW.id::text);
|
37
|
+
RETURN NEW;
|
38
|
+
END;
|
39
|
+
$$ LANGUAGE plpgsql;
|
40
|
+
`);
|
41
|
+
|
42
|
+
// Only trigger if task is queued and not scheduled
|
43
43
|
await tx.executeSql(`
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
44
|
+
CREATE OR REPLACE TRIGGER task_insert_trigger
|
45
|
+
AFTER INSERT ON task
|
46
|
+
FOR EACH ROW WHEN (NEW.status = 'queued' AND NEW."is scheduled to execute on-time" IS NULL)
|
47
|
+
EXECUTE FUNCTION notify_task_insert();
|
48
|
+
`);
|
49
|
+
}
|
50
|
+
|
51
|
+
// Create indexes
|
52
|
+
async function createIndexes(tx: Db.Tx): Promise<void> {
|
53
|
+
// Partial index for polling
|
54
|
+
await tx.executeSql(`
|
55
|
+
CREATE INDEX IF NOT EXISTS idx_task_poll ON task USING btree (
|
56
|
+
"is executed by-handler",
|
57
|
+
"is scheduled to execute on-time" ASC,
|
58
|
+
"priority" DESC,
|
59
|
+
"id" ASC
|
60
|
+
) WHERE status = 'queued';
|
61
|
+
`);
|
62
|
+
|
63
|
+
// Partial index used to adjust priority based on actor
|
64
|
+
await tx.executeSql(`
|
65
|
+
CREATE INDEX IF NOT EXISTS idx_task_queued ON task (
|
66
|
+
"id",
|
67
|
+
"is created by-actor"
|
68
|
+
) WHERE status = 'queued';
|
69
|
+
`);
|
70
|
+
}
|
71
|
+
|
72
|
+
// Check if a task should be given priority
|
73
|
+
// This is used to balance the load across actors
|
74
|
+
// by giving priority to actors with less tasks in the queue
|
75
|
+
// when another actor is crossing a percentage threshold of queued tasks
|
76
|
+
// This is to prevent a single actor from hogging the queue
|
77
|
+
async function adjustPriority(
|
78
|
+
actor: number,
|
79
|
+
priority: number,
|
80
|
+
): Promise<number> {
|
81
|
+
const result = await (sbvrUtils.db.executeSql(
|
82
|
+
`SELECT "is created by-actor", COUNT("id") AS task_count
|
83
|
+
FROM (
|
84
|
+
SELECT "id", "is created by-actor"
|
85
|
+
FROM task
|
86
|
+
WHERE "status" = 'queued'
|
87
|
+
ORDER BY "id" DESC
|
88
|
+
LIMIT 100
|
89
|
+
) AS recent_tasks
|
90
|
+
GROUP BY "is created by-actor"
|
91
|
+
ORDER BY task_count DESC;
|
92
|
+
`,
|
93
|
+
) as Promise<{
|
94
|
+
rowsAffected: number;
|
95
|
+
rows: Array<{
|
96
|
+
'is created by-actor': number;
|
97
|
+
task_count: string;
|
98
|
+
}>;
|
99
|
+
}>);
|
100
|
+
|
101
|
+
// Increase the priority of this task if another actor is filling up the queue
|
102
|
+
if (
|
103
|
+
result.rows.some(
|
104
|
+
(row) =>
|
105
|
+
parseInt(row.task_count, 10) >= 49 &&
|
106
|
+
row['is created by-actor'] !== actor,
|
107
|
+
)
|
108
|
+
) {
|
109
|
+
return priority + 1;
|
110
|
+
}
|
111
|
+
return priority;
|
49
112
|
}
|
50
113
|
|
51
114
|
let worker: Worker | null = null;
|
@@ -58,6 +121,9 @@ export async function setup(db: Db.Database, tx: Db.Tx): Promise<void> {
|
|
58
121
|
// Create trigger function if it doesn't exist
|
59
122
|
await createTrigger(tx);
|
60
123
|
|
124
|
+
// Create indexes if they don't exist
|
125
|
+
await createIndexes(tx);
|
126
|
+
|
61
127
|
const client = new PinejsClient({
|
62
128
|
apiPrefix: `/${apiRoot}/`,
|
63
129
|
});
|
@@ -76,11 +142,17 @@ export async function setup(db: Db.Database, tx: Db.Tx): Promise<void> {
|
|
76
142
|
}
|
77
143
|
|
78
144
|
// Set defaults
|
79
|
-
request.values.status = '
|
145
|
+
request.values.status = 'queued';
|
80
146
|
request.values.attempt_count = 0;
|
81
147
|
request.values.priority ??= 1;
|
82
148
|
request.values.attempt_limit ??= 1;
|
83
149
|
|
150
|
+
// Possibly adjust priority based on actor
|
151
|
+
request.values.priority = await adjustPriority(
|
152
|
+
request.values.is_created_by__actor,
|
153
|
+
request.values.priority,
|
154
|
+
);
|
155
|
+
|
84
156
|
// Set scheduled start time using cron expression if provided
|
85
157
|
if (
|
86
158
|
request.values.is_scheduled_with__cron_expression != null &&
|
package/src/tasks/model.sbvr
CHANGED
@@ -45,7 +45,7 @@ Fact type: task is scheduled to execute on time
|
|
45
45
|
Necessity: each task is scheduled to execute on at most one time
|
46
46
|
Fact type: task has status
|
47
47
|
Necessity: each task has exactly one status
|
48
|
-
Definition: "
|
48
|
+
Definition: "queued" or "cancelled" or "succeeded" or "failed"
|
49
49
|
Fact type: task started on time
|
50
50
|
Necessity: each task started on at most one time
|
51
51
|
Fact type: task ended on time
|
package/src/tasks/types.ts
CHANGED
@@ -4,9 +4,9 @@ import type * as Db from '../database-layer/db';
|
|
4
4
|
import type { PinejsClient } from '../sbvr-api/sbvr-utils';
|
5
5
|
|
6
6
|
export const taskStatuses = [
|
7
|
-
'
|
7
|
+
'queued',
|
8
8
|
'cancelled',
|
9
|
-
'
|
9
|
+
'succeeded',
|
10
10
|
'failed',
|
11
11
|
] as const;
|
12
12
|
export type TaskStatus = (typeof taskStatuses)[number];
|
package/src/tasks/worker.ts
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
import type { AnyObject } from 'pinejs-client-core';
|
2
2
|
import { tasks as tasksEnv } from '../config-loader/env';
|
3
3
|
import type * as Db from '../database-layer/db';
|
4
|
+
import { TransactionClosedError } from '../database-layer/db';
|
4
5
|
import * as permissions from '../sbvr-api/permissions';
|
5
6
|
import { PinejsClient } from '../sbvr-api/sbvr-utils';
|
6
7
|
import { sbvrUtils } from '../server-glue/module';
|
@@ -80,24 +81,34 @@ export class Worker {
|
|
80
81
|
}
|
81
82
|
|
82
83
|
// Execute handler
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
84
|
+
let status: TaskStatus = 'queued';
|
85
|
+
let error: string | undefined;
|
86
|
+
try {
|
87
|
+
await sbvrUtils.db.transaction(async (handlerTx) => {
|
88
|
+
const results = await handler.fn({
|
89
|
+
api: new PinejsClient({
|
90
|
+
passthrough: {
|
91
|
+
tx: handlerTx,
|
92
|
+
},
|
93
|
+
}),
|
94
|
+
params: task.is_executed_with__parameter_set ?? {},
|
95
|
+
tx: handlerTx,
|
96
|
+
});
|
97
|
+
status = results.status;
|
98
|
+
error = results.error;
|
99
|
+
if (results.status !== 'succeeded' && !handlerTx.isClosed()) {
|
100
|
+
await handlerTx.rollback();
|
101
|
+
}
|
102
|
+
});
|
103
|
+
} catch (err) {
|
104
|
+
// Ignore closed/rollback errors
|
105
|
+
if (!(err instanceof TransactionClosedError)) {
|
106
|
+
throw err;
|
107
|
+
}
|
108
|
+
} finally {
|
109
|
+
// Update task with results
|
110
|
+
await this.finalize(tx, task, startedOnTime, status, error);
|
97
111
|
}
|
98
|
-
|
99
|
-
// Update task with results
|
100
|
-
await this.finalize(tx, task, startedOnTime, result.status, result.error);
|
101
112
|
} catch (err) {
|
102
113
|
// This shouldn't happen, but if it does we want to log and kill the process
|
103
114
|
console.error('Task execution failed:', err);
|
@@ -127,7 +138,7 @@ export class Worker {
|
|
127
138
|
// Re-enqueue if the task failed but has retries left, remember that
|
128
139
|
// attemptCount includes the initial attempt while attempt_limit does not
|
129
140
|
if (status === 'failed' && attemptCount < task.attempt_limit) {
|
130
|
-
body.status = '
|
141
|
+
body.status = 'queued';
|
131
142
|
|
132
143
|
// Schedule next attempt using exponential backoff
|
133
144
|
body.is_scheduled_to_execute_on__time =
|
@@ -148,7 +159,7 @@ export class Worker {
|
|
148
159
|
// Create new task with same configuration if previous
|
149
160
|
// iteration completed and has a cron expression
|
150
161
|
if (
|
151
|
-
['failed', '
|
162
|
+
['failed', 'succeeded'].includes(body.status) &&
|
152
163
|
task.is_scheduled_with__cron_expression != null
|
153
164
|
) {
|
154
165
|
await this.client.post({
|
@@ -189,21 +200,21 @@ export class Worker {
|
|
189
200
|
|
190
201
|
const result = await tx.executeSql(
|
191
202
|
`SELECT ${selectColumns}
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
203
|
+
FROM task AS t
|
204
|
+
WHERE
|
205
|
+
t."is executed by-handler" IN (${binds}) AND
|
206
|
+
t."status" = 'queued' AND
|
207
|
+
t."attempt count" <= t."attempt limit" AND
|
208
|
+
(
|
209
|
+
t."is scheduled to execute on-time" IS NULL OR
|
210
|
+
t."is scheduled to execute on-time" <= CURRENT_TIMESTAMP + INTERVAL '${Math.ceil(this.interval / 1000)} second'
|
211
|
+
)
|
212
|
+
ORDER BY
|
213
|
+
t."is scheduled to execute on-time" ASC,
|
214
|
+
t."priority" DESC,
|
215
|
+
t."id" ASC
|
216
|
+
LIMIT ${Math.max(this.concurrency - this.executing, 0)}
|
217
|
+
FOR UPDATE SKIP LOCKED`,
|
207
218
|
handlerNames,
|
208
219
|
);
|
209
220
|
if (result.rows.length === 0) {
|