@trigger.dev/redis-worker 4.5.0-rc.7 → 4.5.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/dist/index.cjs +368 -337
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +460 -443
- package/dist/index.d.ts +460 -443
- package/dist/index.js +368 -337
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -9053,7 +9053,7 @@ var require_Redis = __commonJS({
|
|
|
9053
9053
|
var lodash_1 = require_lodash3();
|
|
9054
9054
|
var Deque = require_denque();
|
|
9055
9055
|
var debug = (0, utils_1.Debug)("redis");
|
|
9056
|
-
var
|
|
9056
|
+
var Redis3 = class _Redis extends Commander_1.default {
|
|
9057
9057
|
constructor(arg1, arg2, arg3) {
|
|
9058
9058
|
super();
|
|
9059
9059
|
this.status = "wait";
|
|
@@ -9632,12 +9632,12 @@ var require_Redis = __commonJS({
|
|
|
9632
9632
|
}).catch(lodash_1.noop);
|
|
9633
9633
|
}
|
|
9634
9634
|
};
|
|
9635
|
-
|
|
9636
|
-
|
|
9637
|
-
|
|
9638
|
-
(0, applyMixin_1.default)(
|
|
9639
|
-
(0, transaction_1.addTransactionSupport)(
|
|
9640
|
-
exports$1.default =
|
|
9635
|
+
Redis3.Cluster = cluster_1.default;
|
|
9636
|
+
Redis3.Command = Command_1.default;
|
|
9637
|
+
Redis3.defaultOptions = RedisOptions_1.DEFAULT_REDIS_OPTIONS;
|
|
9638
|
+
(0, applyMixin_1.default)(Redis3, events_1.EventEmitter);
|
|
9639
|
+
(0, transaction_1.addTransactionSupport)(Redis3.prototype);
|
|
9640
|
+
exports$1.default = Redis3;
|
|
9641
9641
|
}
|
|
9642
9642
|
});
|
|
9643
9643
|
|
|
@@ -9985,6 +9985,37 @@ var SimpleQueue = class {
|
|
|
9985
9985
|
throw e;
|
|
9986
9986
|
}
|
|
9987
9987
|
}
|
|
9988
|
+
/**
|
|
9989
|
+
* Age (in ms) of the oldest *overdue* message — the oldest item whose scheduled
|
|
9990
|
+
* time has already passed (score <= now). Returns 0 when the queue is empty or
|
|
9991
|
+
* only holds future/delayed or in-flight (future-scored) items.
|
|
9992
|
+
*
|
|
9993
|
+
* Resolves the candidate against the `items` hash so orphaned `queue` entries
|
|
9994
|
+
* (a member whose payload is missing — the same stale state `dequeueItems`
|
|
9995
|
+
* cleans up) don't report a phantom stall for work that can't be dequeued. The
|
|
9996
|
+
* Lua scans due items oldest-first and returns the first score whose payload
|
|
9997
|
+
* still exists.
|
|
9998
|
+
*
|
|
9999
|
+
* This is the generic stall signal: it stays at 0 while a queue drains healthily
|
|
10000
|
+
* and rises only when due work sits undrained (poison block, dead consumer,
|
|
10001
|
+
* backpressure).
|
|
10002
|
+
*/
|
|
10003
|
+
async oldestMessageAge() {
|
|
10004
|
+
try {
|
|
10005
|
+
const now = Date.now();
|
|
10006
|
+
const score = Number(await this.redis.getOldestDueScore(`queue`, `items`, now));
|
|
10007
|
+
if (!Number.isFinite(score) || score < 0) {
|
|
10008
|
+
return 0;
|
|
10009
|
+
}
|
|
10010
|
+
return Math.max(0, now - score);
|
|
10011
|
+
} catch (e) {
|
|
10012
|
+
this.logger.error(`SimpleQueue ${this.name}.oldestMessageAge(): error getting oldest age`, {
|
|
10013
|
+
queue: this.name,
|
|
10014
|
+
error: e
|
|
10015
|
+
});
|
|
10016
|
+
return 0;
|
|
10017
|
+
}
|
|
10018
|
+
}
|
|
9988
10019
|
async getJob(id) {
|
|
9989
10020
|
const result = await this.redis.getJob(`queue`, `items`, id);
|
|
9990
10021
|
if (!result) {
|
|
@@ -10151,6 +10182,29 @@ var SimpleQueue = class {
|
|
|
10151
10182
|
return dequeued
|
|
10152
10183
|
`
|
|
10153
10184
|
});
|
|
10185
|
+
this.redis.defineCommand("getOldestDueScore", {
|
|
10186
|
+
numberOfKeys: 2,
|
|
10187
|
+
lua: `
|
|
10188
|
+
local queue = KEYS[1]
|
|
10189
|
+
local items = KEYS[2]
|
|
10190
|
+
local now = tonumber(ARGV[1])
|
|
10191
|
+
|
|
10192
|
+
-- Oldest-first scan of due items, bounded so a long prefix of orphans can't
|
|
10193
|
+
-- make this O(n). Orphans are rare (dequeueItems removes them), so in the
|
|
10194
|
+
-- common case this returns on the first iteration. Read-only: unlike
|
|
10195
|
+
-- dequeueItems we don't ZREM orphans here \u2014 a metric probe must not mutate.
|
|
10196
|
+
local result = redis.call('ZRANGEBYSCORE', queue, '-inf', now, 'WITHSCORES', 'LIMIT', 0, 100)
|
|
10197
|
+
|
|
10198
|
+
for i = 1, #result, 2 do
|
|
10199
|
+
local id = result[i]
|
|
10200
|
+
if redis.call('HEXISTS', items, id) == 1 then
|
|
10201
|
+
return result[i + 1]
|
|
10202
|
+
end
|
|
10203
|
+
end
|
|
10204
|
+
|
|
10205
|
+
return -1
|
|
10206
|
+
`
|
|
10207
|
+
});
|
|
10154
10208
|
this.redis.defineCommand("getJob", {
|
|
10155
10209
|
numberOfKeys: 2,
|
|
10156
10210
|
lua: `
|
|
@@ -11395,6 +11449,15 @@ var Worker = class _Worker {
|
|
|
11395
11449
|
concurrencyLimitPendingObservableGauge.addCallback(
|
|
11396
11450
|
this.#updateConcurrencyLimitPendingMetric.bind(this)
|
|
11397
11451
|
);
|
|
11452
|
+
const oldestMessageAgeObservableGauge = this.meter.createObservableGauge(
|
|
11453
|
+
"redis_worker.queue.oldest_message_age",
|
|
11454
|
+
{
|
|
11455
|
+
description: "Age of the oldest overdue message in the queue",
|
|
11456
|
+
unit: "ms",
|
|
11457
|
+
valueType: ValueType.INT
|
|
11458
|
+
}
|
|
11459
|
+
);
|
|
11460
|
+
oldestMessageAgeObservableGauge.addCallback(this.#updateOldestMessageAgeMetric.bind(this));
|
|
11398
11461
|
}
|
|
11399
11462
|
subscriber;
|
|
11400
11463
|
tracer;
|
|
@@ -11423,6 +11486,12 @@ var Worker = class _Worker {
|
|
|
11423
11486
|
worker_name: this.options.name
|
|
11424
11487
|
});
|
|
11425
11488
|
}
|
|
11489
|
+
async #updateOldestMessageAgeMetric(observableResult) {
|
|
11490
|
+
const oldestMessageAge = await this.queue.oldestMessageAge();
|
|
11491
|
+
observableResult.observe(oldestMessageAge, {
|
|
11492
|
+
worker_name: this.options.name
|
|
11493
|
+
});
|
|
11494
|
+
}
|
|
11426
11495
|
async #updateConcurrencyLimitActiveMetric(observableResult) {
|
|
11427
11496
|
for (const [workerId, limiter] of Object.entries(this.limiters)) {
|
|
11428
11497
|
observableResult.observe(limiter.activeCount, {
|
|
@@ -11716,15 +11785,15 @@ var Worker = class _Worker {
|
|
|
11716
11785
|
await this.flushBatch(queueItem.job, workerId, limiter);
|
|
11717
11786
|
}
|
|
11718
11787
|
} else {
|
|
11719
|
-
limiter(
|
|
11720
|
-
() =>
|
|
11721
|
-
|
|
11722
|
-
|
|
11723
|
-
|
|
11724
|
-
|
|
11725
|
-
|
|
11726
|
-
}
|
|
11727
|
-
|
|
11788
|
+
limiter(() => this.processItem(queueItem, items.length, workerId, limiter)).catch(
|
|
11789
|
+
(err) => {
|
|
11790
|
+
this.logger.error("Unhandled error in processItem:", {
|
|
11791
|
+
error: err,
|
|
11792
|
+
workerId,
|
|
11793
|
+
item
|
|
11794
|
+
});
|
|
11795
|
+
}
|
|
11796
|
+
);
|
|
11728
11797
|
}
|
|
11729
11798
|
}
|
|
11730
11799
|
} catch (error) {
|
|
@@ -11874,13 +11943,25 @@ var Worker = class _Worker {
|
|
|
11874
11943
|
attempt: newAttempt
|
|
11875
11944
|
};
|
|
11876
11945
|
if (!shouldLogError) {
|
|
11877
|
-
this.logger.info(
|
|
11946
|
+
this.logger.info(
|
|
11947
|
+
`Worker batch item reached max attempts. Moving to DLQ.`,
|
|
11948
|
+
dlqLogAttributes
|
|
11949
|
+
);
|
|
11878
11950
|
} else if (errorLogLevel === "warn") {
|
|
11879
|
-
this.logger.warn(
|
|
11951
|
+
this.logger.warn(
|
|
11952
|
+
`Worker batch item reached max attempts. Moving to DLQ.`,
|
|
11953
|
+
dlqLogAttributes
|
|
11954
|
+
);
|
|
11880
11955
|
} else if (errorLogLevel === "info") {
|
|
11881
|
-
this.logger.info(
|
|
11956
|
+
this.logger.info(
|
|
11957
|
+
`Worker batch item reached max attempts. Moving to DLQ.`,
|
|
11958
|
+
dlqLogAttributes
|
|
11959
|
+
);
|
|
11882
11960
|
} else {
|
|
11883
|
-
this.logger.error(
|
|
11961
|
+
this.logger.error(
|
|
11962
|
+
`Worker batch item reached max attempts. Moving to DLQ.`,
|
|
11963
|
+
dlqLogAttributes
|
|
11964
|
+
);
|
|
11884
11965
|
}
|
|
11885
11966
|
await this.queue.moveToDeadLetterQueue(item.id, errorMessage);
|
|
11886
11967
|
continue;
|
|
@@ -12637,135 +12718,6 @@ return 1
|
|
|
12637
12718
|
});
|
|
12638
12719
|
}
|
|
12639
12720
|
};
|
|
12640
|
-
var TenantDispatch = class {
|
|
12641
|
-
constructor(options) {
|
|
12642
|
-
this.options = options;
|
|
12643
|
-
this.redis = createRedisClient(options.redis);
|
|
12644
|
-
this.keys = options.keys;
|
|
12645
|
-
this.shardCount = Math.max(1, options.shardCount);
|
|
12646
|
-
}
|
|
12647
|
-
redis;
|
|
12648
|
-
keys;
|
|
12649
|
-
shardCount;
|
|
12650
|
-
/**
|
|
12651
|
-
* Get the dispatch shard ID for a tenant.
|
|
12652
|
-
* Uses jump consistent hash on the tenant ID so each tenant
|
|
12653
|
-
* always maps to exactly one dispatch shard.
|
|
12654
|
-
*/
|
|
12655
|
-
getShardForTenant(tenantId) {
|
|
12656
|
-
return jumpHash(tenantId, this.shardCount);
|
|
12657
|
-
}
|
|
12658
|
-
/**
|
|
12659
|
-
* Get eligible tenants from a dispatch shard (Level 1).
|
|
12660
|
-
* Returns tenants ordered by oldest message (lowest score first).
|
|
12661
|
-
*/
|
|
12662
|
-
async getTenantsFromShard(shardId, limit = 1e3, maxScore) {
|
|
12663
|
-
const dispatchKey = this.keys.dispatchKey(shardId);
|
|
12664
|
-
const score = maxScore ?? Date.now();
|
|
12665
|
-
const results = await this.redis.zrangebyscore(
|
|
12666
|
-
dispatchKey,
|
|
12667
|
-
"-inf",
|
|
12668
|
-
score,
|
|
12669
|
-
"WITHSCORES",
|
|
12670
|
-
"LIMIT",
|
|
12671
|
-
0,
|
|
12672
|
-
limit
|
|
12673
|
-
);
|
|
12674
|
-
const tenants = [];
|
|
12675
|
-
for (let i = 0; i < results.length; i += 2) {
|
|
12676
|
-
const tenantId = results[i];
|
|
12677
|
-
const scoreStr = results[i + 1];
|
|
12678
|
-
if (tenantId && scoreStr) {
|
|
12679
|
-
tenants.push({
|
|
12680
|
-
tenantId,
|
|
12681
|
-
score: parseFloat(scoreStr)
|
|
12682
|
-
});
|
|
12683
|
-
}
|
|
12684
|
-
}
|
|
12685
|
-
return tenants;
|
|
12686
|
-
}
|
|
12687
|
-
/**
|
|
12688
|
-
* Get queues for a specific tenant (Level 2).
|
|
12689
|
-
* Returns queues ordered by oldest message (lowest score first).
|
|
12690
|
-
*/
|
|
12691
|
-
async getQueuesForTenant(tenantId, limit = 1e3, maxScore) {
|
|
12692
|
-
const tenantQueueKey = this.keys.tenantQueueIndexKey(tenantId);
|
|
12693
|
-
const score = maxScore ?? Date.now();
|
|
12694
|
-
const results = await this.redis.zrangebyscore(
|
|
12695
|
-
tenantQueueKey,
|
|
12696
|
-
"-inf",
|
|
12697
|
-
score,
|
|
12698
|
-
"WITHSCORES",
|
|
12699
|
-
"LIMIT",
|
|
12700
|
-
0,
|
|
12701
|
-
limit
|
|
12702
|
-
);
|
|
12703
|
-
const queues = [];
|
|
12704
|
-
for (let i = 0; i < results.length; i += 2) {
|
|
12705
|
-
const queueId = results[i];
|
|
12706
|
-
const scoreStr = results[i + 1];
|
|
12707
|
-
if (queueId && scoreStr) {
|
|
12708
|
-
queues.push({
|
|
12709
|
-
queueId,
|
|
12710
|
-
score: parseFloat(scoreStr),
|
|
12711
|
-
tenantId
|
|
12712
|
-
});
|
|
12713
|
-
}
|
|
12714
|
-
}
|
|
12715
|
-
return queues;
|
|
12716
|
-
}
|
|
12717
|
-
/**
|
|
12718
|
-
* Get the number of tenants in a dispatch shard.
|
|
12719
|
-
*/
|
|
12720
|
-
async getShardTenantCount(shardId) {
|
|
12721
|
-
const dispatchKey = this.keys.dispatchKey(shardId);
|
|
12722
|
-
return await this.redis.zcard(dispatchKey);
|
|
12723
|
-
}
|
|
12724
|
-
/**
|
|
12725
|
-
* Get total tenant count across all dispatch shards.
|
|
12726
|
-
* Note: tenants may appear in multiple shards, so this may overcount.
|
|
12727
|
-
*/
|
|
12728
|
-
async getTotalTenantCount() {
|
|
12729
|
-
const counts = await Promise.all(
|
|
12730
|
-
Array.from({ length: this.shardCount }, (_, i) => this.getShardTenantCount(i))
|
|
12731
|
-
);
|
|
12732
|
-
return counts.reduce((sum, count) => sum + count, 0);
|
|
12733
|
-
}
|
|
12734
|
-
/**
|
|
12735
|
-
* Get the number of queues for a tenant.
|
|
12736
|
-
*/
|
|
12737
|
-
async getTenantQueueCount(tenantId) {
|
|
12738
|
-
const tenantQueueKey = this.keys.tenantQueueIndexKey(tenantId);
|
|
12739
|
-
return await this.redis.zcard(tenantQueueKey);
|
|
12740
|
-
}
|
|
12741
|
-
/**
|
|
12742
|
-
* Remove a tenant from a specific dispatch shard.
|
|
12743
|
-
*/
|
|
12744
|
-
async removeTenantFromShard(shardId, tenantId) {
|
|
12745
|
-
const dispatchKey = this.keys.dispatchKey(shardId);
|
|
12746
|
-
await this.redis.zrem(dispatchKey, tenantId);
|
|
12747
|
-
}
|
|
12748
|
-
/**
|
|
12749
|
-
* Add a tenant to a dispatch shard with the given score.
|
|
12750
|
-
*/
|
|
12751
|
-
async addTenantToShard(shardId, tenantId, score) {
|
|
12752
|
-
const dispatchKey = this.keys.dispatchKey(shardId);
|
|
12753
|
-
await this.redis.zadd(dispatchKey, score, tenantId);
|
|
12754
|
-
}
|
|
12755
|
-
/**
|
|
12756
|
-
* Remove a queue from a tenant's queue index.
|
|
12757
|
-
*/
|
|
12758
|
-
async removeQueueFromTenant(tenantId, queueId) {
|
|
12759
|
-
const tenantQueueKey = this.keys.tenantQueueIndexKey(tenantId);
|
|
12760
|
-
await this.redis.zrem(tenantQueueKey, queueId);
|
|
12761
|
-
}
|
|
12762
|
-
/**
|
|
12763
|
-
* Close the Redis connection.
|
|
12764
|
-
*/
|
|
12765
|
-
async close() {
|
|
12766
|
-
await this.redis.quit();
|
|
12767
|
-
}
|
|
12768
|
-
};
|
|
12769
12721
|
|
|
12770
12722
|
// src/fair-queue/telemetry.ts
|
|
12771
12723
|
var FairQueueAttributes = {
|
|
@@ -13245,41 +13197,170 @@ var BatchedSpanManager = class {
|
|
|
13245
13197
|
}
|
|
13246
13198
|
}
|
|
13247
13199
|
/**
|
|
13248
|
-
* Clean up state for a loop when it's stopped.
|
|
13200
|
+
* Clean up state for a loop when it's stopped.
|
|
13201
|
+
*/
|
|
13202
|
+
cleanup(loopId) {
|
|
13203
|
+
this.endCurrentSpan(loopId);
|
|
13204
|
+
this.loopStates.delete(loopId);
|
|
13205
|
+
}
|
|
13206
|
+
/**
|
|
13207
|
+
* Clean up all loop states.
|
|
13208
|
+
*/
|
|
13209
|
+
cleanupAll() {
|
|
13210
|
+
for (const loopId of this.loopStates.keys()) {
|
|
13211
|
+
this.cleanup(loopId);
|
|
13212
|
+
}
|
|
13213
|
+
}
|
|
13214
|
+
};
|
|
13215
|
+
var noopSpan = {
|
|
13216
|
+
spanContext: () => ({
|
|
13217
|
+
traceId: "",
|
|
13218
|
+
spanId: "",
|
|
13219
|
+
traceFlags: 0
|
|
13220
|
+
}),
|
|
13221
|
+
setAttribute: () => noopSpan,
|
|
13222
|
+
setAttributes: () => noopSpan,
|
|
13223
|
+
addEvent: () => noopSpan,
|
|
13224
|
+
addLink: () => noopSpan,
|
|
13225
|
+
addLinks: () => noopSpan,
|
|
13226
|
+
setStatus: () => noopSpan,
|
|
13227
|
+
updateName: () => noopSpan,
|
|
13228
|
+
end: () => {
|
|
13229
|
+
},
|
|
13230
|
+
isRecording: () => false,
|
|
13231
|
+
recordException: () => {
|
|
13232
|
+
}
|
|
13233
|
+
};
|
|
13234
|
+
var noopTelemetry = new FairQueueTelemetry({});
|
|
13235
|
+
var TenantDispatch = class {
|
|
13236
|
+
constructor(options) {
|
|
13237
|
+
this.options = options;
|
|
13238
|
+
this.redis = createRedisClient(options.redis);
|
|
13239
|
+
this.keys = options.keys;
|
|
13240
|
+
this.shardCount = Math.max(1, options.shardCount);
|
|
13241
|
+
}
|
|
13242
|
+
redis;
|
|
13243
|
+
keys;
|
|
13244
|
+
shardCount;
|
|
13245
|
+
/**
|
|
13246
|
+
* Get the dispatch shard ID for a tenant.
|
|
13247
|
+
* Uses jump consistent hash on the tenant ID so each tenant
|
|
13248
|
+
* always maps to exactly one dispatch shard.
|
|
13249
|
+
*/
|
|
13250
|
+
getShardForTenant(tenantId) {
|
|
13251
|
+
return jumpHash(tenantId, this.shardCount);
|
|
13252
|
+
}
|
|
13253
|
+
/**
|
|
13254
|
+
* Get eligible tenants from a dispatch shard (Level 1).
|
|
13255
|
+
* Returns tenants ordered by oldest message (lowest score first).
|
|
13256
|
+
*/
|
|
13257
|
+
async getTenantsFromShard(shardId, limit = 1e3, maxScore) {
|
|
13258
|
+
const dispatchKey = this.keys.dispatchKey(shardId);
|
|
13259
|
+
const score = maxScore ?? Date.now();
|
|
13260
|
+
const results = await this.redis.zrangebyscore(
|
|
13261
|
+
dispatchKey,
|
|
13262
|
+
"-inf",
|
|
13263
|
+
score,
|
|
13264
|
+
"WITHSCORES",
|
|
13265
|
+
"LIMIT",
|
|
13266
|
+
0,
|
|
13267
|
+
limit
|
|
13268
|
+
);
|
|
13269
|
+
const tenants = [];
|
|
13270
|
+
for (let i = 0; i < results.length; i += 2) {
|
|
13271
|
+
const tenantId = results[i];
|
|
13272
|
+
const scoreStr = results[i + 1];
|
|
13273
|
+
if (tenantId && scoreStr) {
|
|
13274
|
+
tenants.push({
|
|
13275
|
+
tenantId,
|
|
13276
|
+
score: parseFloat(scoreStr)
|
|
13277
|
+
});
|
|
13278
|
+
}
|
|
13279
|
+
}
|
|
13280
|
+
return tenants;
|
|
13281
|
+
}
|
|
13282
|
+
/**
|
|
13283
|
+
* Get queues for a specific tenant (Level 2).
|
|
13284
|
+
* Returns queues ordered by oldest message (lowest score first).
|
|
13285
|
+
*/
|
|
13286
|
+
async getQueuesForTenant(tenantId, limit = 1e3, maxScore) {
|
|
13287
|
+
const tenantQueueKey = this.keys.tenantQueueIndexKey(tenantId);
|
|
13288
|
+
const score = maxScore ?? Date.now();
|
|
13289
|
+
const results = await this.redis.zrangebyscore(
|
|
13290
|
+
tenantQueueKey,
|
|
13291
|
+
"-inf",
|
|
13292
|
+
score,
|
|
13293
|
+
"WITHSCORES",
|
|
13294
|
+
"LIMIT",
|
|
13295
|
+
0,
|
|
13296
|
+
limit
|
|
13297
|
+
);
|
|
13298
|
+
const queues = [];
|
|
13299
|
+
for (let i = 0; i < results.length; i += 2) {
|
|
13300
|
+
const queueId = results[i];
|
|
13301
|
+
const scoreStr = results[i + 1];
|
|
13302
|
+
if (queueId && scoreStr) {
|
|
13303
|
+
queues.push({
|
|
13304
|
+
queueId,
|
|
13305
|
+
score: parseFloat(scoreStr),
|
|
13306
|
+
tenantId
|
|
13307
|
+
});
|
|
13308
|
+
}
|
|
13309
|
+
}
|
|
13310
|
+
return queues;
|
|
13311
|
+
}
|
|
13312
|
+
/**
|
|
13313
|
+
* Get the number of tenants in a dispatch shard.
|
|
13314
|
+
*/
|
|
13315
|
+
async getShardTenantCount(shardId) {
|
|
13316
|
+
const dispatchKey = this.keys.dispatchKey(shardId);
|
|
13317
|
+
return await this.redis.zcard(dispatchKey);
|
|
13318
|
+
}
|
|
13319
|
+
/**
|
|
13320
|
+
* Get total tenant count across all dispatch shards.
|
|
13321
|
+
* Note: tenants may appear in multiple shards, so this may overcount.
|
|
13322
|
+
*/
|
|
13323
|
+
async getTotalTenantCount() {
|
|
13324
|
+
const counts = await Promise.all(
|
|
13325
|
+
Array.from({ length: this.shardCount }, (_, i) => this.getShardTenantCount(i))
|
|
13326
|
+
);
|
|
13327
|
+
return counts.reduce((sum, count) => sum + count, 0);
|
|
13328
|
+
}
|
|
13329
|
+
/**
|
|
13330
|
+
* Get the number of queues for a tenant.
|
|
13331
|
+
*/
|
|
13332
|
+
async getTenantQueueCount(tenantId) {
|
|
13333
|
+
const tenantQueueKey = this.keys.tenantQueueIndexKey(tenantId);
|
|
13334
|
+
return await this.redis.zcard(tenantQueueKey);
|
|
13335
|
+
}
|
|
13336
|
+
/**
|
|
13337
|
+
* Remove a tenant from a specific dispatch shard.
|
|
13338
|
+
*/
|
|
13339
|
+
async removeTenantFromShard(shardId, tenantId) {
|
|
13340
|
+
const dispatchKey = this.keys.dispatchKey(shardId);
|
|
13341
|
+
await this.redis.zrem(dispatchKey, tenantId);
|
|
13342
|
+
}
|
|
13343
|
+
/**
|
|
13344
|
+
* Add a tenant to a dispatch shard with the given score.
|
|
13249
13345
|
*/
|
|
13250
|
-
|
|
13251
|
-
this.
|
|
13252
|
-
this.
|
|
13346
|
+
async addTenantToShard(shardId, tenantId, score) {
|
|
13347
|
+
const dispatchKey = this.keys.dispatchKey(shardId);
|
|
13348
|
+
await this.redis.zadd(dispatchKey, score, tenantId);
|
|
13253
13349
|
}
|
|
13254
13350
|
/**
|
|
13255
|
-
*
|
|
13351
|
+
* Remove a queue from a tenant's queue index.
|
|
13256
13352
|
*/
|
|
13257
|
-
|
|
13258
|
-
|
|
13259
|
-
|
|
13260
|
-
}
|
|
13353
|
+
async removeQueueFromTenant(tenantId, queueId) {
|
|
13354
|
+
const tenantQueueKey = this.keys.tenantQueueIndexKey(tenantId);
|
|
13355
|
+
await this.redis.zrem(tenantQueueKey, queueId);
|
|
13261
13356
|
}
|
|
13262
|
-
|
|
13263
|
-
|
|
13264
|
-
|
|
13265
|
-
|
|
13266
|
-
|
|
13267
|
-
traceFlags: 0
|
|
13268
|
-
}),
|
|
13269
|
-
setAttribute: () => noopSpan,
|
|
13270
|
-
setAttributes: () => noopSpan,
|
|
13271
|
-
addEvent: () => noopSpan,
|
|
13272
|
-
addLink: () => noopSpan,
|
|
13273
|
-
addLinks: () => noopSpan,
|
|
13274
|
-
setStatus: () => noopSpan,
|
|
13275
|
-
updateName: () => noopSpan,
|
|
13276
|
-
end: () => {
|
|
13277
|
-
},
|
|
13278
|
-
isRecording: () => false,
|
|
13279
|
-
recordException: () => {
|
|
13357
|
+
/**
|
|
13358
|
+
* Close the Redis connection.
|
|
13359
|
+
*/
|
|
13360
|
+
async close() {
|
|
13361
|
+
await this.redis.quit();
|
|
13280
13362
|
}
|
|
13281
13363
|
};
|
|
13282
|
-
var noopTelemetry = new FairQueueTelemetry({});
|
|
13283
13364
|
var VisibilityManager = class {
|
|
13284
13365
|
constructor(options) {
|
|
13285
13366
|
this.options = options;
|
|
@@ -14261,6 +14342,101 @@ var CallbackFairQueueKeyProducer = class extends DefaultFairQueueKeyProducer {
|
|
|
14261
14342
|
return this.groupExtractor(groupName, queueId);
|
|
14262
14343
|
}
|
|
14263
14344
|
};
|
|
14345
|
+
var ExponentialBackoffRetry = class {
|
|
14346
|
+
maxAttempts;
|
|
14347
|
+
options;
|
|
14348
|
+
constructor(options) {
|
|
14349
|
+
this.options = {
|
|
14350
|
+
maxAttempts: options?.maxAttempts ?? 12,
|
|
14351
|
+
factor: options?.factor ?? 2,
|
|
14352
|
+
minTimeoutInMs: options?.minTimeoutInMs ?? 1e3,
|
|
14353
|
+
maxTimeoutInMs: options?.maxTimeoutInMs ?? 36e5,
|
|
14354
|
+
// 1 hour
|
|
14355
|
+
randomize: options?.randomize ?? true
|
|
14356
|
+
};
|
|
14357
|
+
this.maxAttempts = this.options.maxAttempts ?? 12;
|
|
14358
|
+
}
|
|
14359
|
+
getNextDelay(attempt, _error) {
|
|
14360
|
+
if (attempt >= this.maxAttempts) {
|
|
14361
|
+
return null;
|
|
14362
|
+
}
|
|
14363
|
+
const delay = calculateNextRetryDelay(this.options, attempt);
|
|
14364
|
+
return delay ?? null;
|
|
14365
|
+
}
|
|
14366
|
+
};
|
|
14367
|
+
var FixedDelayRetry = class {
|
|
14368
|
+
maxAttempts;
|
|
14369
|
+
delayMs;
|
|
14370
|
+
constructor(options) {
|
|
14371
|
+
this.maxAttempts = options.maxAttempts;
|
|
14372
|
+
this.delayMs = options.delayMs;
|
|
14373
|
+
}
|
|
14374
|
+
getNextDelay(attempt, _error) {
|
|
14375
|
+
if (attempt >= this.maxAttempts) {
|
|
14376
|
+
return null;
|
|
14377
|
+
}
|
|
14378
|
+
return this.delayMs;
|
|
14379
|
+
}
|
|
14380
|
+
};
|
|
14381
|
+
var LinearBackoffRetry = class {
|
|
14382
|
+
maxAttempts;
|
|
14383
|
+
baseDelayMs;
|
|
14384
|
+
maxDelayMs;
|
|
14385
|
+
constructor(options) {
|
|
14386
|
+
this.maxAttempts = options.maxAttempts;
|
|
14387
|
+
this.baseDelayMs = options.baseDelayMs;
|
|
14388
|
+
this.maxDelayMs = options.maxDelayMs ?? options.baseDelayMs * options.maxAttempts;
|
|
14389
|
+
}
|
|
14390
|
+
getNextDelay(attempt, _error) {
|
|
14391
|
+
if (attempt >= this.maxAttempts) {
|
|
14392
|
+
return null;
|
|
14393
|
+
}
|
|
14394
|
+
const delay = this.baseDelayMs * attempt;
|
|
14395
|
+
return Math.min(delay, this.maxDelayMs);
|
|
14396
|
+
}
|
|
14397
|
+
};
|
|
14398
|
+
var NoRetry = class {
|
|
14399
|
+
maxAttempts = 1;
|
|
14400
|
+
getNextDelay(_attempt, _error) {
|
|
14401
|
+
return null;
|
|
14402
|
+
}
|
|
14403
|
+
};
|
|
14404
|
+
var ImmediateRetry = class {
|
|
14405
|
+
maxAttempts;
|
|
14406
|
+
constructor(maxAttempts) {
|
|
14407
|
+
this.maxAttempts = maxAttempts;
|
|
14408
|
+
}
|
|
14409
|
+
getNextDelay(attempt, _error) {
|
|
14410
|
+
if (attempt >= this.maxAttempts) {
|
|
14411
|
+
return null;
|
|
14412
|
+
}
|
|
14413
|
+
return 0;
|
|
14414
|
+
}
|
|
14415
|
+
};
|
|
14416
|
+
var CustomRetry = class {
|
|
14417
|
+
maxAttempts;
|
|
14418
|
+
calculateDelay;
|
|
14419
|
+
constructor(options) {
|
|
14420
|
+
this.maxAttempts = options.maxAttempts;
|
|
14421
|
+
this.calculateDelay = options.calculateDelay;
|
|
14422
|
+
}
|
|
14423
|
+
getNextDelay(attempt, error) {
|
|
14424
|
+
if (attempt >= this.maxAttempts) {
|
|
14425
|
+
return null;
|
|
14426
|
+
}
|
|
14427
|
+
return this.calculateDelay(attempt, error);
|
|
14428
|
+
}
|
|
14429
|
+
};
|
|
14430
|
+
var defaultRetryOptions = {
|
|
14431
|
+
maxAttempts: 12,
|
|
14432
|
+
factor: 2,
|
|
14433
|
+
minTimeoutInMs: 1e3,
|
|
14434
|
+
maxTimeoutInMs: 36e5,
|
|
14435
|
+
randomize: true
|
|
14436
|
+
};
|
|
14437
|
+
function createDefaultRetryStrategy() {
|
|
14438
|
+
return new ExponentialBackoffRetry(defaultRetryOptions);
|
|
14439
|
+
}
|
|
14264
14440
|
|
|
14265
14441
|
// src/fair-queue/scheduler.ts
|
|
14266
14442
|
var BaseScheduler = class {
|
|
@@ -14389,9 +14565,7 @@ var DRRScheduler = class extends BaseScheduler {
|
|
|
14389
14565
|
};
|
|
14390
14566
|
})
|
|
14391
14567
|
);
|
|
14392
|
-
const eligibleTenants = tenantData.filter(
|
|
14393
|
-
(t) => !t.isAtCapacity && t.deficit >= 1
|
|
14394
|
-
);
|
|
14568
|
+
const eligibleTenants = tenantData.filter((t) => !t.isAtCapacity && t.deficit >= 1);
|
|
14395
14569
|
const blockedTenants = tenantData.filter((t) => t.isAtCapacity);
|
|
14396
14570
|
if (blockedTenants.length > 0) {
|
|
14397
14571
|
this.logger.debug("DRR: tenants blocked by concurrency", {
|
|
@@ -14684,11 +14858,7 @@ var WeightedScheduler = class extends BaseScheduler {
|
|
|
14684
14858
|
// FairScheduler Implementation
|
|
14685
14859
|
// ============================================================================
|
|
14686
14860
|
async selectQueues(masterQueueShard, consumerId, context2) {
|
|
14687
|
-
const snapshot = await this.#getOrCreateSnapshot(
|
|
14688
|
-
masterQueueShard,
|
|
14689
|
-
consumerId,
|
|
14690
|
-
context2
|
|
14691
|
-
);
|
|
14861
|
+
const snapshot = await this.#getOrCreateSnapshot(masterQueueShard, consumerId, context2);
|
|
14692
14862
|
if (snapshot.queues.length === 0) {
|
|
14693
14863
|
return [];
|
|
14694
14864
|
}
|
|
@@ -15039,101 +15209,6 @@ var RoundRobinScheduler = class extends BaseScheduler {
|
|
|
15039
15209
|
return [...array.slice(normalizedIndex), ...array.slice(0, normalizedIndex)];
|
|
15040
15210
|
}
|
|
15041
15211
|
};
|
|
15042
|
-
var ExponentialBackoffRetry = class {
|
|
15043
|
-
maxAttempts;
|
|
15044
|
-
options;
|
|
15045
|
-
constructor(options) {
|
|
15046
|
-
this.options = {
|
|
15047
|
-
maxAttempts: options?.maxAttempts ?? 12,
|
|
15048
|
-
factor: options?.factor ?? 2,
|
|
15049
|
-
minTimeoutInMs: options?.minTimeoutInMs ?? 1e3,
|
|
15050
|
-
maxTimeoutInMs: options?.maxTimeoutInMs ?? 36e5,
|
|
15051
|
-
// 1 hour
|
|
15052
|
-
randomize: options?.randomize ?? true
|
|
15053
|
-
};
|
|
15054
|
-
this.maxAttempts = this.options.maxAttempts ?? 12;
|
|
15055
|
-
}
|
|
15056
|
-
getNextDelay(attempt, _error) {
|
|
15057
|
-
if (attempt >= this.maxAttempts) {
|
|
15058
|
-
return null;
|
|
15059
|
-
}
|
|
15060
|
-
const delay = calculateNextRetryDelay(this.options, attempt);
|
|
15061
|
-
return delay ?? null;
|
|
15062
|
-
}
|
|
15063
|
-
};
|
|
15064
|
-
var FixedDelayRetry = class {
|
|
15065
|
-
maxAttempts;
|
|
15066
|
-
delayMs;
|
|
15067
|
-
constructor(options) {
|
|
15068
|
-
this.maxAttempts = options.maxAttempts;
|
|
15069
|
-
this.delayMs = options.delayMs;
|
|
15070
|
-
}
|
|
15071
|
-
getNextDelay(attempt, _error) {
|
|
15072
|
-
if (attempt >= this.maxAttempts) {
|
|
15073
|
-
return null;
|
|
15074
|
-
}
|
|
15075
|
-
return this.delayMs;
|
|
15076
|
-
}
|
|
15077
|
-
};
|
|
15078
|
-
var LinearBackoffRetry = class {
|
|
15079
|
-
maxAttempts;
|
|
15080
|
-
baseDelayMs;
|
|
15081
|
-
maxDelayMs;
|
|
15082
|
-
constructor(options) {
|
|
15083
|
-
this.maxAttempts = options.maxAttempts;
|
|
15084
|
-
this.baseDelayMs = options.baseDelayMs;
|
|
15085
|
-
this.maxDelayMs = options.maxDelayMs ?? options.baseDelayMs * options.maxAttempts;
|
|
15086
|
-
}
|
|
15087
|
-
getNextDelay(attempt, _error) {
|
|
15088
|
-
if (attempt >= this.maxAttempts) {
|
|
15089
|
-
return null;
|
|
15090
|
-
}
|
|
15091
|
-
const delay = this.baseDelayMs * attempt;
|
|
15092
|
-
return Math.min(delay, this.maxDelayMs);
|
|
15093
|
-
}
|
|
15094
|
-
};
|
|
15095
|
-
var NoRetry = class {
|
|
15096
|
-
maxAttempts = 1;
|
|
15097
|
-
getNextDelay(_attempt, _error) {
|
|
15098
|
-
return null;
|
|
15099
|
-
}
|
|
15100
|
-
};
|
|
15101
|
-
var ImmediateRetry = class {
|
|
15102
|
-
maxAttempts;
|
|
15103
|
-
constructor(maxAttempts) {
|
|
15104
|
-
this.maxAttempts = maxAttempts;
|
|
15105
|
-
}
|
|
15106
|
-
getNextDelay(attempt, _error) {
|
|
15107
|
-
if (attempt >= this.maxAttempts) {
|
|
15108
|
-
return null;
|
|
15109
|
-
}
|
|
15110
|
-
return 0;
|
|
15111
|
-
}
|
|
15112
|
-
};
|
|
15113
|
-
var CustomRetry = class {
|
|
15114
|
-
maxAttempts;
|
|
15115
|
-
calculateDelay;
|
|
15116
|
-
constructor(options) {
|
|
15117
|
-
this.maxAttempts = options.maxAttempts;
|
|
15118
|
-
this.calculateDelay = options.calculateDelay;
|
|
15119
|
-
}
|
|
15120
|
-
getNextDelay(attempt, error) {
|
|
15121
|
-
if (attempt >= this.maxAttempts) {
|
|
15122
|
-
return null;
|
|
15123
|
-
}
|
|
15124
|
-
return this.calculateDelay(attempt, error);
|
|
15125
|
-
}
|
|
15126
|
-
};
|
|
15127
|
-
var defaultRetryOptions = {
|
|
15128
|
-
maxAttempts: 12,
|
|
15129
|
-
factor: 2,
|
|
15130
|
-
minTimeoutInMs: 1e3,
|
|
15131
|
-
maxTimeoutInMs: 36e5,
|
|
15132
|
-
randomize: true
|
|
15133
|
-
};
|
|
15134
|
-
function createDefaultRetryStrategy() {
|
|
15135
|
-
return new ExponentialBackoffRetry(defaultRetryOptions);
|
|
15136
|
-
}
|
|
15137
15212
|
|
|
15138
15213
|
// src/fair-queue/index.ts
|
|
15139
15214
|
var FairQueue = class {
|
|
@@ -16061,10 +16136,7 @@ var FairQueue = class {
|
|
|
16061
16136
|
if (this.concurrencyManager && storedMessage) {
|
|
16062
16137
|
await this.concurrencyManager.release(descriptor, messageId);
|
|
16063
16138
|
}
|
|
16064
|
-
const { queueEmpty } = await this.#updateAllIndexesAfterDequeue(
|
|
16065
|
-
queueId,
|
|
16066
|
-
descriptor.tenantId
|
|
16067
|
-
);
|
|
16139
|
+
const { queueEmpty } = await this.#updateAllIndexesAfterDequeue(queueId, descriptor.tenantId);
|
|
16068
16140
|
if (queueEmpty) {
|
|
16069
16141
|
this.queueDescriptorCache.delete(queueId);
|
|
16070
16142
|
this.queueCooloffStates.delete(queueId);
|
|
@@ -16321,38 +16393,6 @@ var FairQueue = class {
|
|
|
16321
16393
|
}
|
|
16322
16394
|
return false;
|
|
16323
16395
|
}
|
|
16324
|
-
#incrementCooloff(queueId) {
|
|
16325
|
-
if (this.queueCooloffStates.size >= this.maxCooloffStatesSize) {
|
|
16326
|
-
this.logger.warn("Cooloff states cache hit size cap, clearing all entries", {
|
|
16327
|
-
size: this.queueCooloffStates.size,
|
|
16328
|
-
cap: this.maxCooloffStatesSize
|
|
16329
|
-
});
|
|
16330
|
-
this.queueCooloffStates.clear();
|
|
16331
|
-
}
|
|
16332
|
-
const state = this.queueCooloffStates.get(queueId) ?? {
|
|
16333
|
-
tag: "normal",
|
|
16334
|
-
consecutiveFailures: 0
|
|
16335
|
-
};
|
|
16336
|
-
if (state.tag === "normal") {
|
|
16337
|
-
const newFailures = state.consecutiveFailures + 1;
|
|
16338
|
-
if (newFailures >= this.cooloffThreshold) {
|
|
16339
|
-
this.queueCooloffStates.set(queueId, {
|
|
16340
|
-
tag: "cooloff",
|
|
16341
|
-
expiresAt: Date.now() + this.cooloffPeriodMs
|
|
16342
|
-
});
|
|
16343
|
-
this.logger.debug("Queue entered cooloff", {
|
|
16344
|
-
queueId,
|
|
16345
|
-
cooloffPeriodMs: this.cooloffPeriodMs,
|
|
16346
|
-
consecutiveFailures: newFailures
|
|
16347
|
-
});
|
|
16348
|
-
} else {
|
|
16349
|
-
this.queueCooloffStates.set(queueId, {
|
|
16350
|
-
tag: "normal",
|
|
16351
|
-
consecutiveFailures: newFailures
|
|
16352
|
-
});
|
|
16353
|
-
}
|
|
16354
|
-
}
|
|
16355
|
-
}
|
|
16356
16396
|
#resetCooloff(queueId) {
|
|
16357
16397
|
this.queueCooloffStates.delete(queueId);
|
|
16358
16398
|
}
|
|
@@ -16620,7 +16660,10 @@ var stringToError = z.string().transform((v, ctx) => {
|
|
|
16620
16660
|
try {
|
|
16621
16661
|
return BufferEntryError.parse(JSON.parse(v));
|
|
16622
16662
|
} catch {
|
|
16623
|
-
ctx.addIssue({
|
|
16663
|
+
ctx.addIssue({
|
|
16664
|
+
code: z.ZodIssueCode.custom,
|
|
16665
|
+
message: "expected JSON-encoded BufferEntryError"
|
|
16666
|
+
});
|
|
16624
16667
|
return z.NEVER;
|
|
16625
16668
|
}
|
|
16626
16669
|
});
|
|
@@ -16818,11 +16861,7 @@ var MollifierBuffer = class {
|
|
|
16818
16861
|
// not an error.
|
|
16819
16862
|
async listEntriesForEnv(envId, maxCount) {
|
|
16820
16863
|
if (maxCount <= 0) return [];
|
|
16821
|
-
const runIds = await this.redis.lrange(
|
|
16822
|
-
`mollifier:queue:${envId}`,
|
|
16823
|
-
0,
|
|
16824
|
-
maxCount - 1
|
|
16825
|
-
);
|
|
16864
|
+
const runIds = await this.redis.lrange(`mollifier:queue:${envId}`, 0, maxCount - 1);
|
|
16826
16865
|
if (runIds.length === 0) return [];
|
|
16827
16866
|
const pipeline = this.redis.pipeline();
|
|
16828
16867
|
for (const runId of runIds) {
|
|
@@ -16959,10 +16998,7 @@ var MollifierBuffer = class {
|
|
|
16959
16998
|
// never wiped by a slow predecessor.
|
|
16960
16999
|
async releaseClaim(input) {
|
|
16961
17000
|
const claimKey = makeIdempotencyClaimKey(input);
|
|
16962
|
-
await this.redis.releaseMollifierClaim(
|
|
16963
|
-
claimKey,
|
|
16964
|
-
`${PENDING_PREFIX}${input.token}`
|
|
16965
|
-
);
|
|
17001
|
+
await this.redis.releaseMollifierClaim(claimKey, `${PENDING_PREFIX}${input.token}`);
|
|
16966
17002
|
}
|
|
16967
17003
|
// Read the current claim value, used by the wait/poll loop on losers
|
|
16968
17004
|
// to detect "pending" → "resolved" transitions and timeouts.
|
|
@@ -17647,9 +17683,7 @@ var MollifierDrainer = class {
|
|
|
17647
17683
|
const orgs = await this.buffer.listOrgs();
|
|
17648
17684
|
if (orgs.length === 0) return { drained: 0, failed: 0 };
|
|
17649
17685
|
const orgSlice = this.takeOrgSlice(orgs);
|
|
17650
|
-
const envsByOrg = await Promise.all(
|
|
17651
|
-
orgSlice.map((orgId) => this.buffer.listEnvsForOrg(orgId))
|
|
17652
|
-
);
|
|
17686
|
+
const envsByOrg = await Promise.all(orgSlice.map((orgId) => this.buffer.listEnvsForOrg(orgId)));
|
|
17653
17687
|
const targets = [];
|
|
17654
17688
|
for (let i = 0; i < orgSlice.length; i++) {
|
|
17655
17689
|
const orgId = orgSlice[i];
|
|
@@ -17862,14 +17896,11 @@ var MollifierDrainer = class {
|
|
|
17862
17896
|
} catch (writeErr) {
|
|
17863
17897
|
if (this.isRetryable(writeErr)) {
|
|
17864
17898
|
await this.buffer.requeue(entry.runId);
|
|
17865
|
-
this.logger.warn(
|
|
17866
|
-
|
|
17867
|
-
|
|
17868
|
-
|
|
17869
|
-
|
|
17870
|
-
writeErr
|
|
17871
|
-
}
|
|
17872
|
-
);
|
|
17899
|
+
this.logger.warn("MollifierDrainer: terminal-failure callback retryable; requeued", {
|
|
17900
|
+
runId: entry.runId,
|
|
17901
|
+
attempts: nextAttempts,
|
|
17902
|
+
writeErr
|
|
17903
|
+
});
|
|
17873
17904
|
return "failed";
|
|
17874
17905
|
}
|
|
17875
17906
|
this.logger.error("MollifierDrainer: terminal-failure callback failed", {
|