@balena/pinejs 16.2.0-build-joshbwlng-tasks-cd1aff5f4bbfacaad189b013578d5cbee51c3932-1 → 16.2.0-build-joshbwlng-tasks-a83c83b4c78803915d0cb6297cc8cc1862622d08-1
Sign up to get free protection for your applications and to get access to all the features.
- package/.husky/pre-commit +0 -2
- package/.versionbot/CHANGELOG.yml +28 -10
- package/CHANGELOG.md +11 -2
- package/out/tasks/worker.d.ts +3 -3
- package/out/tasks/worker.js +63 -63
- package/out/tasks/worker.js.map +1 -1
- package/package.json +5 -5
- package/src/tasks/worker.ts +91 -86
package/.husky/pre-commit
CHANGED
@@ -1,14 +1,6 @@
|
|
1
1
|
- commits:
|
2
|
-
- subject: Organize code
|
3
|
-
hash: cd1aff5f4bbfacaad189b013578d5cbee51c3932
|
4
|
-
body: ""
|
5
|
-
footer:
|
6
|
-
Change-type: patch
|
7
|
-
change-type: patch
|
8
|
-
author: Josh Bowling
|
9
|
-
nested: []
|
10
2
|
- subject: Add async tasks
|
11
|
-
hash:
|
3
|
+
hash: a83c83b4c78803915d0cb6297cc8cc1862622d08
|
12
4
|
body: ""
|
13
5
|
footer:
|
14
6
|
Change-type: minor
|
@@ -17,7 +9,33 @@
|
|
17
9
|
nested: []
|
18
10
|
version: 16.2.0
|
19
11
|
title: ""
|
20
|
-
date: 2024-04-
|
12
|
+
date: 2024-04-15T01:28:02.918Z
|
13
|
+
- commits:
|
14
|
+
- subject: Update dependency husky to v9
|
15
|
+
hash: 715d2dd993591d449bca7ece48f069ed6994c8c7
|
16
|
+
body: |
|
17
|
+
Update husky from 8.0.3 to 9.0.11
|
18
|
+
footer:
|
19
|
+
Change-type: patch
|
20
|
+
change-type: patch
|
21
|
+
author: Self-hosted Renovate Bot
|
22
|
+
nested: []
|
23
|
+
version: 16.1.2
|
24
|
+
title: ""
|
25
|
+
date: 2024-04-14T04:09:27.077Z
|
26
|
+
- commits:
|
27
|
+
- subject: Update dependency @balena/lint to v8
|
28
|
+
hash: 02ba2563aa4ac82ad90accc30534f744c00a0c16
|
29
|
+
body: |
|
30
|
+
Update @balena/lint from 7.3.0 to 8.0.0
|
31
|
+
footer:
|
32
|
+
Change-type: patch
|
33
|
+
change-type: patch
|
34
|
+
author: Self-hosted Renovate Bot
|
35
|
+
nested: []
|
36
|
+
version: 16.1.1
|
37
|
+
title: ""
|
38
|
+
date: 2024-04-11T22:45:27.398Z
|
21
39
|
- commits:
|
22
40
|
- subject: Add support for prettifying index constraint errors with their
|
23
41
|
description
|
package/CHANGELOG.md
CHANGED
@@ -5,11 +5,20 @@ 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-15)
|
9
9
|
|
10
|
-
* Organize code [Josh Bowling]
|
11
10
|
* Add async tasks [Josh Bowling]
|
12
11
|
|
12
|
+
# v16.1.2
|
13
|
+
## (2024-04-14)
|
14
|
+
|
15
|
+
* Update dependency husky to v9 [Self-hosted Renovate Bot]
|
16
|
+
|
17
|
+
# v16.1.1
|
18
|
+
## (2024-04-11)
|
19
|
+
|
20
|
+
* Update dependency @balena/lint to v8 [Self-hosted Renovate Bot]
|
21
|
+
|
13
22
|
# v16.1.0
|
14
23
|
## (2024-04-02)
|
15
24
|
|
package/out/tasks/worker.d.ts
CHANGED
@@ -8,9 +8,9 @@ export declare class Worker {
|
|
8
8
|
private executing;
|
9
9
|
constructor(client: PinejsClient);
|
10
10
|
private canExecute;
|
11
|
-
private poll;
|
12
|
-
start(): void;
|
13
11
|
private execute;
|
12
|
+
private finalize;
|
14
13
|
private getNextAttemptTime;
|
15
|
-
private
|
14
|
+
private poll;
|
15
|
+
start(): void;
|
16
16
|
}
|
package/out/tasks/worker.js
CHANGED
@@ -52,73 +52,18 @@ class Worker {
|
|
52
52
|
canExecute() {
|
53
53
|
return (this.executing < this.concurrency && Object.keys(this.handlers).length > 0);
|
54
54
|
}
|
55
|
-
poll() {
|
56
|
-
let executed = false;
|
57
|
-
const names = Object.keys(this.handlers);
|
58
|
-
const binds = names.map((_, index) => `$${index + 1}`).join(', ');
|
59
|
-
module_1.sbvrUtils.db
|
60
|
-
.transaction(async (tx) => {
|
61
|
-
if (!this.canExecute()) {
|
62
|
-
return;
|
63
|
-
}
|
64
|
-
const result = await tx.executeSql(`SELECT ${selectColumns}
|
65
|
-
FROM task AS t
|
66
|
-
WHERE
|
67
|
-
t."is executed by-handler" IN (${binds}) AND
|
68
|
-
t."status" = 'pending' AND
|
69
|
-
t."attempt count" <= t."attempt limit" AND
|
70
|
-
(
|
71
|
-
t."is scheduled to execute on-time" IS NULL OR
|
72
|
-
t."is scheduled to execute on-time" <= CURRENT_TIMESTAMP + INTERVAL '${Math.ceil(this.interval / 1000)} second'
|
73
|
-
)
|
74
|
-
ORDER BY
|
75
|
-
t."is scheduled to execute on-time" ASC,
|
76
|
-
t."priority" DESC,
|
77
|
-
t."id" ASC
|
78
|
-
LIMIT ${Math.max(this.concurrency - this.executing, 0)}
|
79
|
-
FOR UPDATE SKIP LOCKED`, names);
|
80
|
-
if (result.rows.length === 0) {
|
81
|
-
return;
|
82
|
-
}
|
83
|
-
await Promise.all(result.rows.map(async (row) => {
|
84
|
-
await this.execute(tx, row);
|
85
|
-
}));
|
86
|
-
executed = true;
|
87
|
-
})
|
88
|
-
.catch((err) => {
|
89
|
-
console.error('Failed polling for tasks:', err);
|
90
|
-
})
|
91
|
-
.finally(() => {
|
92
|
-
setTimeout(() => this.poll(), executed ? 0 : this.interval);
|
93
|
-
});
|
94
|
-
}
|
95
|
-
start() {
|
96
|
-
module_1.sbvrUtils.db.on?.('notification', async (msg) => {
|
97
|
-
if (this.canExecute()) {
|
98
|
-
await module_1.sbvrUtils.db.transaction(async (tx) => {
|
99
|
-
const result = await tx.executeSql(`SELECT ${selectColumns} FROM task AS t WHERE id = $1 FOR UPDATE SKIP LOCKED`, [msg.payload]);
|
100
|
-
if (result.rows.length > 0) {
|
101
|
-
await this.execute(tx, result.rows[0]);
|
102
|
-
}
|
103
|
-
});
|
104
|
-
}
|
105
|
-
}, {
|
106
|
-
channel: common_1.channel,
|
107
|
-
});
|
108
|
-
this.poll();
|
109
|
-
}
|
110
55
|
async execute(tx, task) {
|
111
56
|
this.executing++;
|
112
57
|
try {
|
113
58
|
const handler = this.handlers[task.is_executed_by__handler];
|
114
59
|
const startedOnTime = new Date();
|
115
60
|
if (handler == null) {
|
116
|
-
await this.
|
61
|
+
await this.finalize(tx, task, startedOnTime, 'failed', 'Matching task handler not found');
|
117
62
|
return;
|
118
63
|
}
|
119
64
|
if (handler.validate != null &&
|
120
65
|
!handler.validate(task.is_executed_with__parameter_set)) {
|
121
|
-
await this.
|
66
|
+
await this.finalize(tx, task, startedOnTime, 'failed', `Invalid parameter set: ${common_1.ajv.errorsText(handler.validate.errors)}`);
|
122
67
|
return;
|
123
68
|
}
|
124
69
|
const result = await handler.fn({
|
@@ -130,7 +75,7 @@ class Worker {
|
|
130
75
|
params: task.is_executed_with__parameter_set ?? {},
|
131
76
|
tx,
|
132
77
|
});
|
133
|
-
await this.
|
78
|
+
await this.finalize(tx, task, startedOnTime, result.status, result.error);
|
134
79
|
}
|
135
80
|
catch (err) {
|
136
81
|
console.error('Task execution failed:', err);
|
@@ -140,11 +85,7 @@ class Worker {
|
|
140
85
|
this.executing--;
|
141
86
|
}
|
142
87
|
}
|
143
|
-
|
144
|
-
const delay = Math.ceil(Math.exp(Math.min(10, attempt))) * 1000;
|
145
|
-
return new Date(Date.now() + delay);
|
146
|
-
}
|
147
|
-
async update(tx, task, startedOnTime, status, errorMessage) {
|
88
|
+
async finalize(tx, task, startedOnTime, status, errorMessage) {
|
148
89
|
const attemptCount = task.attempt_count + 1;
|
149
90
|
const body = {
|
150
91
|
started_on__time: startedOnTime,
|
@@ -186,6 +127,65 @@ class Worker {
|
|
186
127
|
});
|
187
128
|
}
|
188
129
|
}
|
130
|
+
getNextAttemptTime(attempt) {
|
131
|
+
const delay = Math.ceil(Math.exp(Math.min(10, attempt))) * 1000;
|
132
|
+
return new Date(Date.now() + delay);
|
133
|
+
}
|
134
|
+
poll() {
|
135
|
+
let executed = false;
|
136
|
+
const handlerNames = Object.keys(this.handlers);
|
137
|
+
const binds = handlerNames.map((_, index) => `$${index + 1}`).join(', ');
|
138
|
+
module_1.sbvrUtils.db
|
139
|
+
.transaction(async (tx) => {
|
140
|
+
if (!this.canExecute()) {
|
141
|
+
return;
|
142
|
+
}
|
143
|
+
const result = await tx.executeSql(`SELECT ${selectColumns}
|
144
|
+
FROM task AS t
|
145
|
+
WHERE
|
146
|
+
t."is executed by-handler" IN (${binds}) AND
|
147
|
+
t."status" = 'pending' AND
|
148
|
+
t."attempt count" <= t."attempt limit" AND
|
149
|
+
(
|
150
|
+
t."is scheduled to execute on-time" IS NULL OR
|
151
|
+
t."is scheduled to execute on-time" <= CURRENT_TIMESTAMP + INTERVAL '${Math.ceil(this.interval / 1000)} second'
|
152
|
+
)
|
153
|
+
ORDER BY
|
154
|
+
t."is scheduled to execute on-time" ASC,
|
155
|
+
t."priority" DESC,
|
156
|
+
t."id" ASC
|
157
|
+
LIMIT ${Math.max(this.concurrency - this.executing, 0)}
|
158
|
+
FOR UPDATE SKIP LOCKED`, handlerNames);
|
159
|
+
if (result.rows.length === 0) {
|
160
|
+
return;
|
161
|
+
}
|
162
|
+
await Promise.all(result.rows.map(async (row) => {
|
163
|
+
await this.execute(tx, row);
|
164
|
+
}));
|
165
|
+
executed = true;
|
166
|
+
})
|
167
|
+
.catch((err) => {
|
168
|
+
console.error('Failed polling for tasks:', err);
|
169
|
+
})
|
170
|
+
.finally(() => {
|
171
|
+
setTimeout(() => this.poll(), executed ? 0 : this.interval);
|
172
|
+
});
|
173
|
+
}
|
174
|
+
start() {
|
175
|
+
module_1.sbvrUtils.db.on?.('notification', async (msg) => {
|
176
|
+
if (this.canExecute()) {
|
177
|
+
await module_1.sbvrUtils.db.transaction(async (tx) => {
|
178
|
+
const result = await tx.executeSql(`SELECT ${selectColumns} FROM task AS t WHERE id = $1 FOR UPDATE SKIP LOCKED`, [msg.payload]);
|
179
|
+
if (result.rows.length > 0) {
|
180
|
+
await this.execute(tx, result.rows[0]);
|
181
|
+
}
|
182
|
+
});
|
183
|
+
}
|
184
|
+
}, {
|
185
|
+
channel: common_1.channel,
|
186
|
+
});
|
187
|
+
this.poll();
|
188
|
+
}
|
189
189
|
}
|
190
190
|
exports.Worker = Worker;
|
191
191
|
//# sourceMappingURL=worker.js.map
|
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;
|
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-a83c83b4c78803915d0cb6297cc8cc1862622d08-1",
|
4
4
|
"main": "out/server-glue/module",
|
5
5
|
"type": "commonjs",
|
6
6
|
"repository": "git@github.com:balena-io/pinejs.git",
|
@@ -12,7 +12,7 @@
|
|
12
12
|
},
|
13
13
|
"scripts": {
|
14
14
|
"prepublish": "require-npm4-to-publish",
|
15
|
-
"prepare": "node -e \"try {
|
15
|
+
"prepare": "node -e \"try { (await import('husky')).default() } catch (e) { if (e.code !== 'ERR_MODULE_NOT_FOUND') throw e }\" --input-type module && npm run build",
|
16
16
|
"build": "grunt build",
|
17
17
|
"webpack-browser": "grunt browser",
|
18
18
|
"webpack-module": "grunt module",
|
@@ -65,7 +65,7 @@
|
|
65
65
|
"typed-error": "^3.2.2"
|
66
66
|
},
|
67
67
|
"devDependencies": {
|
68
|
-
"@balena/lint": "^
|
68
|
+
"@balena/lint": "^8.0.0",
|
69
69
|
"@faker-js/faker": "^8.3.1",
|
70
70
|
"@types/busboy": "^1.5.3",
|
71
71
|
"@types/chai": "^4.3.11",
|
@@ -90,7 +90,7 @@
|
|
90
90
|
"grunt-text-replace": "^0.4.0",
|
91
91
|
"grunt-ts": "^6.0.0-beta.22",
|
92
92
|
"grunt-webpack": "^6.0.0",
|
93
|
-
"husky": "^
|
93
|
+
"husky": "^9.0.0",
|
94
94
|
"json-schema-to-ts": "^3.0.1",
|
95
95
|
"lint-staged": "^15.2.0",
|
96
96
|
"load-grunt-tasks": "^5.1.0",
|
@@ -147,6 +147,6 @@
|
|
147
147
|
"recursive": true
|
148
148
|
},
|
149
149
|
"versionist": {
|
150
|
-
"publishedAt": "2024-04-
|
150
|
+
"publishedAt": "2024-04-15T01:28:03.598Z"
|
151
151
|
}
|
152
152
|
}
|
package/src/tasks/worker.ts
CHANGED
@@ -37,93 +37,22 @@ export class Worker {
|
|
37
37
|
this.interval = tasksEnv.queueIntervalMS;
|
38
38
|
}
|
39
39
|
|
40
|
+
// Check if instance can execute more tasks
|
40
41
|
private canExecute(): boolean {
|
41
42
|
return (
|
42
43
|
this.executing < this.concurrency && Object.keys(this.handlers).length > 0
|
43
44
|
);
|
44
45
|
}
|
45
46
|
|
46
|
-
|
47
|
-
let executed = false;
|
48
|
-
const names = Object.keys(this.handlers);
|
49
|
-
const binds = names.map((_, index) => `$${index + 1}`).join(', ');
|
50
|
-
sbvrUtils.db
|
51
|
-
.transaction(async (tx) => {
|
52
|
-
if (!this.canExecute()) {
|
53
|
-
return;
|
54
|
-
}
|
55
|
-
|
56
|
-
const result = await tx.executeSql(
|
57
|
-
`SELECT ${selectColumns}
|
58
|
-
FROM task AS t
|
59
|
-
WHERE
|
60
|
-
t."is executed by-handler" IN (${binds}) AND
|
61
|
-
t."status" = 'pending' AND
|
62
|
-
t."attempt count" <= t."attempt limit" AND
|
63
|
-
(
|
64
|
-
t."is scheduled to execute on-time" IS NULL OR
|
65
|
-
t."is scheduled to execute on-time" <= CURRENT_TIMESTAMP + INTERVAL '${Math.ceil(this.interval / 1000)} second'
|
66
|
-
)
|
67
|
-
ORDER BY
|
68
|
-
t."is scheduled to execute on-time" ASC,
|
69
|
-
t."priority" DESC,
|
70
|
-
t."id" ASC
|
71
|
-
LIMIT ${Math.max(this.concurrency - this.executing, 0)}
|
72
|
-
FOR UPDATE SKIP LOCKED`,
|
73
|
-
names,
|
74
|
-
);
|
75
|
-
if (result.rows.length === 0) {
|
76
|
-
return;
|
77
|
-
}
|
78
|
-
|
79
|
-
// Tasks found, execute them in parallel
|
80
|
-
await Promise.all(
|
81
|
-
result.rows.map(async (row) => {
|
82
|
-
await this.execute(tx, row as PartialTask);
|
83
|
-
}),
|
84
|
-
);
|
85
|
-
executed = true;
|
86
|
-
})
|
87
|
-
.catch((err) => {
|
88
|
-
console.error('Failed polling for tasks:', err);
|
89
|
-
})
|
90
|
-
.finally(() => {
|
91
|
-
setTimeout(() => this.poll(), executed ? 0 : this.interval);
|
92
|
-
});
|
93
|
-
}
|
94
|
-
|
95
|
-
// Start listening and polling for tasks
|
96
|
-
public start() {
|
97
|
-
sbvrUtils.db.on?.(
|
98
|
-
'notification',
|
99
|
-
async (msg) => {
|
100
|
-
if (this.canExecute()) {
|
101
|
-
await sbvrUtils.db.transaction(async (tx) => {
|
102
|
-
const result = await tx.executeSql(
|
103
|
-
`SELECT ${selectColumns} FROM task AS t WHERE id = $1 FOR UPDATE SKIP LOCKED`,
|
104
|
-
[msg.payload],
|
105
|
-
);
|
106
|
-
if (result.rows.length > 0) {
|
107
|
-
await this.execute(tx, result.rows[0] as PartialTask);
|
108
|
-
}
|
109
|
-
});
|
110
|
-
}
|
111
|
-
},
|
112
|
-
{
|
113
|
-
channel,
|
114
|
-
},
|
115
|
-
);
|
116
|
-
this.poll();
|
117
|
-
}
|
118
|
-
|
47
|
+
// Execute a task
|
119
48
|
private async execute(tx: Db.Tx, task: PartialTask): Promise<void> {
|
120
49
|
this.executing++;
|
121
50
|
try {
|
122
|
-
// Get
|
51
|
+
// Get specified handler
|
123
52
|
const handler = this.handlers[task.is_executed_by__handler];
|
124
53
|
const startedOnTime = new Date();
|
125
54
|
if (handler == null) {
|
126
|
-
await this.
|
55
|
+
await this.finalize(
|
127
56
|
tx,
|
128
57
|
task,
|
129
58
|
startedOnTime,
|
@@ -140,7 +69,7 @@ export class Worker {
|
|
140
69
|
handler.validate != null &&
|
141
70
|
!handler.validate(task.is_executed_with__parameter_set)
|
142
71
|
) {
|
143
|
-
await this.
|
72
|
+
await this.finalize(
|
144
73
|
tx,
|
145
74
|
task,
|
146
75
|
startedOnTime,
|
@@ -150,7 +79,7 @@ export class Worker {
|
|
150
79
|
return;
|
151
80
|
}
|
152
81
|
|
153
|
-
// Execute
|
82
|
+
// Execute handler
|
154
83
|
const result = await handler.fn({
|
155
84
|
api: new PinejsClient({
|
156
85
|
passthrough: {
|
@@ -161,10 +90,10 @@ export class Worker {
|
|
161
90
|
tx,
|
162
91
|
});
|
163
92
|
|
164
|
-
// Update
|
165
|
-
await this.
|
93
|
+
// Update task with results
|
94
|
+
await this.finalize(tx, task, startedOnTime, result.status, result.error);
|
166
95
|
} catch (err) {
|
167
|
-
// This shouldn't
|
96
|
+
// This shouldn't happen, but if it does we want to log and kill the process
|
168
97
|
console.error('Task execution failed:', err);
|
169
98
|
process.exit(1);
|
170
99
|
} finally {
|
@@ -172,12 +101,8 @@ export class Worker {
|
|
172
101
|
}
|
173
102
|
}
|
174
103
|
|
175
|
-
|
176
|
-
|
177
|
-
return new Date(Date.now() + delay);
|
178
|
-
}
|
179
|
-
|
180
|
-
private async update(
|
104
|
+
// Update task and schedule next attempt if needed
|
105
|
+
private async finalize(
|
181
106
|
tx: Db.Tx,
|
182
107
|
task: PartialTask,
|
183
108
|
startedOnTime: Date,
|
@@ -238,4 +163,84 @@ export class Worker {
|
|
238
163
|
});
|
239
164
|
}
|
240
165
|
}
|
166
|
+
|
167
|
+
// Calculate next attempt time using exponential backoff
|
168
|
+
private getNextAttemptTime(attempt: number): Date | null {
|
169
|
+
const delay = Math.ceil(Math.exp(Math.min(10, attempt))) * 1000;
|
170
|
+
return new Date(Date.now() + delay);
|
171
|
+
}
|
172
|
+
|
173
|
+
// Poll for tasks to execute
|
174
|
+
private poll(): void {
|
175
|
+
let executed = false;
|
176
|
+
const handlerNames = Object.keys(this.handlers);
|
177
|
+
const binds = handlerNames.map((_, index) => `$${index + 1}`).join(', ');
|
178
|
+
sbvrUtils.db
|
179
|
+
.transaction(async (tx) => {
|
180
|
+
if (!this.canExecute()) {
|
181
|
+
return;
|
182
|
+
}
|
183
|
+
|
184
|
+
const result = await tx.executeSql(
|
185
|
+
`SELECT ${selectColumns}
|
186
|
+
FROM task AS t
|
187
|
+
WHERE
|
188
|
+
t."is executed by-handler" IN (${binds}) AND
|
189
|
+
t."status" = 'pending' AND
|
190
|
+
t."attempt count" <= t."attempt limit" AND
|
191
|
+
(
|
192
|
+
t."is scheduled to execute on-time" IS NULL OR
|
193
|
+
t."is scheduled to execute on-time" <= CURRENT_TIMESTAMP + INTERVAL '${Math.ceil(this.interval / 1000)} second'
|
194
|
+
)
|
195
|
+
ORDER BY
|
196
|
+
t."is scheduled to execute on-time" ASC,
|
197
|
+
t."priority" DESC,
|
198
|
+
t."id" ASC
|
199
|
+
LIMIT ${Math.max(this.concurrency - this.executing, 0)}
|
200
|
+
FOR UPDATE SKIP LOCKED`,
|
201
|
+
handlerNames,
|
202
|
+
);
|
203
|
+
if (result.rows.length === 0) {
|
204
|
+
return;
|
205
|
+
}
|
206
|
+
|
207
|
+
// Tasks found, execute them in parallel
|
208
|
+
await Promise.all(
|
209
|
+
result.rows.map(async (row) => {
|
210
|
+
await this.execute(tx, row as PartialTask);
|
211
|
+
}),
|
212
|
+
);
|
213
|
+
executed = true;
|
214
|
+
})
|
215
|
+
.catch((err) => {
|
216
|
+
console.error('Failed polling for tasks:', err);
|
217
|
+
})
|
218
|
+
.finally(() => {
|
219
|
+
setTimeout(() => this.poll(), executed ? 0 : this.interval);
|
220
|
+
});
|
221
|
+
}
|
222
|
+
|
223
|
+
// Start listening and polling for tasks
|
224
|
+
public start(): void {
|
225
|
+
sbvrUtils.db.on?.(
|
226
|
+
'notification',
|
227
|
+
async (msg) => {
|
228
|
+
if (this.canExecute()) {
|
229
|
+
await sbvrUtils.db.transaction(async (tx) => {
|
230
|
+
const result = await tx.executeSql(
|
231
|
+
`SELECT ${selectColumns} FROM task AS t WHERE id = $1 FOR UPDATE SKIP LOCKED`,
|
232
|
+
[msg.payload],
|
233
|
+
);
|
234
|
+
if (result.rows.length > 0) {
|
235
|
+
await this.execute(tx, result.rows[0] as PartialTask);
|
236
|
+
}
|
237
|
+
});
|
238
|
+
}
|
239
|
+
},
|
240
|
+
{
|
241
|
+
channel,
|
242
|
+
},
|
243
|
+
);
|
244
|
+
this.poll();
|
245
|
+
}
|
241
246
|
}
|