bunqueue 1.1.3 → 1.2.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 +37 -0
- package/dist/application/dlqManager.d.ts.map +1 -1
- package/dist/application/dlqManager.js +11 -3
- package/dist/application/dlqManager.js.map +1 -1
- package/dist/application/eventsManager.d.ts +10 -1
- package/dist/application/eventsManager.d.ts.map +1 -1
- package/dist/application/eventsManager.js +91 -21
- package/dist/application/eventsManager.js.map +1 -1
- package/dist/application/operations/ack.d.ts +16 -1
- package/dist/application/operations/ack.d.ts.map +1 -1
- package/dist/application/operations/ack.js +269 -5
- package/dist/application/operations/ack.js.map +1 -1
- package/dist/application/operations/jobManagement.d.ts.map +1 -1
- package/dist/application/operations/jobManagement.js +17 -3
- package/dist/application/operations/jobManagement.js.map +1 -1
- package/dist/application/operations/pull.d.ts +1 -0
- package/dist/application/operations/pull.d.ts.map +1 -1
- package/dist/application/operations/pull.js +106 -8
- package/dist/application/operations/pull.js.map +1 -1
- package/dist/application/operations/push.d.ts.map +1 -1
- package/dist/application/operations/push.js +9 -0
- package/dist/application/operations/push.js.map +1 -1
- package/dist/application/operations/queueControl.d.ts +4 -1
- package/dist/application/operations/queueControl.d.ts.map +1 -1
- package/dist/application/operations/queueControl.js +16 -13
- package/dist/application/operations/queueControl.js.map +1 -1
- package/dist/application/queueManager.d.ts +28 -2
- package/dist/application/queueManager.d.ts.map +1 -1
- package/dist/application/queueManager.js +132 -34
- package/dist/application/queueManager.js.map +1 -1
- package/dist/application/webhookManager.d.ts +7 -1
- package/dist/application/webhookManager.d.ts.map +1 -1
- package/dist/application/webhookManager.js +38 -6
- package/dist/application/webhookManager.js.map +1 -1
- package/dist/application/workerManager.d.ts +5 -1
- package/dist/application/workerManager.d.ts.map +1 -1
- package/dist/application/workerManager.js +35 -10
- package/dist/application/workerManager.js.map +1 -1
- package/dist/bunqueue +0 -0
- package/dist/cli/commands/backup.d.ts +20 -0
- package/dist/cli/commands/backup.d.ts.map +1 -0
- package/dist/cli/commands/backup.js +143 -0
- package/dist/cli/commands/backup.js.map +1 -0
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +6 -0
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +27 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/domain/queue/priorityQueue.d.ts +15 -0
- package/dist/domain/queue/priorityQueue.d.ts.map +1 -1
- package/dist/domain/queue/priorityQueue.js +40 -0
- package/dist/domain/queue/priorityQueue.js.map +1 -1
- package/dist/domain/queue/shard.d.ts +67 -0
- package/dist/domain/queue/shard.d.ts.map +1 -1
- package/dist/domain/queue/shard.js +198 -0
- package/dist/domain/queue/shard.js.map +1 -1
- package/dist/infrastructure/backup/index.d.ts +5 -0
- package/dist/infrastructure/backup/index.d.ts.map +1 -0
- package/dist/infrastructure/backup/index.js +5 -0
- package/dist/infrastructure/backup/index.js.map +1 -0
- package/dist/infrastructure/backup/s3Backup.d.ts +101 -0
- package/dist/infrastructure/backup/s3Backup.d.ts.map +1 -0
- package/dist/infrastructure/backup/s3Backup.js +313 -0
- package/dist/infrastructure/backup/s3Backup.js.map +1 -0
- package/dist/infrastructure/persistence/sqlite.d.ts +3 -1
- package/dist/infrastructure/persistence/sqlite.d.ts.map +1 -1
- package/dist/infrastructure/persistence/sqlite.js +25 -4
- package/dist/infrastructure/persistence/sqlite.js.map +1 -1
- package/dist/infrastructure/scheduler/cronScheduler.d.ts +7 -0
- package/dist/infrastructure/scheduler/cronScheduler.d.ts.map +1 -1
- package/dist/infrastructure/scheduler/cronScheduler.js +49 -18
- package/dist/infrastructure/scheduler/cronScheduler.js.map +1 -1
- package/dist/infrastructure/server/handlers/advanced.d.ts +1 -1
- package/dist/infrastructure/server/handlers/advanced.d.ts.map +1 -1
- package/dist/infrastructure/server/handlers/advanced.js +14 -11
- package/dist/infrastructure/server/handlers/advanced.js.map +1 -1
- package/dist/infrastructure/server/handlers/core.d.ts +2 -2
- package/dist/infrastructure/server/handlers/core.d.ts.map +1 -1
- package/dist/infrastructure/server/handlers/core.js +6 -12
- package/dist/infrastructure/server/handlers/core.js.map +1 -1
- package/dist/infrastructure/server/http.d.ts.map +1 -1
- package/dist/infrastructure/server/http.js +22 -27
- package/dist/infrastructure/server/http.js.map +1 -1
- package/dist/infrastructure/server/rateLimiter.d.ts +4 -3
- package/dist/infrastructure/server/rateLimiter.d.ts.map +1 -1
- package/dist/infrastructure/server/rateLimiter.js +71 -19
- package/dist/infrastructure/server/rateLimiter.js.map +1 -1
- package/dist/main.js +18 -0
- package/dist/main.js.map +1 -1
- package/dist/shared/hash.d.ts +4 -4
- package/dist/shared/hash.d.ts.map +1 -1
- package/dist/shared/hash.js +20 -4
- package/dist/shared/hash.js.map +1 -1
- package/dist/shared/lock.d.ts +7 -1
- package/dist/shared/lock.d.ts.map +1 -1
- package/dist/shared/lock.js +75 -40
- package/dist/shared/lock.js.map +1 -1
- package/dist/shared/logger.d.ts +1 -0
- package/dist/shared/logger.d.ts.map +1 -1
- package/dist/shared/logger.js +1 -0
- package/dist/shared/logger.js.map +1 -1
- package/dist/shared/lru.d.ts +55 -0
- package/dist/shared/lru.d.ts.map +1 -1
- package/dist/shared/lru.js +165 -2
- package/dist/shared/lru.js.map +1 -1
- package/dist/shared/minHeap.d.ts +35 -0
- package/dist/shared/minHeap.d.ts.map +1 -0
- package/dist/shared/minHeap.js +116 -0
- package/dist/shared/minHeap.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -59,6 +59,8 @@ bunqueue
|
|
|
59
59
|
docker run -p 6789:6789 -p 6790:6790 ghcr.io/egeominotti/bunqueue
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
+
<img src=".github/terminal.png" alt="bunqueue server running" width="600" />
|
|
63
|
+
|
|
62
64
|
### Production Setup
|
|
63
65
|
|
|
64
66
|
For production, enable **persistence** and **authentication**:
|
|
@@ -672,6 +674,41 @@ curl -X POST http://localhost:6790/cron \
|
|
|
672
674
|
| `DATA_PATH` | - | SQLite database path (in-memory if not set) |
|
|
673
675
|
| `CORS_ALLOW_ORIGIN` | * | Allowed CORS origins |
|
|
674
676
|
|
|
677
|
+
### S3 Backup Configuration
|
|
678
|
+
|
|
679
|
+
| Variable | Default | Description |
|
|
680
|
+
|----------|---------|-------------|
|
|
681
|
+
| `S3_BACKUP_ENABLED` | 0 | Enable automated S3 backups (1/true) |
|
|
682
|
+
| `S3_ACCESS_KEY_ID` | - | S3 access key |
|
|
683
|
+
| `S3_SECRET_ACCESS_KEY` | - | S3 secret key |
|
|
684
|
+
| `S3_BUCKET` | - | S3 bucket name |
|
|
685
|
+
| `S3_REGION` | us-east-1 | S3 region |
|
|
686
|
+
| `S3_ENDPOINT` | - | Custom endpoint for S3-compatible services |
|
|
687
|
+
| `S3_BACKUP_INTERVAL` | 21600000 | Backup interval in ms (default: 6 hours) |
|
|
688
|
+
| `S3_BACKUP_RETENTION` | 7 | Number of backups to retain |
|
|
689
|
+
| `S3_BACKUP_PREFIX` | backups/ | Prefix for backup files |
|
|
690
|
+
|
|
691
|
+
**Supported S3 Providers:**
|
|
692
|
+
- AWS S3
|
|
693
|
+
- Cloudflare R2: `S3_ENDPOINT=https://<account-id>.r2.cloudflarestorage.com`
|
|
694
|
+
- MinIO: `S3_ENDPOINT=http://localhost:9000`
|
|
695
|
+
- DigitalOcean Spaces: `S3_ENDPOINT=https://<region>.digitaloceanspaces.com`
|
|
696
|
+
|
|
697
|
+
**CLI Commands:**
|
|
698
|
+
```bash
|
|
699
|
+
# Create backup immediately
|
|
700
|
+
bunqueue backup now
|
|
701
|
+
|
|
702
|
+
# List available backups
|
|
703
|
+
bunqueue backup list
|
|
704
|
+
|
|
705
|
+
# Restore from backup (requires --force)
|
|
706
|
+
bunqueue backup restore backups/bunqueue-2024-01-15.db --force
|
|
707
|
+
|
|
708
|
+
# Show backup status
|
|
709
|
+
bunqueue backup status
|
|
710
|
+
```
|
|
711
|
+
|
|
675
712
|
### Authentication
|
|
676
713
|
|
|
677
714
|
Enable authentication by setting `AUTH_TOKENS`:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dlqManager.d.ts","sourceRoot":"","sources":["../../src/application/dlqManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAGnD,iCAAiC;AACjC,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,QAAQ,EAAE,GAAG,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;CACnC;AAED,wBAAwB;AACxB,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,GAAG,EAAE,CAIhF;AAED,0BAA0B;AAC1B,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"dlqManager.d.ts","sourceRoot":"","sources":["../../src/application/dlqManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAGnD,iCAAiC;AACjC,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,QAAQ,EAAE,GAAG,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;CACnC;AAED,wBAAwB;AACxB,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,GAAG,EAAE,CAIhF;AAED,0BAA0B;AAC1B,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,CAqClF;AAED,8BAA8B;AAC9B,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,GAAG,MAAM,CAGnE"}
|
|
@@ -13,25 +13,33 @@ export function getDlqJobs(queue, ctx, count) {
|
|
|
13
13
|
export function retryDlqJobs(queue, ctx, jobId) {
|
|
14
14
|
const idx = shardIndex(queue);
|
|
15
15
|
const shard = ctx.shards[idx];
|
|
16
|
+
const now = Date.now();
|
|
16
17
|
if (jobId) {
|
|
18
|
+
// removeFromDlq already decrements dlq counter
|
|
17
19
|
const job = shard.removeFromDlq(queue, jobId);
|
|
18
20
|
if (job) {
|
|
19
21
|
job.attempts = 0;
|
|
20
|
-
job.runAt =
|
|
22
|
+
job.runAt = now;
|
|
21
23
|
shard.getQueue(queue).push(job);
|
|
24
|
+
// Update running counters for O(1) stats and temporal index
|
|
25
|
+
const isDelayed = job.runAt > now;
|
|
26
|
+
shard.incrementQueued(job.id, isDelayed, job.createdAt, queue);
|
|
22
27
|
ctx.jobIndex.set(job.id, { type: 'queue', shardIdx: idx, queueName: queue });
|
|
23
28
|
return 1;
|
|
24
29
|
}
|
|
25
30
|
return 0;
|
|
26
31
|
}
|
|
27
|
-
// Retry all
|
|
32
|
+
// Retry all - clearDlq already decrements dlq counter
|
|
28
33
|
const jobs = shard.getDlq(queue);
|
|
29
34
|
const count = jobs.length;
|
|
30
35
|
shard.clearDlq(queue);
|
|
31
36
|
for (const job of jobs) {
|
|
32
37
|
job.attempts = 0;
|
|
33
|
-
job.runAt =
|
|
38
|
+
job.runAt = now;
|
|
34
39
|
shard.getQueue(queue).push(job);
|
|
40
|
+
// Update running counters for O(1) stats and temporal index
|
|
41
|
+
const isDelayed = job.runAt > now;
|
|
42
|
+
shard.incrementQueued(job.id, isDelayed, job.createdAt, queue);
|
|
35
43
|
ctx.jobIndex.set(job.id, { type: 'queue', shardIdx: idx, queueName: queue });
|
|
36
44
|
}
|
|
37
45
|
return count;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dlqManager.js","sourceRoot":"","sources":["../../src/application/dlqManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAQ5C,wBAAwB;AACxB,MAAM,UAAU,UAAU,CAAC,KAAa,EAAE,GAAe,EAAE,KAAc;IACvE,MAAM,GAAG,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;IAC9B,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC3C,OAAO,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAC7C,CAAC;AAED,0BAA0B;AAC1B,MAAM,UAAU,YAAY,CAAC,KAAa,EAAE,GAAe,EAAE,KAAa;IACxE,MAAM,GAAG,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;IAC9B,MAAM,KAAK,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"dlqManager.js","sourceRoot":"","sources":["../../src/application/dlqManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,12 +11,21 @@ export type EventSubscriber = (event: JobEvent) => void;
|
|
|
11
11
|
export declare class EventsManager {
|
|
12
12
|
private readonly webhookManager;
|
|
13
13
|
private readonly subscribers;
|
|
14
|
+
/** Waiters for specific job completions - for efficient WaitJob implementation */
|
|
15
|
+
private readonly completionWaiters;
|
|
14
16
|
constructor(webhookManager: WebhookManager);
|
|
15
17
|
/** Subscribe to job events */
|
|
16
18
|
subscribe(callback: EventSubscriber): () => void;
|
|
17
19
|
/** Clear all subscribers (for shutdown) */
|
|
18
20
|
clear(): void;
|
|
19
|
-
/**
|
|
21
|
+
/**
|
|
22
|
+
* Wait for a specific job to complete - event-driven, no polling
|
|
23
|
+
* Returns true if job completed, false if timeout
|
|
24
|
+
*/
|
|
25
|
+
waitForJobCompletion(jobId: JobId, timeoutMs: number): Promise<boolean>;
|
|
26
|
+
/** Check if broadcast has any listeners - for batch optimizations */
|
|
27
|
+
needsBroadcast(): boolean;
|
|
28
|
+
/** Broadcast event to all subscribers - optimized to skip work when no listeners */
|
|
20
29
|
broadcast(event: Partial<JobEvent> & {
|
|
21
30
|
eventType: EventType;
|
|
22
31
|
queue: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"eventsManager.d.ts","sourceRoot":"","sources":["../../src/application/eventsManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,KAAK,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAIvD,gCAAgC;AAChC,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;AAExD,2BAA2B;AAC3B,qBAAa,aAAa;
|
|
1
|
+
{"version":3,"file":"eventsManager.d.ts","sourceRoot":"","sources":["../../src/application/eventsManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,KAAK,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAIvD,gCAAgC;AAChC,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;AAExD,2BAA2B;AAC3B,qBAAa,aAAa;IAKZ,OAAO,CAAC,QAAQ,CAAC,cAAc;IAJ3C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAyB;IACrD,kFAAkF;IAClF,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAwC;gBAE7C,cAAc,EAAE,cAAc;IAE3D,8BAA8B;IAC9B,SAAS,CAAC,QAAQ,EAAE,eAAe,GAAG,MAAM,IAAI;IAQhD,2CAA2C;IAC3C,KAAK,IAAI,IAAI;IAWb;;;OAGG;IACH,oBAAoB,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IA8BvE,qEAAqE;IACrE,cAAc,IAAI,OAAO;IAQzB,oFAAoF;IACpF,SAAS,CACP,KAAK,EAAE,OAAO,CAAC,QAAQ,CAAC,GAAG;QACzB,SAAS,EAAE,SAAS,CAAC;QACrB,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,KAAK,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,GACA,IAAI;IAuDP,+CAA+C;IAC/C,OAAO,CAAC,iBAAiB;CAgB1B"}
|
|
@@ -7,6 +7,8 @@ import { webhookLog } from '../shared/logger';
|
|
|
7
7
|
export class EventsManager {
|
|
8
8
|
webhookManager;
|
|
9
9
|
subscribers = [];
|
|
10
|
+
/** Waiters for specific job completions - for efficient WaitJob implementation */
|
|
11
|
+
completionWaiters = new Map();
|
|
10
12
|
constructor(webhookManager) {
|
|
11
13
|
this.webhookManager = webhookManager;
|
|
12
14
|
}
|
|
@@ -22,34 +24,102 @@ export class EventsManager {
|
|
|
22
24
|
/** Clear all subscribers (for shutdown) */
|
|
23
25
|
clear() {
|
|
24
26
|
this.subscribers.length = 0;
|
|
27
|
+
// Clear all waiters
|
|
28
|
+
for (const waiters of this.completionWaiters.values()) {
|
|
29
|
+
for (const resolve of waiters) {
|
|
30
|
+
resolve();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
this.completionWaiters.clear();
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Wait for a specific job to complete - event-driven, no polling
|
|
37
|
+
* Returns true if job completed, false if timeout
|
|
38
|
+
*/
|
|
39
|
+
waitForJobCompletion(jobId, timeoutMs) {
|
|
40
|
+
const jobKey = String(jobId);
|
|
41
|
+
return new Promise((resolve) => {
|
|
42
|
+
const timer = setTimeout(() => {
|
|
43
|
+
// Timeout - remove from waiters
|
|
44
|
+
const waiters = this.completionWaiters.get(jobKey);
|
|
45
|
+
if (waiters) {
|
|
46
|
+
const idx = waiters.indexOf(resolveWaiter);
|
|
47
|
+
if (idx !== -1)
|
|
48
|
+
waiters.splice(idx, 1);
|
|
49
|
+
if (waiters.length === 0)
|
|
50
|
+
this.completionWaiters.delete(jobKey);
|
|
51
|
+
}
|
|
52
|
+
resolve(false);
|
|
53
|
+
}, timeoutMs);
|
|
54
|
+
const resolveWaiter = () => {
|
|
55
|
+
clearTimeout(timer);
|
|
56
|
+
resolve(true);
|
|
57
|
+
};
|
|
58
|
+
// Add to waiters
|
|
59
|
+
let waiters = this.completionWaiters.get(jobKey);
|
|
60
|
+
if (!waiters) {
|
|
61
|
+
waiters = [];
|
|
62
|
+
this.completionWaiters.set(jobKey, waiters);
|
|
63
|
+
}
|
|
64
|
+
waiters.push(resolveWaiter);
|
|
65
|
+
});
|
|
25
66
|
}
|
|
26
|
-
/**
|
|
67
|
+
/** Check if broadcast has any listeners - for batch optimizations */
|
|
68
|
+
needsBroadcast() {
|
|
69
|
+
return (this.subscribers.length > 0 ||
|
|
70
|
+
this.webhookManager.hasEnabledWebhooks() ||
|
|
71
|
+
this.completionWaiters.size > 0);
|
|
72
|
+
}
|
|
73
|
+
/** Broadcast event to all subscribers - optimized to skip work when no listeners */
|
|
27
74
|
broadcast(event) {
|
|
75
|
+
const hasSubscribers = this.subscribers.length > 0;
|
|
76
|
+
const hasWebhooks = this.webhookManager.hasEnabledWebhooks();
|
|
77
|
+
const isCompletion = event.eventType === "completed" /* EventType.Completed */;
|
|
78
|
+
const hasWaiters = isCompletion && this.completionWaiters.size > 0;
|
|
79
|
+
// Fast path: nothing to notify
|
|
80
|
+
if (!hasSubscribers && !hasWebhooks && !hasWaiters) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
28
83
|
// Notify subscribers
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
84
|
+
if (hasSubscribers) {
|
|
85
|
+
for (const sub of this.subscribers) {
|
|
86
|
+
try {
|
|
87
|
+
sub(event);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// Ignore subscriber errors
|
|
91
|
+
}
|
|
32
92
|
}
|
|
33
|
-
|
|
34
|
-
|
|
93
|
+
}
|
|
94
|
+
// Notify completion waiters for WaitJob - O(1) lookup
|
|
95
|
+
if (hasWaiters) {
|
|
96
|
+
const jobKey = String(event.jobId);
|
|
97
|
+
const waiters = this.completionWaiters.get(jobKey);
|
|
98
|
+
if (waiters) {
|
|
99
|
+
this.completionWaiters.delete(jobKey);
|
|
100
|
+
for (const resolve of waiters) {
|
|
101
|
+
resolve();
|
|
102
|
+
}
|
|
35
103
|
}
|
|
36
104
|
}
|
|
37
|
-
// Trigger webhooks
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
.
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
105
|
+
// Trigger webhooks - only if there are enabled webhooks
|
|
106
|
+
if (hasWebhooks) {
|
|
107
|
+
const webhookEvent = this.mapEventToWebhook(event.eventType);
|
|
108
|
+
if (webhookEvent) {
|
|
109
|
+
this.webhookManager
|
|
110
|
+
.trigger(webhookEvent, String(event.jobId), event.queue, {
|
|
111
|
+
data: event.data,
|
|
112
|
+
error: event.error,
|
|
113
|
+
})
|
|
114
|
+
.catch((err) => {
|
|
115
|
+
webhookLog.error('Webhook trigger failed', {
|
|
116
|
+
event: webhookEvent,
|
|
117
|
+
jobId: String(event.jobId),
|
|
118
|
+
queue: event.queue,
|
|
119
|
+
error: String(err),
|
|
120
|
+
});
|
|
51
121
|
});
|
|
52
|
-
}
|
|
122
|
+
}
|
|
53
123
|
}
|
|
54
124
|
}
|
|
55
125
|
/** Map internal event type to webhook event */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"eventsManager.js","sourceRoot":"","sources":["../../src/application/eventsManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAK9C,2BAA2B;AAC3B,MAAM,OAAO,aAAa;
|
|
1
|
+
{"version":3,"file":"eventsManager.js","sourceRoot":"","sources":["../../src/application/eventsManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -33,6 +33,12 @@ export interface AckContext {
|
|
|
33
33
|
error?: string;
|
|
34
34
|
}) => void;
|
|
35
35
|
onJobCompleted: (jobId: JobId) => void;
|
|
36
|
+
/** Batch notify completions - more efficient than per-job calls */
|
|
37
|
+
onJobsCompleted?: (jobIds: JobId[]) => void;
|
|
38
|
+
/** Fast check if broadcast is needed - avoids function call overhead */
|
|
39
|
+
needsBroadcast?: () => boolean;
|
|
40
|
+
/** Check if any jobs are waiting for dependencies */
|
|
41
|
+
hasPendingDeps?: () => boolean;
|
|
36
42
|
}
|
|
37
43
|
/**
|
|
38
44
|
* Acknowledge job completion
|
|
@@ -43,7 +49,16 @@ export declare function ackJob(jobId: JobId, result: unknown, ctx: AckContext):
|
|
|
43
49
|
*/
|
|
44
50
|
export declare function failJob(jobId: JobId, error: string | undefined, ctx: AckContext): Promise<void>;
|
|
45
51
|
/**
|
|
46
|
-
* Acknowledge multiple jobs
|
|
52
|
+
* Acknowledge multiple jobs - optimized batch processing
|
|
53
|
+
* Groups jobs by shard to minimize lock acquisitions: O(shards) instead of O(n)
|
|
47
54
|
*/
|
|
48
55
|
export declare function ackJobBatch(jobIds: JobId[], ctx: AckContext): Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* Acknowledge multiple jobs with individual results - optimized batch processing
|
|
58
|
+
* Same as ackJobBatch but supports passing result data for each job
|
|
59
|
+
*/
|
|
60
|
+
export declare function ackJobBatchWithResults(items: Array<{
|
|
61
|
+
id: JobId;
|
|
62
|
+
result: unknown;
|
|
63
|
+
}>, ctx: AckContext): Promise<void>;
|
|
49
64
|
//# sourceMappingURL=ack.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ack.d.ts","sourceRoot":"","sources":["../../../src/application/operations/ack.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,KAAK,GAAG,EAAE,KAAK,KAAK,EAA8B,MAAM,wBAAwB,CAAC;AAC1F,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AACvE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAGhD,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAEzD,4BAA4B;AAC5B,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,aAAa,GAAG,IAAI,CAAC;IAC9B,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,gBAAgB,EAAE,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,CAAC;IACpC,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,aAAa,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IAC9B,UAAU,EAAE,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACpC,QAAQ,EAAE,GAAG,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;IAClC,cAAc,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAClC,WAAW,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/B,SAAS,EAAE,CAAC,KAAK,EAAE;QACjB,SAAS,EAAE,SAAS,CAAC;QACrB,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,KAAK,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,IAAI,CAAC,EAAE,OAAO,CAAC;QACf,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,KAAK,IAAI,CAAC;IACX,cAAc,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"ack.d.ts","sourceRoot":"","sources":["../../../src/application/operations/ack.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,KAAK,GAAG,EAAE,KAAK,KAAK,EAA8B,MAAM,wBAAwB,CAAC;AAC1F,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AACvE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAGhD,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAEzD,4BAA4B;AAC5B,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,aAAa,GAAG,IAAI,CAAC;IAC9B,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,gBAAgB,EAAE,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,CAAC;IACpC,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,aAAa,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IAC9B,UAAU,EAAE,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACpC,QAAQ,EAAE,GAAG,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;IAClC,cAAc,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAClC,WAAW,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/B,SAAS,EAAE,CAAC,KAAK,EAAE;QACjB,SAAS,EAAE,SAAS,CAAC;QACrB,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,KAAK,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,IAAI,CAAC,EAAE,OAAO,CAAC;QACf,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,KAAK,IAAI,CAAC;IACX,cAAc,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACvC,mEAAmE;IACnE,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,IAAI,CAAC;IAC5C,wEAAwE;IACxE,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC;IAC/B,qDAAqD;IACrD,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC;CAChC;AAED;;GAEG;AACH,wBAAsB,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAgD1F;AAED;;GAEG;AACH,wBAAsB,OAAO,CAC3B,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,GAAG,EAAE,UAAU,GACd,OAAO,CAAC,IAAI,CAAC,CAuDf;AAKD;;;GAGG;AAEH,wBAAsB,WAAW,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CA8JjF;AAED;;;GAGG;AAEH,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,CAAC,EAC5C,GAAG,EAAE,UAAU,GACd,OAAO,CAAC,IAAI,CAAC,CA2If"}
|
|
@@ -76,8 +76,11 @@ export async function failJob(jobId, error, ctx) {
|
|
|
76
76
|
shard.releaseJobResources(job.queue, job.uniqueKey, job.groupId);
|
|
77
77
|
if (canRetry(job)) {
|
|
78
78
|
// Retry with exponential backoff
|
|
79
|
-
|
|
79
|
+
const now = Date.now();
|
|
80
|
+
job.runAt = now + calculateBackoff(job);
|
|
80
81
|
shard.getQueue(job.queue).push(job);
|
|
82
|
+
// Update running counters for O(1) stats (retry jobs are always delayed)
|
|
83
|
+
shard.incrementQueued(jobId, true, job.createdAt, job.queue);
|
|
81
84
|
ctx.jobIndex.set(jobId, { type: 'queue', shardIdx: idx, queueName: job.queue });
|
|
82
85
|
ctx.storage?.updateForRetry(job);
|
|
83
86
|
}
|
|
@@ -88,7 +91,7 @@ export async function failJob(jobId, error, ctx) {
|
|
|
88
91
|
ctx.totalFailed.value++;
|
|
89
92
|
}
|
|
90
93
|
else {
|
|
91
|
-
// Move to DLQ
|
|
94
|
+
// Move to DLQ (addToDlq already updates dlq counter)
|
|
92
95
|
shard.addToDlq(job);
|
|
93
96
|
ctx.jobIndex.set(jobId, { type: 'dlq', queueName: job.queue });
|
|
94
97
|
ctx.storage?.markFailed(job, error ?? null);
|
|
@@ -104,12 +107,273 @@ export async function failJob(jobId, error, ctx) {
|
|
|
104
107
|
error,
|
|
105
108
|
});
|
|
106
109
|
}
|
|
110
|
+
/** Debug flag for ackBatch timing */
|
|
111
|
+
const DEBUG_ACK_BATCH = process.env.DEBUG_ACK_BATCH === '1';
|
|
107
112
|
/**
|
|
108
|
-
* Acknowledge multiple jobs
|
|
113
|
+
* Acknowledge multiple jobs - optimized batch processing
|
|
114
|
+
* Groups jobs by shard to minimize lock acquisitions: O(shards) instead of O(n)
|
|
109
115
|
*/
|
|
116
|
+
// eslint-disable-next-line complexity
|
|
110
117
|
export async function ackJobBatch(jobIds, ctx) {
|
|
111
|
-
|
|
112
|
-
|
|
118
|
+
if (jobIds.length === 0)
|
|
119
|
+
return;
|
|
120
|
+
// Small batches - use parallel individual acks (overhead of grouping not worth it)
|
|
121
|
+
if (jobIds.length <= 4) {
|
|
122
|
+
await Promise.all(jobIds.map((id) => ackJob(id, undefined, ctx)));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const t0 = DEBUG_ACK_BATCH ? performance.now() : 0;
|
|
126
|
+
// Step 1: Group jobs by processing shard
|
|
127
|
+
const byProcShard = new Map();
|
|
128
|
+
for (const jobId of jobIds) {
|
|
129
|
+
const procIdx = processingShardIndex(jobId);
|
|
130
|
+
let group = byProcShard.get(procIdx);
|
|
131
|
+
if (!group) {
|
|
132
|
+
group = [];
|
|
133
|
+
byProcShard.set(procIdx, group);
|
|
134
|
+
}
|
|
135
|
+
group.push(jobId);
|
|
136
|
+
}
|
|
137
|
+
const t1 = DEBUG_ACK_BATCH ? performance.now() : 0;
|
|
138
|
+
// Step 2: Extract all jobs from processing shards (one lock per shard)
|
|
139
|
+
// Use arrays instead of Map for faster iteration in step 5
|
|
140
|
+
const extractedJobs = [];
|
|
141
|
+
await Promise.all(Array.from(byProcShard.entries()).map(async ([procIdx, ids]) => {
|
|
142
|
+
await withWriteLock(ctx.processingLocks[procIdx], () => {
|
|
143
|
+
for (const jobId of ids) {
|
|
144
|
+
const job = ctx.processingShards[procIdx].get(jobId);
|
|
145
|
+
if (job) {
|
|
146
|
+
ctx.processingShards[procIdx].delete(jobId);
|
|
147
|
+
extractedJobs.push({ id: jobId, job });
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}));
|
|
152
|
+
const t2 = DEBUG_ACK_BATCH ? performance.now() : 0;
|
|
153
|
+
// Step 3: Group extracted jobs by queue shard
|
|
154
|
+
const byQueueShard = new Map();
|
|
155
|
+
for (let i = 0; i < extractedJobs.length; i++) {
|
|
156
|
+
const job = extractedJobs[i].job;
|
|
157
|
+
const idx = shardIndex(job.queue);
|
|
158
|
+
let group = byQueueShard.get(idx);
|
|
159
|
+
if (!group) {
|
|
160
|
+
group = [];
|
|
161
|
+
byQueueShard.set(idx, group);
|
|
162
|
+
}
|
|
163
|
+
group.push(job);
|
|
164
|
+
}
|
|
165
|
+
const t3 = DEBUG_ACK_BATCH ? performance.now() : 0;
|
|
166
|
+
// Step 4: Release resources for each queue shard (one lock per shard)
|
|
167
|
+
await Promise.all(Array.from(byQueueShard.entries()).map(async ([idx, jobs]) => {
|
|
168
|
+
await withWriteLock(ctx.shardLocks[idx], () => {
|
|
169
|
+
const shard = ctx.shards[idx];
|
|
170
|
+
for (const job of jobs) {
|
|
171
|
+
shard.releaseJobResources(job.queue, job.uniqueKey, job.groupId);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}));
|
|
175
|
+
const t4 = DEBUG_ACK_BATCH ? performance.now() : 0;
|
|
176
|
+
// Step 5: Update indexes and metrics (no locks needed - single-threaded)
|
|
177
|
+
// Optimized: use indexed for loop (faster than for-of), minimize function calls
|
|
178
|
+
const now = Date.now();
|
|
179
|
+
const completedLocation = { type: 'completed' };
|
|
180
|
+
const storage = ctx.storage;
|
|
181
|
+
const hasStorage = storage !== null;
|
|
182
|
+
const jobCount = extractedJobs.length;
|
|
183
|
+
// Pre-check flags (check once, not per job)
|
|
184
|
+
const needsBroadcast = ctx.needsBroadcast?.() ?? true;
|
|
185
|
+
const hasPendingDeps = ctx.hasPendingDeps?.() ?? true;
|
|
186
|
+
const t5a = DEBUG_ACK_BATCH ? performance.now() : 0;
|
|
187
|
+
// Batch counter update (single BigInt operation instead of N increments)
|
|
188
|
+
ctx.totalCompleted.value += BigInt(jobCount);
|
|
189
|
+
const t5b = DEBUG_ACK_BATCH ? performance.now() : 0;
|
|
190
|
+
// Main loop - indexed for loop is faster than for-of on arrays
|
|
191
|
+
for (let i = 0; i < jobCount; i++) {
|
|
192
|
+
const { id: jobId, job } = extractedJobs[i];
|
|
193
|
+
if (!job.removeOnComplete) {
|
|
194
|
+
ctx.completedJobs.add(jobId);
|
|
195
|
+
ctx.jobIndex.set(jobId, completedLocation);
|
|
196
|
+
if (hasStorage)
|
|
197
|
+
storage.markCompleted(jobId, now);
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
ctx.jobIndex.delete(jobId);
|
|
201
|
+
if (hasStorage)
|
|
202
|
+
storage.deleteJob(jobId);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const t5c = DEBUG_ACK_BATCH ? performance.now() : 0;
|
|
206
|
+
// Broadcast events (only if listeners exist)
|
|
207
|
+
if (needsBroadcast) {
|
|
208
|
+
const event = {
|
|
209
|
+
eventType: 'completed',
|
|
210
|
+
queue: '',
|
|
211
|
+
jobId: '',
|
|
212
|
+
timestamp: now,
|
|
213
|
+
};
|
|
214
|
+
for (let i = 0; i < jobCount; i++) {
|
|
215
|
+
const { id: jobId, job } = extractedJobs[i];
|
|
216
|
+
event.queue = job.queue;
|
|
217
|
+
event.jobId = jobId;
|
|
218
|
+
ctx.broadcast(event);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
const t5d = DEBUG_ACK_BATCH ? performance.now() : 0;
|
|
222
|
+
// Batch notify completions (for dependency checking)
|
|
223
|
+
if (hasPendingDeps && ctx.onJobsCompleted) {
|
|
224
|
+
const completedIds = extractedJobs.map((e) => e.id);
|
|
225
|
+
ctx.onJobsCompleted(completedIds);
|
|
226
|
+
}
|
|
227
|
+
else if (hasPendingDeps) {
|
|
228
|
+
for (let i = 0; i < jobCount; i++) {
|
|
229
|
+
ctx.onJobCompleted(extractedJobs[i].id);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
const t5 = DEBUG_ACK_BATCH ? performance.now() : 0;
|
|
233
|
+
if (DEBUG_ACK_BATCH) {
|
|
234
|
+
console.log(` Step 5 details:`);
|
|
235
|
+
console.log(` 5a checks: ${(t5b - t5a).toFixed(2)}ms`);
|
|
236
|
+
console.log(` 5b main loop: ${(t5c - t5b).toFixed(2)}ms`);
|
|
237
|
+
console.log(` 5c broadcast: ${(t5d - t5c).toFixed(2)}ms (needsBroadcast=${needsBroadcast})`);
|
|
238
|
+
console.log(` 5d deps: ${(t5 - t5d).toFixed(2)}ms (hasPendingDeps=${hasPendingDeps})`);
|
|
239
|
+
}
|
|
240
|
+
if (DEBUG_ACK_BATCH) {
|
|
241
|
+
console.log(`ackBatch timing (${jobIds.length} jobs):`);
|
|
242
|
+
console.log(` Step 1 (group proc): ${(t1 - t0).toFixed(2)}ms`);
|
|
243
|
+
console.log(` Step 2 (extract): ${(t2 - t1).toFixed(2)}ms`);
|
|
244
|
+
console.log(` Step 3 (group queue): ${(t3 - t2).toFixed(2)}ms`);
|
|
245
|
+
console.log(` Step 4 (release): ${(t4 - t3).toFixed(2)}ms`);
|
|
246
|
+
console.log(` Step 5 (update idx): ${(t5 - t4).toFixed(2)}ms`);
|
|
247
|
+
console.log(` Total: ${(t5 - t0).toFixed(2)}ms`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Acknowledge multiple jobs with individual results - optimized batch processing
|
|
252
|
+
* Same as ackJobBatch but supports passing result data for each job
|
|
253
|
+
*/
|
|
254
|
+
// eslint-disable-next-line complexity
|
|
255
|
+
export async function ackJobBatchWithResults(items, ctx) {
|
|
256
|
+
if (items.length === 0)
|
|
257
|
+
return;
|
|
258
|
+
// Small batches - use parallel individual acks
|
|
259
|
+
if (items.length <= 4) {
|
|
260
|
+
await Promise.all(items.map((item) => ackJob(item.id, item.result, ctx)));
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
const t0 = DEBUG_ACK_BATCH ? performance.now() : 0;
|
|
264
|
+
// Step 1: Group jobs by processing shard
|
|
265
|
+
const byProcShard = new Map();
|
|
266
|
+
for (const item of items) {
|
|
267
|
+
const procIdx = processingShardIndex(item.id);
|
|
268
|
+
let group = byProcShard.get(procIdx);
|
|
269
|
+
if (!group) {
|
|
270
|
+
group = [];
|
|
271
|
+
byProcShard.set(procIdx, group);
|
|
272
|
+
}
|
|
273
|
+
group.push(item);
|
|
274
|
+
}
|
|
275
|
+
const t1 = DEBUG_ACK_BATCH ? performance.now() : 0;
|
|
276
|
+
// Step 2: Extract all jobs from processing shards (one lock per shard)
|
|
277
|
+
// Keep result with each extracted job
|
|
278
|
+
const extractedJobs = [];
|
|
279
|
+
await Promise.all(Array.from(byProcShard.entries()).map(async ([procIdx, itemsInShard]) => {
|
|
280
|
+
await withWriteLock(ctx.processingLocks[procIdx], () => {
|
|
281
|
+
for (const item of itemsInShard) {
|
|
282
|
+
const job = ctx.processingShards[procIdx].get(item.id);
|
|
283
|
+
if (job) {
|
|
284
|
+
ctx.processingShards[procIdx].delete(item.id);
|
|
285
|
+
extractedJobs.push({ id: item.id, job, result: item.result });
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
}));
|
|
290
|
+
const t2 = DEBUG_ACK_BATCH ? performance.now() : 0;
|
|
291
|
+
// Step 3: Group extracted jobs by queue shard
|
|
292
|
+
const byQueueShard = new Map();
|
|
293
|
+
for (let i = 0; i < extractedJobs.length; i++) {
|
|
294
|
+
const job = extractedJobs[i].job;
|
|
295
|
+
const idx = shardIndex(job.queue);
|
|
296
|
+
let group = byQueueShard.get(idx);
|
|
297
|
+
if (!group) {
|
|
298
|
+
group = [];
|
|
299
|
+
byQueueShard.set(idx, group);
|
|
300
|
+
}
|
|
301
|
+
group.push(job);
|
|
302
|
+
}
|
|
303
|
+
const t3 = DEBUG_ACK_BATCH ? performance.now() : 0;
|
|
304
|
+
// Step 4: Release resources for each queue shard (one lock per shard)
|
|
305
|
+
await Promise.all(Array.from(byQueueShard.entries()).map(async ([idx, jobs]) => {
|
|
306
|
+
await withWriteLock(ctx.shardLocks[idx], () => {
|
|
307
|
+
const shard = ctx.shards[idx];
|
|
308
|
+
for (const job of jobs) {
|
|
309
|
+
shard.releaseJobResources(job.queue, job.uniqueKey, job.groupId);
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
}));
|
|
313
|
+
const t4 = DEBUG_ACK_BATCH ? performance.now() : 0;
|
|
314
|
+
// Step 5: Update indexes, store results, and metrics
|
|
315
|
+
const now = Date.now();
|
|
316
|
+
const completedLocation = { type: 'completed' };
|
|
317
|
+
const storage = ctx.storage;
|
|
318
|
+
const hasStorage = storage !== null;
|
|
319
|
+
const jobCount = extractedJobs.length;
|
|
320
|
+
// Pre-check flags
|
|
321
|
+
const needsBroadcast = ctx.needsBroadcast?.() ?? true;
|
|
322
|
+
const hasPendingDeps = ctx.hasPendingDeps?.() ?? true;
|
|
323
|
+
// Batch counter update
|
|
324
|
+
ctx.totalCompleted.value += BigInt(jobCount);
|
|
325
|
+
// Main loop with result storage
|
|
326
|
+
for (let i = 0; i < jobCount; i++) {
|
|
327
|
+
const { id: jobId, job, result } = extractedJobs[i];
|
|
328
|
+
if (!job.removeOnComplete) {
|
|
329
|
+
ctx.completedJobs.add(jobId);
|
|
330
|
+
if (result !== undefined) {
|
|
331
|
+
ctx.jobResults.set(jobId, result);
|
|
332
|
+
if (hasStorage)
|
|
333
|
+
storage.storeResult(jobId, result);
|
|
334
|
+
}
|
|
335
|
+
ctx.jobIndex.set(jobId, completedLocation);
|
|
336
|
+
if (hasStorage)
|
|
337
|
+
storage.markCompleted(jobId, now);
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
ctx.jobIndex.delete(jobId);
|
|
341
|
+
if (hasStorage)
|
|
342
|
+
storage.deleteJob(jobId);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
// Broadcast events with results (only if listeners exist)
|
|
346
|
+
if (needsBroadcast) {
|
|
347
|
+
for (let i = 0; i < jobCount; i++) {
|
|
348
|
+
const { id: jobId, job, result } = extractedJobs[i];
|
|
349
|
+
ctx.broadcast({
|
|
350
|
+
eventType: 'completed',
|
|
351
|
+
queue: job.queue,
|
|
352
|
+
jobId,
|
|
353
|
+
timestamp: now,
|
|
354
|
+
data: result,
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
// Batch notify completions (for dependency checking)
|
|
359
|
+
if (hasPendingDeps && ctx.onJobsCompleted) {
|
|
360
|
+
const completedIds = extractedJobs.map((e) => e.id);
|
|
361
|
+
ctx.onJobsCompleted(completedIds);
|
|
362
|
+
}
|
|
363
|
+
else if (hasPendingDeps) {
|
|
364
|
+
for (let i = 0; i < jobCount; i++) {
|
|
365
|
+
ctx.onJobCompleted(extractedJobs[i].id);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
const t5 = DEBUG_ACK_BATCH ? performance.now() : 0;
|
|
369
|
+
if (DEBUG_ACK_BATCH) {
|
|
370
|
+
console.log(`ackBatchWithResults timing (${items.length} jobs):`);
|
|
371
|
+
console.log(` Step 1 (group proc): ${(t1 - t0).toFixed(2)}ms`);
|
|
372
|
+
console.log(` Step 2 (extract): ${(t2 - t1).toFixed(2)}ms`);
|
|
373
|
+
console.log(` Step 3 (group queue): ${(t3 - t2).toFixed(2)}ms`);
|
|
374
|
+
console.log(` Step 4 (release): ${(t4 - t3).toFixed(2)}ms`);
|
|
375
|
+
console.log(` Step 5 (update+store): ${(t5 - t4).toFixed(2)}ms`);
|
|
376
|
+
console.log(` Total: ${(t5 - t0).toFixed(2)}ms`);
|
|
113
377
|
}
|
|
114
378
|
}
|
|
115
379
|
//# sourceMappingURL=ack.js.map
|