queasy 0.2.0 → 0.3.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/.github/workflows/check.yml +3 -0
- package/.github/workflows/publish.yml +3 -0
- package/CLAUDE.md +5 -4
- package/Readme.md +9 -4
- package/biome.json +5 -1
- package/dist/client.d.ts +33 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +199 -0
- package/dist/client.js.map +1 -0
- package/dist/constants.d.ts +10 -0
- package/dist/constants.d.ts.map +1 -0
- package/{src → dist}/constants.js +2 -10
- package/dist/constants.js.map +1 -0
- package/dist/errors.d.ts +7 -0
- package/dist/errors.d.ts.map +1 -0
- package/{src → dist}/errors.js +1 -13
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/manager.d.ts +19 -0
- package/dist/manager.d.ts.map +1 -0
- package/dist/manager.js +67 -0
- package/dist/manager.js.map +1 -0
- package/dist/pool.d.ts +29 -0
- package/dist/pool.d.ts.map +1 -0
- package/{src → dist}/pool.js +23 -82
- package/dist/pool.js.map +1 -0
- package/dist/queasy.lua +390 -0
- package/dist/queue.d.ts +22 -0
- package/dist/queue.d.ts.map +1 -0
- package/dist/queue.js +81 -0
- package/dist/queue.js.map +1 -0
- package/dist/types.d.ts +92 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +4 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +24 -0
- package/dist/utils.js.map +1 -0
- package/dist/worker.d.ts +2 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +42 -0
- package/dist/worker.js.map +1 -0
- package/docker-compose.yml +0 -2
- package/fuzztest/Readme.md +185 -0
- package/fuzztest/fuzz.ts +356 -0
- package/fuzztest/handlers/cascade-a.ts +90 -0
- package/fuzztest/handlers/cascade-b.ts +71 -0
- package/fuzztest/handlers/fail-handler.ts +47 -0
- package/fuzztest/handlers/periodic.ts +89 -0
- package/fuzztest/process.ts +100 -0
- package/fuzztest/shared/chaos.ts +29 -0
- package/fuzztest/shared/stream.ts +40 -0
- package/package.json +8 -7
- package/plans/redis-options.md +279 -0
- package/src/client.ts +246 -0
- package/src/constants.ts +33 -0
- package/src/errors.ts +13 -0
- package/src/index.ts +2 -0
- package/src/manager.ts +78 -0
- package/src/pool.ts +129 -0
- package/src/queasy.lua +2 -3
- package/src/queue.ts +108 -0
- package/src/types.ts +16 -0
- package/src/{utils.js → utils.ts} +3 -20
- package/src/{worker.js → worker.ts} +5 -12
- package/test/{client.test.js → client.test.ts} +6 -7
- package/test/{errors.test.js → errors.test.ts} +1 -1
- package/test/fixtures/always-fail-handler.ts +5 -0
- package/test/fixtures/data-logger-handler.ts +11 -0
- package/test/fixtures/failure-handler.ts +6 -0
- package/test/fixtures/permanent-error-handler.ts +6 -0
- package/test/fixtures/slow-handler.ts +6 -0
- package/test/fixtures/success-handler.js +0 -5
- package/test/fixtures/success-handler.ts +6 -0
- package/test/fixtures/with-failure-handler.ts +5 -0
- package/test/{guards.test.js → guards.test.ts} +21 -34
- package/test/{manager.test.js → manager.test.ts} +26 -34
- package/test/{pool.test.js → pool.test.ts} +14 -16
- package/test/{queue.test.js → queue.test.ts} +21 -21
- package/test/{redis-functions.test.js → redis-functions.test.ts} +14 -20
- package/test/{utils.test.js → utils.test.ts} +1 -1
- package/tsconfig.json +20 -0
- package/jsconfig.json +0 -17
- package/src/client.js +0 -258
- package/src/index.js +0 -2
- package/src/manager.js +0 -94
- package/src/queue.js +0 -154
- package/test/fixtures/always-fail-handler.js +0 -8
- package/test/fixtures/data-logger-handler.js +0 -19
- package/test/fixtures/failure-handler.js +0 -9
- package/test/fixtures/permanent-error-handler.js +0 -10
- package/test/fixtures/slow-handler.js +0 -9
- package/test/fixtures/with-failure-handler.js +0 -8
- /package/test/fixtures/{no-handle-handler.js → no-handle-handler.ts} +0 -0
package/{src → dist}/pool.js
RENAMED
|
@@ -1,42 +1,23 @@
|
|
|
1
1
|
import { availableParallelism } from 'node:os';
|
|
2
2
|
import { Worker } from 'node:worker_threads';
|
|
3
|
-
import { WORKER_CAPACITY } from
|
|
4
|
-
import { generateId } from
|
|
5
|
-
|
|
6
|
-
/** @typedef {import('./types').DoneMessage} DoneMessage */
|
|
7
|
-
/** @typedef {import('./types').Job} Job */
|
|
8
|
-
|
|
9
|
-
/** @typedef {{
|
|
10
|
-
* worker: Worker,
|
|
11
|
-
* capacity: number,
|
|
12
|
-
* id: string,
|
|
13
|
-
* jobCount: number,
|
|
14
|
-
* stalledJobs: Set<string>
|
|
15
|
-
* }} WorkerEntry */
|
|
16
|
-
|
|
17
|
-
/** @typedef {{
|
|
18
|
-
* resolve: (value: DoneMessage) => void,
|
|
19
|
-
* reject: (reason: DoneMessage) => void,
|
|
20
|
-
* size: number,
|
|
21
|
-
* timer: NodeJS.Timeout
|
|
22
|
-
* }} JobEntry */
|
|
23
|
-
|
|
3
|
+
import { WORKER_CAPACITY } from "./constants.js";
|
|
4
|
+
import { generateId } from "./utils.js";
|
|
24
5
|
export class Pool {
|
|
25
|
-
|
|
6
|
+
workers;
|
|
7
|
+
activeJobs;
|
|
8
|
+
capacity;
|
|
26
9
|
constructor(targetCount) {
|
|
27
|
-
/** @type {Set<WorkerEntry>} */
|
|
28
10
|
this.workers = new Set();
|
|
29
|
-
/** @type {Map<string, JobEntry>} */
|
|
30
11
|
this.activeJobs = new Map();
|
|
31
|
-
|
|
32
12
|
this.capacity = 0;
|
|
33
|
-
|
|
34
13
|
const count = targetCount ?? availableParallelism();
|
|
35
|
-
for (let i = 0; i < count; i++)
|
|
14
|
+
for (let i = 0; i < count; i++)
|
|
15
|
+
this.createWorker();
|
|
36
16
|
}
|
|
37
|
-
|
|
38
17
|
createWorker() {
|
|
39
|
-
|
|
18
|
+
// Copy file extension from current file so it works in src and dist.
|
|
19
|
+
const workerFilename = `./worker${import.meta.url.slice(-3)}`;
|
|
20
|
+
const worker = new Worker(new URL(workerFilename, import.meta.url));
|
|
40
21
|
const entry = {
|
|
41
22
|
worker,
|
|
42
23
|
capacity: WORKER_CAPACITY,
|
|
@@ -48,11 +29,6 @@ export class Pool {
|
|
|
48
29
|
worker.on('message', (message) => this.handleWorkerMessage(entry, message));
|
|
49
30
|
this.workers.add(entry);
|
|
50
31
|
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* @param {WorkerEntry} workerEntry
|
|
54
|
-
* @param {DoneMessage} message
|
|
55
|
-
*/
|
|
56
32
|
handleWorkerMessage(workerEntry, message) {
|
|
57
33
|
const { jobId, error } = message;
|
|
58
34
|
const jobEntry = this.activeJobs.get(jobId);
|
|
@@ -64,44 +40,24 @@ export class Pool {
|
|
|
64
40
|
workerEntry.capacity += jobEntry.size;
|
|
65
41
|
this.capacity += jobEntry.size;
|
|
66
42
|
workerEntry.jobCount -= 1;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (workerEntry.stalledJobs.has(jobId)) workerEntry.stalledJobs.delete(jobId);
|
|
70
|
-
|
|
43
|
+
if (workerEntry.stalledJobs.has(jobId))
|
|
44
|
+
workerEntry.stalledJobs.delete(jobId);
|
|
71
45
|
this.activeJobs.delete(jobId);
|
|
72
46
|
jobEntry[error ? 'reject' : 'resolve'](message);
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (!this.workers.has(workerEntry)) this.terminateIfEmpty(workerEntry);
|
|
47
|
+
if (!this.workers.has(workerEntry))
|
|
48
|
+
this.terminateIfEmpty(workerEntry);
|
|
76
49
|
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
*
|
|
80
|
-
* @param {WorkerEntry} workerEntry
|
|
81
|
-
* @param {string} jobId
|
|
82
|
-
*/
|
|
83
50
|
handleTimeout(workerEntry, jobId) {
|
|
84
51
|
workerEntry.stalledJobs.add(jobId);
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if (this.workers.delete(workerEntry)) this.createWorker();
|
|
52
|
+
if (this.workers.delete(workerEntry))
|
|
53
|
+
this.createWorker();
|
|
88
54
|
this.capacity -= workerEntry.capacity;
|
|
89
|
-
|
|
90
|
-
// If this is the last job in this worker, terminate it.
|
|
91
55
|
this.terminateIfEmpty(workerEntry);
|
|
92
56
|
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Stops adding new jobs to a worker if it has stalled jobs.
|
|
96
|
-
* Terminates workers if all remaining jobs are stalled.
|
|
97
|
-
* @param {WorkerEntry} workerEntry
|
|
98
|
-
* @returns
|
|
99
|
-
*/
|
|
100
57
|
async terminateIfEmpty({ stalledJobs, jobCount, worker }) {
|
|
101
|
-
|
|
102
|
-
|
|
58
|
+
if (jobCount > stalledJobs.size)
|
|
59
|
+
return;
|
|
103
60
|
await worker.terminate();
|
|
104
|
-
|
|
105
61
|
for (const jobId of stalledJobs) {
|
|
106
62
|
const jobEntry = this.activeJobs.get(jobId);
|
|
107
63
|
this.activeJobs.delete(jobId);
|
|
@@ -112,26 +68,15 @@ export class Pool {
|
|
|
112
68
|
});
|
|
113
69
|
}
|
|
114
70
|
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Processes a job to the most free worker
|
|
118
|
-
* @param {string} handlerPath
|
|
119
|
-
* @param {Job} job
|
|
120
|
-
* @param {number} size
|
|
121
|
-
* @param {number} timeout - Maximum time in ms
|
|
122
|
-
* @returns {Promise<DoneMessage>}
|
|
123
|
-
*/
|
|
124
71
|
process(handlerPath, job, size, timeout) {
|
|
125
|
-
// Find worker with most capacity
|
|
126
72
|
let workerEntry = null;
|
|
127
73
|
for (const entry of this.workers) {
|
|
128
|
-
if (!workerEntry || entry.capacity > workerEntry.capacity)
|
|
74
|
+
if (!workerEntry || entry.capacity > workerEntry.capacity)
|
|
75
|
+
workerEntry = entry;
|
|
129
76
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
77
|
+
if (!workerEntry)
|
|
78
|
+
throw Error("Can't process job without workers");
|
|
133
79
|
const timer = setTimeout(() => this.handleTimeout(workerEntry, job.id), timeout);
|
|
134
|
-
|
|
135
80
|
return new Promise((resolve, reject) => {
|
|
136
81
|
this.activeJobs.set(job.id, { resolve, reject, size, timer });
|
|
137
82
|
workerEntry.capacity -= size;
|
|
@@ -140,10 +85,6 @@ export class Pool {
|
|
|
140
85
|
workerEntry.worker.postMessage({ op: 'exec', handlerPath, job });
|
|
141
86
|
});
|
|
142
87
|
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Terminates all workers
|
|
146
|
-
*/
|
|
147
88
|
async close() {
|
|
148
89
|
await Promise.all([...this.workers].map(async ({ worker }) => worker.terminate()));
|
|
149
90
|
for (const [jobId, { reject, timer }] of this.activeJobs.entries()) {
|
|
@@ -154,8 +95,8 @@ export class Pool {
|
|
|
154
95
|
error: { name: 'StallError', message: 'Pool is closing', kind: 'stall' },
|
|
155
96
|
});
|
|
156
97
|
}
|
|
157
|
-
|
|
158
98
|
this.workers = new Set();
|
|
159
99
|
this.activeJobs.clear();
|
|
160
100
|
}
|
|
161
101
|
}
|
|
102
|
+
//# sourceMappingURL=pool.js.map
|
package/dist/pool.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pool.js","sourceRoot":"","sources":["../src/pool.ts"],"names":[],"mappings":"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"}
|
package/dist/queasy.lua
ADDED
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
#!lua name=queasy
|
|
2
|
+
|
|
3
|
+
--[[
|
|
4
|
+
Queasy: Redis Lua functions for job queue management
|
|
5
|
+
|
|
6
|
+
Key structure:
|
|
7
|
+
- {queue} - sorted set of waiting job IDs (score = run_at or -run_at if blocked)
|
|
8
|
+
- {queue}:expiry - sorted set of client heartbeat expiries (member = client_id, score = expiry)
|
|
9
|
+
- {queue}:checkouts:{client_id} - set of job IDs checked out by this client
|
|
10
|
+
- {queue}:waiting_job:{id} - hash with job data for waiting jobs
|
|
11
|
+
- {queue}:active_job:{id} - hash with job data for active jobs
|
|
12
|
+
]]
|
|
13
|
+
|
|
14
|
+
-- Key helpers
|
|
15
|
+
local function get_waiting_job_key(queue_key, id)
|
|
16
|
+
return queue_key .. ':waiting_job:' .. id
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
local function get_active_job_key(queue_key, id)
|
|
20
|
+
return queue_key .. ':active_job:' .. id
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
local function get_expiry_key(queue_key)
|
|
24
|
+
return queue_key .. ':expiry'
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
local function get_checkouts_key(queue_key, client_id)
|
|
28
|
+
return queue_key .. ':checkouts:' .. client_id
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
-- Helper: Add job to waiting queue with appropriate flags
|
|
32
|
+
local function add_to_waiting(queue_key, id, score, update_run_at)
|
|
33
|
+
local flag = nil
|
|
34
|
+
|
|
35
|
+
if update_run_at == 'false' then
|
|
36
|
+
flag = 'NX'
|
|
37
|
+
elseif update_run_at == 'if_later' then
|
|
38
|
+
flag = score >= 0 and 'GT' or 'LT'
|
|
39
|
+
elseif update_run_at == 'if_earlier' then
|
|
40
|
+
flag = score >= 0 and 'LT' or 'GT'
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
if flag then
|
|
44
|
+
redis.call('ZADD', queue_key, flag, score, id)
|
|
45
|
+
else
|
|
46
|
+
redis.call('ZADD', queue_key, score, id)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
-- Helper: Upsert job to waiting queue
|
|
51
|
+
local function dispatch(
|
|
52
|
+
queue_key,
|
|
53
|
+
id, run_at, data,
|
|
54
|
+
update_data, update_run_at, reset_counts
|
|
55
|
+
)
|
|
56
|
+
local waiting_job_key = get_waiting_job_key(queue_key, id)
|
|
57
|
+
local active_job_key = get_active_job_key(queue_key, id)
|
|
58
|
+
|
|
59
|
+
-- id is always stored so that HGETALL (e.g. during dequeue) includes it
|
|
60
|
+
redis.call('HSET', waiting_job_key, 'id', id)
|
|
61
|
+
|
|
62
|
+
-- If reset_counts is true, reset counters to 0, otherwise initialize them
|
|
63
|
+
redis.call(reset_counts and 'HSET' or 'HSETNX', waiting_job_key, 'retry_count', '0')
|
|
64
|
+
redis.call(reset_counts and 'HSET' or 'HSETNX', waiting_job_key, 'stall_count', '0')
|
|
65
|
+
|
|
66
|
+
-- Handle data
|
|
67
|
+
redis.call(update_data and 'HSET' or 'HSETNX', waiting_job_key, 'data', data)
|
|
68
|
+
|
|
69
|
+
-- Check if there's an active job with this ID
|
|
70
|
+
local is_blocked = redis.call('EXISTS', active_job_key) == 1
|
|
71
|
+
local score = is_blocked and -tonumber(run_at) or tonumber(run_at)
|
|
72
|
+
|
|
73
|
+
if is_blocked then
|
|
74
|
+
-- save these flags in case they need to be applied later
|
|
75
|
+
redis.call('HSET', waiting_job_key,
|
|
76
|
+
'reset_counts', tostring(reset_counts),
|
|
77
|
+
'update_data', tostring(update_data),
|
|
78
|
+
'update_run_at', update_run_at)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
-- Add to waiting queue
|
|
82
|
+
add_to_waiting(queue_key, id, score, update_run_at)
|
|
83
|
+
|
|
84
|
+
return { ok = 'OK' }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
-- Helper: Move job back to waiting for retry
|
|
88
|
+
local function do_retry(queue_key, id, retry_at)
|
|
89
|
+
local waiting_job_key = get_waiting_job_key(queue_key, id)
|
|
90
|
+
local active_job_key = get_active_job_key(queue_key, id)
|
|
91
|
+
|
|
92
|
+
local existing_score = redis.call('ZSCORE', queue_key, id)
|
|
93
|
+
|
|
94
|
+
if existing_score then
|
|
95
|
+
local run_at = -existing_score.double
|
|
96
|
+
local job = redis.call('HGETALL', waiting_job_key)['map']
|
|
97
|
+
|
|
98
|
+
redis.call('RENAME', active_job_key, waiting_job_key)
|
|
99
|
+
redis.call('ZADD', queue_key, retry_at, id)
|
|
100
|
+
|
|
101
|
+
if next(job) then
|
|
102
|
+
dispatch(
|
|
103
|
+
queue_key,
|
|
104
|
+
id, run_at, job.data,
|
|
105
|
+
job.update_data == 'true', job.update_run_at, job.reset_counts == 'true'
|
|
106
|
+
)
|
|
107
|
+
end
|
|
108
|
+
else
|
|
109
|
+
redis.call('RENAME', active_job_key, waiting_job_key)
|
|
110
|
+
redis.call('ZADD', queue_key, retry_at, id)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
return { ok = 'OK' }
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
-- Helper: Clear active job and unblock waiting job
|
|
117
|
+
local function finish(queue_key, id, client_id, now)
|
|
118
|
+
local waiting_job_key = get_waiting_job_key(queue_key, id)
|
|
119
|
+
local active_job_key = get_active_job_key(queue_key, id)
|
|
120
|
+
local checkouts_key = get_checkouts_key(queue_key, client_id)
|
|
121
|
+
|
|
122
|
+
redis.call('SREM', checkouts_key, id)
|
|
123
|
+
redis.call('DEL', active_job_key)
|
|
124
|
+
|
|
125
|
+
local score = redis.call('ZSCORE', queue_key, id)
|
|
126
|
+
|
|
127
|
+
if score then
|
|
128
|
+
score = tonumber(score.double)
|
|
129
|
+
if score < 0 then
|
|
130
|
+
score = -score
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
local update_run_at = redis.call('HGET', waiting_job_key, 'update_run_at') or 'true'
|
|
134
|
+
add_to_waiting(queue_key, id, score, update_run_at)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
return { ok = 'OK' }
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
-- Helper: Handle permanent failure
|
|
141
|
+
-- Creates a fail job and finishes the original job
|
|
142
|
+
local function fail(queue_key, fail_queue_key, id, client_id, fail_job_id, fail_job_data, now)
|
|
143
|
+
-- Dispatch the fail job
|
|
144
|
+
dispatch(fail_queue_key,
|
|
145
|
+
fail_job_id, 0, fail_job_data,
|
|
146
|
+
'false', 'false', 'false')
|
|
147
|
+
|
|
148
|
+
-- Finish the original job
|
|
149
|
+
finish(queue_key, id, client_id, now)
|
|
150
|
+
|
|
151
|
+
return { ok = 'OK' }
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
-- Helper: Handle retriable failure
|
|
155
|
+
local function retry(queue_key, id, client_id, retry_at, now)
|
|
156
|
+
local active_job_key = get_active_job_key(queue_key, id)
|
|
157
|
+
local checkouts_key = get_checkouts_key(queue_key, client_id)
|
|
158
|
+
|
|
159
|
+
redis.call('SREM', checkouts_key, id)
|
|
160
|
+
|
|
161
|
+
local retry_count = tonumber(redis.call('HGET', active_job_key, 'retry_count'))
|
|
162
|
+
|
|
163
|
+
retry_count = retry_count + 1
|
|
164
|
+
redis.call('HSET', active_job_key, 'retry_count', retry_count)
|
|
165
|
+
|
|
166
|
+
local result = do_retry(queue_key, id, retry_at)
|
|
167
|
+
|
|
168
|
+
return result
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
-- Helper: Handle stalled job
|
|
172
|
+
local function handle_stall(queue_key, id, retry_at)
|
|
173
|
+
local active_job_key = get_active_job_key(queue_key, id)
|
|
174
|
+
|
|
175
|
+
local stall_count = tonumber(redis.call('HGET', active_job_key, 'stall_count'))
|
|
176
|
+
|
|
177
|
+
stall_count = stall_count + 1
|
|
178
|
+
redis.call('HSET', active_job_key, 'stall_count', stall_count)
|
|
179
|
+
|
|
180
|
+
return do_retry(queue_key, id, retry_at)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
-- Sweep stalled clients
|
|
184
|
+
local function sweep(queue_key, now)
|
|
185
|
+
local expiry_key = get_expiry_key(queue_key)
|
|
186
|
+
|
|
187
|
+
-- Find first stalled client
|
|
188
|
+
local stalled = redis.call('ZRANGEBYSCORE', expiry_key, 0, now, 'LIMIT', 0, 1)
|
|
189
|
+
|
|
190
|
+
if #stalled == 0 then return 0 end
|
|
191
|
+
|
|
192
|
+
local stalled_client_id = stalled[1]
|
|
193
|
+
local checkouts_key = get_checkouts_key(queue_key, stalled_client_id)
|
|
194
|
+
|
|
195
|
+
-- Get all job IDs checked out by this client
|
|
196
|
+
-- RESP3 returns SMEMBERS as { set = { id1 = true, id2 = true, ... } }
|
|
197
|
+
local members_resp = redis.call('SMEMBERS', checkouts_key)
|
|
198
|
+
|
|
199
|
+
for id, _ in pairs(members_resp['set']) do
|
|
200
|
+
handle_stall(queue_key, id, 0)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
-- Clean up the stalled client
|
|
204
|
+
redis.call('ZREM', expiry_key, stalled_client_id)
|
|
205
|
+
redis.call('DEL', checkouts_key)
|
|
206
|
+
|
|
207
|
+
return 1
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
-- Dequeue jobs from waiting queue
|
|
211
|
+
local function dequeue(queue_key, client_id, now, expiry, limit)
|
|
212
|
+
local expiry_key = get_expiry_key(queue_key)
|
|
213
|
+
local checkouts_key = get_checkouts_key(queue_key, client_id)
|
|
214
|
+
local jobs = redis.call('ZRANGEBYSCORE', queue_key, 0, now, 'LIMIT', 0, limit)
|
|
215
|
+
local result = {}
|
|
216
|
+
|
|
217
|
+
for _, id in ipairs(jobs) do
|
|
218
|
+
redis.call('ZREM', queue_key, id)
|
|
219
|
+
local waiting_job_key = get_waiting_job_key(queue_key, id)
|
|
220
|
+
local active_job_key = get_active_job_key(queue_key, id)
|
|
221
|
+
|
|
222
|
+
redis.call('RENAME', waiting_job_key, active_job_key)
|
|
223
|
+
local job = redis.call('HGETALL', active_job_key)
|
|
224
|
+
|
|
225
|
+
redis.call('SADD', checkouts_key, id)
|
|
226
|
+
table.insert(result, job)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
-- Add this client to queue and bump its expiry
|
|
230
|
+
redis.call('ZADD', expiry_key, expiry, client_id)
|
|
231
|
+
|
|
232
|
+
-- Sweep stalled clients
|
|
233
|
+
sweep(queue_key, now)
|
|
234
|
+
|
|
235
|
+
return result
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
-- Cancel a waiting job
|
|
239
|
+
local function cancel(queue_key, id)
|
|
240
|
+
local waiting_job_key = get_waiting_job_key(queue_key, id)
|
|
241
|
+
local removed = redis.call('ZREM', queue_key, id)
|
|
242
|
+
if removed == 1 then
|
|
243
|
+
redis.call('DEL', waiting_job_key)
|
|
244
|
+
end
|
|
245
|
+
return removed
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
-- Bump heartbeat for client and sweep stalled clients
|
|
249
|
+
local function bump(queue_key, client_id, now, expiry)
|
|
250
|
+
local expiry_key = get_expiry_key(queue_key)
|
|
251
|
+
|
|
252
|
+
-- Check if this client exists in expiry set
|
|
253
|
+
-- This can’t be skipped in favour of ZADD XX CH — when a client's new expiry
|
|
254
|
+
-- is the same as the old one, XX CH returns 0 but we need it to return 1
|
|
255
|
+
if not redis.call('ZSCORE', expiry_key, client_id) then
|
|
256
|
+
return 0
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
-- Update expiry
|
|
260
|
+
redis.call('ZADD', expiry_key, 'XX', expiry, client_id)
|
|
261
|
+
|
|
262
|
+
-- Sweep stalled clients
|
|
263
|
+
sweep(queue_key, now)
|
|
264
|
+
|
|
265
|
+
return 1
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
-- Register: queasy_dispatch
|
|
269
|
+
redis.register_function {
|
|
270
|
+
function_name = 'queasy_dispatch',
|
|
271
|
+
callback = function(keys, args)
|
|
272
|
+
local queue_key = keys[1]
|
|
273
|
+
local id = args[1]
|
|
274
|
+
local run_at = tonumber(args[2])
|
|
275
|
+
local data = args[3]
|
|
276
|
+
local update_data = args[4] == 'true'
|
|
277
|
+
local update_run_at = args[5]
|
|
278
|
+
local reset_counts = args[6] == 'true'
|
|
279
|
+
|
|
280
|
+
redis.setresp(3)
|
|
281
|
+
return dispatch(
|
|
282
|
+
queue_key,
|
|
283
|
+
id, run_at, data,
|
|
284
|
+
update_data, update_run_at, reset_counts
|
|
285
|
+
)
|
|
286
|
+
end,
|
|
287
|
+
flags = {}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
-- Register: queasy_dequeue
|
|
291
|
+
redis.register_function {
|
|
292
|
+
function_name = 'queasy_dequeue',
|
|
293
|
+
callback = function(keys, args)
|
|
294
|
+
local queue_key = keys[1]
|
|
295
|
+
local client_id = args[1]
|
|
296
|
+
local now = tonumber(args[2])
|
|
297
|
+
local expiry = tonumber(args[3])
|
|
298
|
+
local limit = tonumber(args[4])
|
|
299
|
+
|
|
300
|
+
redis.setresp(3)
|
|
301
|
+
return dequeue(queue_key, client_id, now, expiry, limit)
|
|
302
|
+
end,
|
|
303
|
+
flags = {}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
-- Register: queasy_cancel
|
|
307
|
+
redis.register_function {
|
|
308
|
+
function_name = 'queasy_cancel',
|
|
309
|
+
callback = function(keys, args)
|
|
310
|
+
local queue_key = keys[1]
|
|
311
|
+
local id = args[1]
|
|
312
|
+
|
|
313
|
+
redis.setresp(3)
|
|
314
|
+
return cancel(queue_key, id)
|
|
315
|
+
end,
|
|
316
|
+
flags = {}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
-- Register: queasy_bump
|
|
320
|
+
redis.register_function {
|
|
321
|
+
function_name = 'queasy_bump',
|
|
322
|
+
callback = function(keys, args)
|
|
323
|
+
local queue_key = keys[1]
|
|
324
|
+
local client_id = args[1]
|
|
325
|
+
local now = tonumber(args[2])
|
|
326
|
+
local expiry = tonumber(args[3])
|
|
327
|
+
|
|
328
|
+
redis.setresp(3)
|
|
329
|
+
return bump(queue_key, client_id, now, expiry)
|
|
330
|
+
end,
|
|
331
|
+
flags = {}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
-- Register: queasy_finish
|
|
335
|
+
redis.register_function {
|
|
336
|
+
function_name = 'queasy_finish',
|
|
337
|
+
callback = function(keys, args)
|
|
338
|
+
local queue_key = keys[1]
|
|
339
|
+
local id = args[1]
|
|
340
|
+
local client_id = args[2]
|
|
341
|
+
local now = tonumber(args[3])
|
|
342
|
+
|
|
343
|
+
redis.setresp(3)
|
|
344
|
+
return finish(queue_key, id, client_id, now)
|
|
345
|
+
end,
|
|
346
|
+
flags = {}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
-- Register: queasy_retry
|
|
350
|
+
redis.register_function {
|
|
351
|
+
function_name = 'queasy_retry',
|
|
352
|
+
callback = function(keys, args)
|
|
353
|
+
local queue_key = keys[1]
|
|
354
|
+
local id = args[1]
|
|
355
|
+
local client_id = args[2]
|
|
356
|
+
local retry_at = tonumber(args[3])
|
|
357
|
+
local now = tonumber(args[5])
|
|
358
|
+
|
|
359
|
+
redis.setresp(3)
|
|
360
|
+
return retry(queue_key, id, client_id, retry_at, now)
|
|
361
|
+
end,
|
|
362
|
+
flags = {}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
-- Register: queasy_fail
|
|
366
|
+
redis.register_function {
|
|
367
|
+
function_name = 'queasy_fail',
|
|
368
|
+
callback = function(keys, args)
|
|
369
|
+
local queue_key = keys[1]
|
|
370
|
+
local fail_queue_key = keys[2]
|
|
371
|
+
local id = args[1]
|
|
372
|
+
local client_id = args[2]
|
|
373
|
+
local fail_job_id = args[3]
|
|
374
|
+
local fail_job_data = args[4]
|
|
375
|
+
local now = tonumber(args[5])
|
|
376
|
+
|
|
377
|
+
redis.setresp(3)
|
|
378
|
+
return fail(queue_key, fail_queue_key, id, client_id, fail_job_id, fail_job_data, now)
|
|
379
|
+
end,
|
|
380
|
+
flags = {}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
-- Register: queasy_version
|
|
384
|
+
redis.register_function {
|
|
385
|
+
function_name = 'queasy_version',
|
|
386
|
+
callback = function(keys, args)
|
|
387
|
+
return '__QUEASY_VERSION__'
|
|
388
|
+
end,
|
|
389
|
+
flags = {}
|
|
390
|
+
}
|
package/dist/queue.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Client } from './client.ts';
|
|
2
|
+
import type { Manager } from './manager.ts';
|
|
3
|
+
import type { Pool } from './pool.ts';
|
|
4
|
+
import type { HandlerOptions, JobOptions, ListenOptions } from './types.ts';
|
|
5
|
+
export declare class Queue {
|
|
6
|
+
key: string;
|
|
7
|
+
client: Client;
|
|
8
|
+
pool: Pool | undefined;
|
|
9
|
+
manager: Manager | undefined;
|
|
10
|
+
handlerOptions: Required<HandlerOptions> | undefined;
|
|
11
|
+
handlerPath: string | undefined;
|
|
12
|
+
failKey: string | undefined;
|
|
13
|
+
constructor(key: string, client: Client, pool: Pool | undefined, manager: Manager | undefined);
|
|
14
|
+
listen(handlerPath: string, options?: ListenOptions): Promise<void>;
|
|
15
|
+
dispatch(data: any, options?: JobOptions): Promise<string>;
|
|
16
|
+
cancel(id: string): Promise<boolean>;
|
|
17
|
+
dequeue(count: number): Promise<{
|
|
18
|
+
count: number;
|
|
19
|
+
promise: Promise<unknown[]>;
|
|
20
|
+
}>;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=queue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../src/queue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,KAAK,EAAe,cAAc,EAAO,UAAU,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAG9F,qBAAa,KAAK;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,IAAI,GAAG,SAAS,CAAC;IACvB,OAAO,EAAE,OAAO,GAAG,SAAS,CAAC;IAC7B,cAAc,EAAE,QAAQ,CAAC,cAAc,CAAC,GAAG,SAAS,CAAC;IACrD,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;gBAEhB,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,SAAS,EAAE,OAAO,EAAE,OAAO,GAAG,SAAS;IAUvF,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBvE,QAAQ,CAEV,IAAI,EAAE,GAAG,EACT,OAAO,GAAE,UAAe,GACzB,OAAO,CAAC,MAAM,CAAC;IAcZ,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKpC,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAA;KAAE,CAAC;CAyCxF"}
|
package/dist/queue.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { DEFAULT_RETRY_OPTIONS, FAILJOB_RETRY_OPTIONS } from "./constants.js";
|
|
2
|
+
import { generateId } from "./utils.js";
|
|
3
|
+
export class Queue {
|
|
4
|
+
key;
|
|
5
|
+
client;
|
|
6
|
+
pool;
|
|
7
|
+
manager;
|
|
8
|
+
handlerOptions;
|
|
9
|
+
handlerPath;
|
|
10
|
+
failKey;
|
|
11
|
+
constructor(key, client, pool, manager) {
|
|
12
|
+
this.key = key;
|
|
13
|
+
this.client = client;
|
|
14
|
+
this.pool = pool;
|
|
15
|
+
this.manager = manager;
|
|
16
|
+
this.handlerOptions = undefined;
|
|
17
|
+
this.handlerPath = undefined;
|
|
18
|
+
this.failKey = undefined;
|
|
19
|
+
}
|
|
20
|
+
async listen(handlerPath, options = {}) {
|
|
21
|
+
const { failHandler, failRetryOptions, ...retryOptions } = options;
|
|
22
|
+
if (this.client.disconnected)
|
|
23
|
+
throw new Error("Can't listen: client disconnected");
|
|
24
|
+
if (!this.pool || !this.manager)
|
|
25
|
+
throw new Error("Can't listen: non-processing client");
|
|
26
|
+
this.handlerPath = handlerPath;
|
|
27
|
+
this.handlerOptions = { ...DEFAULT_RETRY_OPTIONS, ...retryOptions };
|
|
28
|
+
if (failHandler) {
|
|
29
|
+
this.failKey = `${this.key}-fail`;
|
|
30
|
+
const failQueue = this.client.queue(this.failKey, true);
|
|
31
|
+
failQueue.listen(failHandler, { ...FAILJOB_RETRY_OPTIONS, ...failRetryOptions });
|
|
32
|
+
}
|
|
33
|
+
this.manager.addQueue(this);
|
|
34
|
+
}
|
|
35
|
+
async dispatch(
|
|
36
|
+
// biome-ignore lint/suspicious/noExplicitAny: Data is any serializable value
|
|
37
|
+
data, options = {}) {
|
|
38
|
+
if (this.client.disconnected)
|
|
39
|
+
throw new Error("Can't dispatch: client disconnected");
|
|
40
|
+
const { id = generateId(), runAt = 0, updateData = false, updateRunAt = false, resetCounts = false, } = options;
|
|
41
|
+
await this.client.dispatch(this.key, id, runAt, data, updateData, updateRunAt, resetCounts);
|
|
42
|
+
return id;
|
|
43
|
+
}
|
|
44
|
+
async cancel(id) {
|
|
45
|
+
if (this.client.disconnected)
|
|
46
|
+
throw new Error("Can't cancel: client disconnected");
|
|
47
|
+
return await this.client.cancel(this.key, id);
|
|
48
|
+
}
|
|
49
|
+
async dequeue(count) {
|
|
50
|
+
const pool = this.pool;
|
|
51
|
+
const handlerPath = this.handlerPath;
|
|
52
|
+
const { maxRetries, maxStalls, maxBackoff, minBackoff, size, timeout } = this.handlerOptions;
|
|
53
|
+
const jobs = await this.client.dequeue(this.key, count);
|
|
54
|
+
const promise = Promise.all(jobs.map(async (job) => {
|
|
55
|
+
if (job.stallCount >= maxStalls) {
|
|
56
|
+
if (!this.failKey)
|
|
57
|
+
return this.client.finish(this.key, job.id);
|
|
58
|
+
const failJobData = [job.id, job.data, { message: 'Max stalls exceeded' }];
|
|
59
|
+
return this.client.fail(this.key, this.failKey, job.id, failJobData);
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
await pool.process(handlerPath, job, size, timeout);
|
|
63
|
+
await this.client.finish(this.key, job.id);
|
|
64
|
+
}
|
|
65
|
+
catch (message) {
|
|
66
|
+
const { error } = message;
|
|
67
|
+
const { retryAt = 0, kind } = error;
|
|
68
|
+
if (kind === 'permanent' || job.retryCount >= maxRetries) {
|
|
69
|
+
if (!this.failKey)
|
|
70
|
+
return this.client.finish(this.key, job.id);
|
|
71
|
+
const failJobData = [job.id, job.data, error];
|
|
72
|
+
return this.client.fail(this.key, this.failKey, job.id, failJobData);
|
|
73
|
+
}
|
|
74
|
+
const backoffUntil = Date.now() + Math.min(maxBackoff, minBackoff * 2 ** job.retryCount);
|
|
75
|
+
await this.client.retry(this.key, job.id, Math.max(retryAt, backoffUntil));
|
|
76
|
+
}
|
|
77
|
+
}));
|
|
78
|
+
return { count: jobs.length, promise };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=queue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue.js","sourceRoot":"","sources":["../src/queue.ts"],"names":[],"mappings":"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"}
|