@artian-techs/adonis-orbit 0.2.0 → 0.2.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/build/src/recorder.d.ts
CHANGED
|
@@ -9,6 +9,12 @@ import type { OrbitConfig } from './types.js';
|
|
|
9
9
|
* The channels of the queue answer at once, thus the events are written apart.
|
|
10
10
|
* A sink that refuses an event must never stop the job that runs.
|
|
11
11
|
*
|
|
12
|
+
* The start and the end of a short job come in the same tick, and a sink
|
|
13
|
+
* answers each write apart. The end would land before the start, and the row
|
|
14
|
+
* would stay open. The events of one job thus go to one sink one after the
|
|
15
|
+
* other, in the order that the queue gave them. Two jobs do not wait for each
|
|
16
|
+
* other, and two sinks do not wait for each other.
|
|
17
|
+
*
|
|
12
18
|
* A configuration that names no sink keeps nothing.
|
|
13
19
|
*
|
|
14
20
|
* The recorder gives back a `flush`. It waits for the writes that are open,
|
package/build/src/recorder.js
CHANGED
|
@@ -46,6 +46,12 @@ function keep(rule, job, payload) {
|
|
|
46
46
|
* The channels of the queue answer at once, thus the events are written apart.
|
|
47
47
|
* A sink that refuses an event must never stop the job that runs.
|
|
48
48
|
*
|
|
49
|
+
* The start and the end of a short job come in the same tick, and a sink
|
|
50
|
+
* answers each write apart. The end would land before the start, and the row
|
|
51
|
+
* would stay open. The events of one job thus go to one sink one after the
|
|
52
|
+
* other, in the order that the queue gave them. Two jobs do not wait for each
|
|
53
|
+
* other, and two sinks do not wait for each other.
|
|
54
|
+
*
|
|
49
55
|
* A configuration that names no sink keeps nothing.
|
|
50
56
|
*
|
|
51
57
|
* The recorder gives back a `flush`. It waits for the writes that are open,
|
|
@@ -56,13 +62,30 @@ export function recordJobRuns(config) {
|
|
|
56
62
|
const rule = config.payload ?? 'full';
|
|
57
63
|
/** The writes that are started, and that no sink finished yet. */
|
|
58
64
|
const pending = new Set();
|
|
65
|
+
/** The last write of each job that a sink did not finish yet, by sink. */
|
|
66
|
+
const lanes = new Map(sinks.map((sink) => [sink, new Map()]));
|
|
59
67
|
function write(events) {
|
|
68
|
+
const ids = [...new Set(events.map((event) => event.job.id))];
|
|
60
69
|
for (const sink of sinks) {
|
|
61
|
-
const
|
|
70
|
+
const lane = lanes.get(sink);
|
|
71
|
+
const before = Promise.all(ids.map((id) => lane.get(id)));
|
|
72
|
+
const done = before
|
|
73
|
+
.then(() => sink.write(events))
|
|
74
|
+
.catch((error) => {
|
|
62
75
|
logger.error({ err: error }, 'The trace of a job could not be written');
|
|
63
76
|
});
|
|
77
|
+
for (const id of ids) {
|
|
78
|
+
lane.set(id, done);
|
|
79
|
+
}
|
|
64
80
|
pending.add(done);
|
|
65
|
-
void done.finally(() =>
|
|
81
|
+
void done.finally(() => {
|
|
82
|
+
pending.delete(done);
|
|
83
|
+
for (const id of ids) {
|
|
84
|
+
if (lane.get(id) === done) {
|
|
85
|
+
lane.delete(id);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
});
|
|
66
89
|
}
|
|
67
90
|
}
|
|
68
91
|
/**
|
|
@@ -2,13 +2,18 @@ import type { JobEvent, Sink } from '../types.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* Keep the trace of the jobs in the table of the application.
|
|
4
4
|
*
|
|
5
|
-
* The
|
|
5
|
+
* The three writes are written in SQL because they need `on conflict`, which
|
|
6
6
|
* Lucid does not carry. The model would read the row before writing it, and
|
|
7
7
|
* two processes that write together would then undo each other.
|
|
8
8
|
*
|
|
9
|
-
* The
|
|
10
|
-
*
|
|
11
|
-
* the
|
|
9
|
+
* The three events of one job write the same row, and each one can land
|
|
10
|
+
* first: the dispatch and the worker run in two processes, and a worker that
|
|
11
|
+
* stops before its writes end leaves the row to the reconciler. The first
|
|
12
|
+
* event writes the row, and the next ones complete it.
|
|
13
|
+
*
|
|
14
|
+
* A row that is closed stays closed. A start that lands after the end fills
|
|
15
|
+
* what the end did not know, and changes nothing else. A `lost` that lands
|
|
16
|
+
* after the end changes nothing: the row has its answer.
|
|
12
17
|
*/
|
|
13
18
|
export default class DatabaseSink implements Sink {
|
|
14
19
|
#private;
|
|
@@ -7,16 +7,25 @@ const STATUS = {
|
|
|
7
7
|
retrying: JobRunStatus.RETRYING,
|
|
8
8
|
lost: JobRunStatus.LOST,
|
|
9
9
|
};
|
|
10
|
+
/** The statuses of a row that waits for an answer. */
|
|
11
|
+
const OPEN = [JobRunStatus.PENDING, JobRunStatus.RUNNING, JobRunStatus.RETRYING];
|
|
12
|
+
/** `job_runs.status in (?, ?, ?)`, with one mark for each open status. */
|
|
13
|
+
const IS_OPEN = `${JobRun.table}.status in (${OPEN.map(() => '?').join(', ')})`;
|
|
10
14
|
/**
|
|
11
15
|
* Keep the trace of the jobs in the table of the application.
|
|
12
16
|
*
|
|
13
|
-
* The
|
|
17
|
+
* The three writes are written in SQL because they need `on conflict`, which
|
|
14
18
|
* Lucid does not carry. The model would read the row before writing it, and
|
|
15
19
|
* two processes that write together would then undo each other.
|
|
16
20
|
*
|
|
17
|
-
* The
|
|
18
|
-
*
|
|
19
|
-
* the
|
|
21
|
+
* The three events of one job write the same row, and each one can land
|
|
22
|
+
* first: the dispatch and the worker run in two processes, and a worker that
|
|
23
|
+
* stops before its writes end leaves the row to the reconciler. The first
|
|
24
|
+
* event writes the row, and the next ones complete it.
|
|
25
|
+
*
|
|
26
|
+
* A row that is closed stays closed. A start that lands after the end fills
|
|
27
|
+
* what the end did not know, and changes nothing else. A `lost` that lands
|
|
28
|
+
* after the end changes nothing: the row has its answer.
|
|
20
29
|
*/
|
|
21
30
|
export default class DatabaseSink {
|
|
22
31
|
async write(events) {
|
|
@@ -52,15 +61,24 @@ export default class DatabaseSink {
|
|
|
52
61
|
values ${rows}
|
|
53
62
|
on conflict (job_id) do nothing`, values);
|
|
54
63
|
}
|
|
55
|
-
/**
|
|
64
|
+
/**
|
|
65
|
+
* Write the row of the job that a worker holds, or complete it.
|
|
66
|
+
*
|
|
67
|
+
* An open row takes the start: the status, the count of the tries and the
|
|
68
|
+
* time of this try. A closed row keeps its status, and takes the time of the
|
|
69
|
+
* start only while it has none.
|
|
70
|
+
*/
|
|
56
71
|
#take({ job, at }) {
|
|
57
72
|
return db.rawQuery(`insert into ${JobRun.table}
|
|
58
73
|
(job_id, name, queue, status, payload, attempts, started_at, created_at, updated_at)
|
|
59
74
|
values (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
60
75
|
on conflict (job_id) do update set
|
|
61
|
-
status = excluded.status,
|
|
62
|
-
attempts = excluded.attempts,
|
|
63
|
-
started_at =
|
|
76
|
+
status = case when ${IS_OPEN} then excluded.status else ${JobRun.table}.status end,
|
|
77
|
+
attempts = greatest(${JobRun.table}.attempts, excluded.attempts),
|
|
78
|
+
started_at = case when ${IS_OPEN}
|
|
79
|
+
then excluded.started_at
|
|
80
|
+
else coalesce(${JobRun.table}.started_at, excluded.started_at)
|
|
81
|
+
end,
|
|
64
82
|
updated_at = excluded.updated_at`, [
|
|
65
83
|
job.id,
|
|
66
84
|
job.name,
|
|
@@ -71,19 +89,43 @@ export default class DatabaseSink {
|
|
|
71
89
|
at,
|
|
72
90
|
at,
|
|
73
91
|
at,
|
|
92
|
+
...OPEN,
|
|
93
|
+
...OPEN,
|
|
74
94
|
]);
|
|
75
95
|
}
|
|
76
|
-
/**
|
|
96
|
+
/**
|
|
97
|
+
* Say how the try of a job ended.
|
|
98
|
+
*
|
|
99
|
+
* The end can land before the start, thus it writes the row when there is
|
|
100
|
+
* none. A `lost` says that nobody answered, thus it writes nothing on a row
|
|
101
|
+
* that has an answer.
|
|
102
|
+
*/
|
|
77
103
|
#close({ job, at, outcome, duration, error }) {
|
|
78
104
|
const ended = outcome !== 'retrying';
|
|
79
|
-
return JobRun.
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
105
|
+
return db.rawQuery(`insert into ${JobRun.table}
|
|
106
|
+
(job_id, name, queue, status, payload, attempts, duration, error, finished_at, created_at, updated_at)
|
|
107
|
+
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
108
|
+
on conflict (job_id) do update set
|
|
109
|
+
status = excluded.status,
|
|
110
|
+
attempts = greatest(${JobRun.table}.attempts, excluded.attempts),
|
|
111
|
+
duration = excluded.duration,
|
|
112
|
+
error = excluded.error,
|
|
113
|
+
finished_at = excluded.finished_at,
|
|
114
|
+
updated_at = excluded.updated_at
|
|
115
|
+
where excluded.status <> ? or ${IS_OPEN}`, [
|
|
116
|
+
job.id,
|
|
117
|
+
job.name,
|
|
118
|
+
job.queue,
|
|
119
|
+
STATUS[outcome],
|
|
120
|
+
JSON.stringify(job.payload),
|
|
121
|
+
job.attempts,
|
|
83
122
|
duration,
|
|
84
123
|
error,
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
124
|
+
ended ? at : null,
|
|
125
|
+
at,
|
|
126
|
+
at,
|
|
127
|
+
JobRunStatus.LOST,
|
|
128
|
+
...OPEN,
|
|
129
|
+
]);
|
|
88
130
|
}
|
|
89
131
|
}
|