@elysiumjs/heracles 0.4.4 → 0.5.0
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/README.md +83 -75
- package/dist/heracles.d.ts +13 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +2 -0
- package/dist/job.d.ts +249 -0
- package/dist/queue.d.ts +247 -0
- package/dist/transport.d.ts +427 -0
- package/dist/transports/redis.transport.d.ts +251 -0
- package/dist/utils.d.ts +34 -0
- package/dist/worker.d.ts +250 -0
- package/dist/workers/base.worker.d.ts +216 -0
- package/dist/workers/redis.worker.d.ts +57 -0
- package/package.json +12 -10
- package/tsconfig.json +34 -33
- package/USAGE.md +0 -527
- package/index.ts +0 -16
- package/src/heracles.ts +0 -68
- package/src/index.ts +0 -28
- package/src/job.ts +0 -413
- package/src/queue.ts +0 -567
- package/src/transport.ts +0 -518
- package/src/transports/redis.transport.ts +0 -1558
- package/src/transports/thread.transport.ts +0 -289
- package/src/utils.ts +0 -61
- package/src/worker.ts +0 -312
- package/src/worker_pool.ts +0 -393
- package/src/workers/base.worker.ts +0 -804
- package/src/workers/redis.worker.ts +0 -523
- package/src/workers/thread.worker.entry.ts +0 -171
- package/src/workers/thread.worker.ts +0 -525
- package/types.d.ts +0 -21
package/src/worker_pool.ts
DELETED
|
@@ -1,393 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2025-present Workbud Technologies Inc. All rights reserved.
|
|
2
|
-
//
|
|
3
|
-
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
// you may not use this file except in compliance with the License.
|
|
5
|
-
// You may obtain a copy of the License at
|
|
6
|
-
//
|
|
7
|
-
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
//
|
|
9
|
-
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
// See the License for the specific language governing permissions and
|
|
13
|
-
// limitations under the License.
|
|
14
|
-
|
|
15
|
-
import type { Job, JobDispatchOptions } from './job';
|
|
16
|
-
import type { WorkerInfo } from './worker';
|
|
17
|
-
|
|
18
|
-
import { InteractsWithConsole } from '@elysiumjs/core';
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Interface for tracking worker selection state per queue.
|
|
22
|
-
*/
|
|
23
|
-
interface QueueWorkerState {
|
|
24
|
-
/**
|
|
25
|
-
* All workers assigned to this queue
|
|
26
|
-
*/
|
|
27
|
-
workers: WorkerInfo[];
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Index of the last selected worker for round-robin
|
|
31
|
-
*/
|
|
32
|
-
lastWorkerIndex: number;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* WorkerPool manages a pool of workers and distributes jobs among them
|
|
37
|
-
* using a round-robin algorithm.
|
|
38
|
-
*/
|
|
39
|
-
export class WorkerPool extends InteractsWithConsole {
|
|
40
|
-
/**
|
|
41
|
-
* Singleton instance of the worker pool
|
|
42
|
-
*/
|
|
43
|
-
private static _instance: WorkerPool;
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* All workers in the pool, indexed by worker ID
|
|
47
|
-
*/
|
|
48
|
-
private workers: Map<string, WorkerInfo> = new Map();
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Queue-to-workers mapping for tracking which workers handle each queue
|
|
52
|
-
*/
|
|
53
|
-
private queueWorkers: Map<string, QueueWorkerState> = new Map();
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Last used worker per queue for true round-robin
|
|
57
|
-
*/
|
|
58
|
-
private lastUsedWorker: Map<string, string> = new Map();
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Get the singleton instance of the worker pool
|
|
62
|
-
*/
|
|
63
|
-
public static get instance(): WorkerPool {
|
|
64
|
-
if (!WorkerPool._instance) {
|
|
65
|
-
WorkerPool._instance = new WorkerPool();
|
|
66
|
-
}
|
|
67
|
-
return WorkerPool._instance;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Private constructor to enforce singleton pattern
|
|
72
|
-
*/
|
|
73
|
-
private constructor() {
|
|
74
|
-
super();
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Initialize the worker pool
|
|
79
|
-
*/
|
|
80
|
-
public async init(): Promise<void> {
|
|
81
|
-
this.info('WorkerPool initialized');
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Add a worker to the pool and associate it with queues
|
|
86
|
-
*
|
|
87
|
-
* @param worker The worker information to add
|
|
88
|
-
* @param queues The queues this worker will handle
|
|
89
|
-
*/
|
|
90
|
-
public async addWorker(worker: WorkerInfo, queues: string[] = []): Promise<void> {
|
|
91
|
-
if (this.workers.has(worker.id)) {
|
|
92
|
-
this.warning(`Worker ${worker.id} is already registered in the pool`);
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Store the worker
|
|
97
|
-
this.workers.set(worker.id, worker);
|
|
98
|
-
|
|
99
|
-
// Associate worker with each queue
|
|
100
|
-
for (const queueName of queues) {
|
|
101
|
-
// Get or create queue state
|
|
102
|
-
const queueState = this.getOrCreateQueueState(queueName);
|
|
103
|
-
|
|
104
|
-
// Add worker to queue if not already present
|
|
105
|
-
if (!queueState.workers.some((w) => w.id === worker.id)) {
|
|
106
|
-
queueState.workers.push(worker);
|
|
107
|
-
this.debug(`Worker ${worker.id} assigned to queue '${queueName}'`);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
this.info(`Worker ${worker.id} added to pool with ${queues.length} queues`);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Remove a worker from the pool
|
|
116
|
-
*
|
|
117
|
-
* @param workerId The ID of the worker to remove
|
|
118
|
-
*/
|
|
119
|
-
public async removeWorker(workerId: string): Promise<void> {
|
|
120
|
-
const worker = this.workers.get(workerId);
|
|
121
|
-
if (!worker) {
|
|
122
|
-
this.warning(`Attempted to remove non-existent worker ${workerId}`);
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Remove worker from all queues
|
|
127
|
-
for (const [queueName, queueState] of this.queueWorkers.entries()) {
|
|
128
|
-
const workerIndex = queueState.workers.findIndex((w) => w.id === workerId);
|
|
129
|
-
if (workerIndex >= 0) {
|
|
130
|
-
queueState.workers.splice(workerIndex, 1);
|
|
131
|
-
|
|
132
|
-
// Adjust lastWorkerIndex if needed
|
|
133
|
-
if (queueState.lastWorkerIndex >= queueState.workers.length) {
|
|
134
|
-
queueState.lastWorkerIndex = queueState.workers.length > 0 ? 0 : -1;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Also adjust lastUsedWorker if it was this worker
|
|
138
|
-
if (this.lastUsedWorker.get(queueName) === workerId) {
|
|
139
|
-
this.lastUsedWorker.delete(queueName);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
this.debug(`Worker ${workerId} removed from queue '${queueName}'`);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Remove worker from the pool
|
|
147
|
-
this.workers.delete(workerId);
|
|
148
|
-
this.info(`Worker ${workerId} removed from pool`);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Terminate all workers in the pool
|
|
153
|
-
*/
|
|
154
|
-
public async terminate(): Promise<void> {
|
|
155
|
-
for (const workerId of this.workers.keys()) {
|
|
156
|
-
await this.removeWorker(workerId);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
this.info('WorkerPool terminated');
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Get the next available worker for a queue using round-robin
|
|
164
|
-
*
|
|
165
|
-
* @param queueName The name of the queue to get a worker for
|
|
166
|
-
* @returns The selected worker info or null if none available
|
|
167
|
-
*/
|
|
168
|
-
public getNextWorker(queueName: string): WorkerInfo | null {
|
|
169
|
-
const queueState = this.queueWorkers.get(queueName);
|
|
170
|
-
if (!queueState || queueState.workers.length === 0) {
|
|
171
|
-
this.warning(`No workers available for queue '${queueName}'`);
|
|
172
|
-
return null;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// True round-robin selection that considers worker load
|
|
176
|
-
// First, sort workers by number of active jobs (lightest load first)
|
|
177
|
-
const sortedWorkers = [...queueState.workers].sort((a, b) => a.activeJobs - b.activeJobs);
|
|
178
|
-
|
|
179
|
-
// If there's a big difference in load (e.g., more than 5 jobs), prefer the least loaded worker
|
|
180
|
-
const leastLoaded = sortedWorkers[0];
|
|
181
|
-
const mostLoaded = sortedWorkers[sortedWorkers.length - 1];
|
|
182
|
-
|
|
183
|
-
if (mostLoaded.activeJobs - leastLoaded.activeJobs > 5) {
|
|
184
|
-
this.debug(
|
|
185
|
-
`Selected least loaded worker ${leastLoaded.id} for queue '${queueName}' (load: ${leastLoaded.activeJobs}/${mostLoaded.activeJobs})`
|
|
186
|
-
);
|
|
187
|
-
return leastLoaded;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Otherwise, use round-robin to ensure fair distribution
|
|
191
|
-
// Find the index of the last used worker, or -1 if none was used yet
|
|
192
|
-
const lastUsedWorkerId = this.lastUsedWorker.get(queueName);
|
|
193
|
-
let lastIndex = -1;
|
|
194
|
-
|
|
195
|
-
if (lastUsedWorkerId) {
|
|
196
|
-
lastIndex = queueState.workers.findIndex((w) => w.id === lastUsedWorkerId);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Move to the next worker
|
|
200
|
-
const nextIndex = (lastIndex + 1) % queueState.workers.length;
|
|
201
|
-
const selectedWorker = queueState.workers[nextIndex];
|
|
202
|
-
|
|
203
|
-
// Update the last used worker
|
|
204
|
-
this.lastUsedWorker.set(queueName, selectedWorker.id);
|
|
205
|
-
queueState.lastWorkerIndex = nextIndex;
|
|
206
|
-
|
|
207
|
-
this.debug(
|
|
208
|
-
`Selected worker ${selectedWorker.id} for queue '${queueName}' (worker ${nextIndex + 1}/${queueState.workers.length}, active jobs: ${selectedWorker.activeJobs})`
|
|
209
|
-
);
|
|
210
|
-
return selectedWorker;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Execute a job using a worker from the pool
|
|
215
|
-
*
|
|
216
|
-
* @param job The job to execute
|
|
217
|
-
* @param queueName The queue the job belongs to
|
|
218
|
-
* @param options Optional job dispatch options
|
|
219
|
-
* @returns A promise that resolves when the job is dispatched to a worker
|
|
220
|
-
*/
|
|
221
|
-
public async runJob(
|
|
222
|
-
job: Job,
|
|
223
|
-
queueName: string,
|
|
224
|
-
options: JobDispatchOptions = {}
|
|
225
|
-
): Promise<void> {
|
|
226
|
-
const worker = this.getNextWorker(queueName);
|
|
227
|
-
if (!worker) {
|
|
228
|
-
throw new Error(`No workers available for queue '${queueName}'`);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// Assign queue name to job
|
|
232
|
-
job.queueName = queueName;
|
|
233
|
-
|
|
234
|
-
// Apply job options
|
|
235
|
-
const jobOptions = {
|
|
236
|
-
scheduledFor: options.scheduledFor,
|
|
237
|
-
priority: options.priority || 10,
|
|
238
|
-
maxRetries: options.maxRetries,
|
|
239
|
-
retryDelay: options.retryDelay
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
// Dispatch job to worker
|
|
243
|
-
try {
|
|
244
|
-
this.debug(`Dispatching job ${job.id} to worker ${worker.id} in queue '${queueName}'`);
|
|
245
|
-
// Pass job options to worker
|
|
246
|
-
if (typeof worker.addJob === 'function') {
|
|
247
|
-
const result = await worker.addJob(job, queueName);
|
|
248
|
-
if (!result) {
|
|
249
|
-
throw new Error(`Worker ${worker.id} failed to accept job ${job.id}`);
|
|
250
|
-
}
|
|
251
|
-
} else {
|
|
252
|
-
throw new Error(`Worker ${worker.id} does not implement addJob method`);
|
|
253
|
-
}
|
|
254
|
-
} catch (error) {
|
|
255
|
-
// If this worker fails, remove it and try another worker
|
|
256
|
-
this.error(`Failed to dispatch job ${job.id} to worker ${worker.id}: ${error}`);
|
|
257
|
-
await this.removeWorker(worker.id);
|
|
258
|
-
|
|
259
|
-
// Retry with another worker
|
|
260
|
-
await this.runJob(job, queueName, options);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* Cancel a job across all workers
|
|
266
|
-
*
|
|
267
|
-
* @param jobId The ID of the job to cancel
|
|
268
|
-
* @param queueName Optional queue name to limit search
|
|
269
|
-
* @returns True if the job was canceled, false otherwise
|
|
270
|
-
*/
|
|
271
|
-
public async cancelJob(jobId: string, queueName?: string): Promise<boolean> {
|
|
272
|
-
let canceled = false;
|
|
273
|
-
|
|
274
|
-
if (queueName) {
|
|
275
|
-
// Try to cancel in specific queue
|
|
276
|
-
const queueState = this.queueWorkers.get(queueName);
|
|
277
|
-
if (queueState) {
|
|
278
|
-
// Try each worker in the queue
|
|
279
|
-
for (const worker of queueState.workers) {
|
|
280
|
-
try {
|
|
281
|
-
if (await worker.cancelJob(jobId, queueName)) {
|
|
282
|
-
canceled = true;
|
|
283
|
-
this.debug(`Job ${jobId} cancelled on worker ${worker.id}`);
|
|
284
|
-
break;
|
|
285
|
-
}
|
|
286
|
-
} catch (error) {
|
|
287
|
-
this.warning(`Error cancelling job ${jobId} on worker ${worker.id}: ${error}`);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
} else {
|
|
292
|
-
// Try all workers
|
|
293
|
-
for (const worker of this.workers.values()) {
|
|
294
|
-
try {
|
|
295
|
-
if (await worker.cancelJob(jobId)) {
|
|
296
|
-
canceled = true;
|
|
297
|
-
this.debug(`Job ${jobId} cancelled on worker ${worker.id}`);
|
|
298
|
-
break;
|
|
299
|
-
}
|
|
300
|
-
} catch (error) {
|
|
301
|
-
this.warning(`Error cancelling job ${jobId} on worker ${worker.id}: ${error}`);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
return canceled;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* Cancel all jobs across all workers
|
|
311
|
-
*
|
|
312
|
-
* @param queueName Optional queue name to limit cancellation
|
|
313
|
-
* @returns The number of jobs canceled
|
|
314
|
-
*/
|
|
315
|
-
public async cancelAllJobs(queueName?: string): Promise<number> {
|
|
316
|
-
let total = 0;
|
|
317
|
-
|
|
318
|
-
if (queueName) {
|
|
319
|
-
// Cancel in specific queue
|
|
320
|
-
const queueState = this.queueWorkers.get(queueName);
|
|
321
|
-
if (queueState) {
|
|
322
|
-
for (const worker of queueState.workers) {
|
|
323
|
-
try {
|
|
324
|
-
const count = await worker.cancelAllJobs(queueName);
|
|
325
|
-
total += count;
|
|
326
|
-
this.debug(`Cancelled ${count} jobs on worker ${worker.id} in queue ${queueName}`);
|
|
327
|
-
} catch (error) {
|
|
328
|
-
this.warning(`Error cancelling jobs on worker ${worker.id}: ${error}`);
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
} else {
|
|
333
|
-
// Cancel in all workers
|
|
334
|
-
for (const worker of this.workers.values()) {
|
|
335
|
-
try {
|
|
336
|
-
const count = await worker.cancelAllJobs();
|
|
337
|
-
total += count;
|
|
338
|
-
this.debug(`Cancelled ${count} jobs on worker ${worker.id}`);
|
|
339
|
-
} catch (error) {
|
|
340
|
-
this.warning(`Error cancelling jobs on worker ${worker.id}: ${error}`);
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
return total;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
/**
|
|
349
|
-
* Get all queues being handled by this pool
|
|
350
|
-
*
|
|
351
|
-
* @returns An array of queue names
|
|
352
|
-
*/
|
|
353
|
-
public getQueues(): string[] {
|
|
354
|
-
return Array.from(this.queueWorkers.keys());
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
/**
|
|
358
|
-
* Get all workers in the pool
|
|
359
|
-
*
|
|
360
|
-
* @returns An array of worker infos
|
|
361
|
-
*/
|
|
362
|
-
public getWorkers(): WorkerInfo[] {
|
|
363
|
-
return Array.from(this.workers.values());
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
/**
|
|
367
|
-
* Get workers for a specific queue
|
|
368
|
-
*
|
|
369
|
-
* @param queueName The name of the queue
|
|
370
|
-
* @returns An array of worker infos for the queue
|
|
371
|
-
*/
|
|
372
|
-
public getWorkersForQueue(queueName: string): WorkerInfo[] {
|
|
373
|
-
const queueState = this.queueWorkers.get(queueName);
|
|
374
|
-
return queueState ? [...queueState.workers] : [];
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
/**
|
|
378
|
-
* Get or create queue state for tracking workers
|
|
379
|
-
*
|
|
380
|
-
* @param queueName The name of the queue
|
|
381
|
-
* @returns The queue worker state
|
|
382
|
-
*/
|
|
383
|
-
private getOrCreateQueueState(queueName: string): QueueWorkerState {
|
|
384
|
-
if (!this.queueWorkers.has(queueName)) {
|
|
385
|
-
this.queueWorkers.set(queueName, {
|
|
386
|
-
workers: [],
|
|
387
|
-
lastWorkerIndex: -1
|
|
388
|
-
});
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
return this.queueWorkers.get(queueName)!;
|
|
392
|
-
}
|
|
393
|
-
}
|