@trigger.dev/redis-worker 0.0.0-prerelease-20251209163704 → 0.0.0-prerelease-20251215135620
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 +139 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +31 -4
- package/dist/index.d.ts +31 -4
- package/dist/index.js +139 -30
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -11784,12 +11784,7 @@ var ConcurrencyManager = class {
|
|
|
11784
11784
|
);
|
|
11785
11785
|
const keys = groupData.map((g) => g.key);
|
|
11786
11786
|
const limits = groupData.map((g) => g.limit.toString());
|
|
11787
|
-
const result = await this.redis.reserveConcurrency(
|
|
11788
|
-
keys.length.toString(),
|
|
11789
|
-
messageId,
|
|
11790
|
-
...keys,
|
|
11791
|
-
...limits
|
|
11792
|
-
);
|
|
11787
|
+
const result = await this.redis.reserveConcurrency(keys.length, keys, messageId, ...limits);
|
|
11793
11788
|
return result === 1;
|
|
11794
11789
|
}
|
|
11795
11790
|
/**
|
|
@@ -11885,16 +11880,14 @@ var ConcurrencyManager = class {
|
|
|
11885
11880
|
// ============================================================================
|
|
11886
11881
|
#registerCommands() {
|
|
11887
11882
|
this.redis.defineCommand("reserveConcurrency", {
|
|
11888
|
-
numberOfKeys: 0,
|
|
11889
|
-
// Will pass number of keys in ARGV
|
|
11890
11883
|
lua: `
|
|
11891
|
-
local numGroups =
|
|
11892
|
-
local messageId = ARGV[
|
|
11884
|
+
local numGroups = #KEYS
|
|
11885
|
+
local messageId = ARGV[1]
|
|
11893
11886
|
|
|
11894
11887
|
-- Check all groups first
|
|
11895
11888
|
for i = 1, numGroups do
|
|
11896
|
-
local key =
|
|
11897
|
-
local limit = tonumber(ARGV[
|
|
11889
|
+
local key = KEYS[i]
|
|
11890
|
+
local limit = tonumber(ARGV[1 + i]) -- Limits start at ARGV[2]
|
|
11898
11891
|
local current = redis.call('SCARD', key)
|
|
11899
11892
|
|
|
11900
11893
|
if current >= limit then
|
|
@@ -11904,7 +11897,7 @@ end
|
|
|
11904
11897
|
|
|
11905
11898
|
-- All groups have capacity, add message to all
|
|
11906
11899
|
for i = 1, numGroups do
|
|
11907
|
-
local key =
|
|
11900
|
+
local key = KEYS[i]
|
|
11908
11901
|
redis.call('SADD', key, messageId)
|
|
11909
11902
|
end
|
|
11910
11903
|
|
|
@@ -12517,8 +12510,12 @@ var VisibilityManager = class {
|
|
|
12517
12510
|
const inflightKey = this.keys.inflightKey(shardId);
|
|
12518
12511
|
const member = this.#makeMember(messageId, queueId);
|
|
12519
12512
|
const newDeadline = Date.now() + extendMs;
|
|
12520
|
-
const result = await this.redis.
|
|
12521
|
-
|
|
12513
|
+
const result = await this.redis.heartbeatMessage(
|
|
12514
|
+
inflightKey,
|
|
12515
|
+
member,
|
|
12516
|
+
newDeadline.toString()
|
|
12517
|
+
);
|
|
12518
|
+
const success = result === 1;
|
|
12522
12519
|
if (success) {
|
|
12523
12520
|
this.logger.debug("Heartbeat successful", {
|
|
12524
12521
|
messageId,
|
|
@@ -12775,6 +12772,24 @@ redis.call('HDEL', inflightDataKey, messageId)
|
|
|
12775
12772
|
redis.call('ZADD', queueKey, score, messageId)
|
|
12776
12773
|
redis.call('HSET', queueItemsKey, messageId, payload)
|
|
12777
12774
|
|
|
12775
|
+
return 1
|
|
12776
|
+
`
|
|
12777
|
+
});
|
|
12778
|
+
this.redis.defineCommand("heartbeatMessage", {
|
|
12779
|
+
numberOfKeys: 1,
|
|
12780
|
+
lua: `
|
|
12781
|
+
local inflightKey = KEYS[1]
|
|
12782
|
+
local member = ARGV[1]
|
|
12783
|
+
local newDeadline = tonumber(ARGV[2])
|
|
12784
|
+
|
|
12785
|
+
-- Check if member exists in the in-flight set
|
|
12786
|
+
local score = redis.call('ZSCORE', inflightKey, member)
|
|
12787
|
+
if not score then
|
|
12788
|
+
return 0
|
|
12789
|
+
end
|
|
12790
|
+
|
|
12791
|
+
-- Update the deadline
|
|
12792
|
+
redis.call('ZADD', inflightKey, 'XX', newDeadline, member)
|
|
12778
12793
|
return 1
|
|
12779
12794
|
`
|
|
12780
12795
|
});
|
|
@@ -13222,7 +13237,20 @@ var DRRScheduler = class extends BaseScheduler {
|
|
|
13222
13237
|
const eligibleTenants = tenantData.filter(
|
|
13223
13238
|
(t) => !t.isAtCapacity && t.deficit >= 1
|
|
13224
13239
|
);
|
|
13240
|
+
const blockedTenants = tenantData.filter((t) => t.isAtCapacity);
|
|
13241
|
+
if (blockedTenants.length > 0) {
|
|
13242
|
+
this.logger.debug("DRR: tenants blocked by concurrency", {
|
|
13243
|
+
blockedCount: blockedTenants.length,
|
|
13244
|
+
blockedTenants: blockedTenants.map((t) => t.tenantId)
|
|
13245
|
+
});
|
|
13246
|
+
}
|
|
13225
13247
|
eligibleTenants.sort((a, b) => b.deficit - a.deficit);
|
|
13248
|
+
this.logger.debug("DRR: queue selection complete", {
|
|
13249
|
+
totalQueues: queues.length,
|
|
13250
|
+
totalTenants: tenantIds.length,
|
|
13251
|
+
eligibleTenants: eligibleTenants.length,
|
|
13252
|
+
topTenantDeficit: eligibleTenants[0]?.deficit
|
|
13253
|
+
});
|
|
13226
13254
|
return eligibleTenants.map((t) => ({
|
|
13227
13255
|
tenantId: t.tenantId,
|
|
13228
13256
|
queues: t.queues
|
|
@@ -13527,7 +13555,10 @@ var WeightedScheduler = class extends BaseScheduler {
|
|
|
13527
13555
|
return { tenantId, avgAge };
|
|
13528
13556
|
});
|
|
13529
13557
|
const maxAge = Math.max(...tenantAges.map((t) => t.avgAge));
|
|
13530
|
-
const weightedTenants = tenantAges.map((t) => ({
|
|
13558
|
+
const weightedTenants = maxAge === 0 ? tenantAges.map((t) => ({
|
|
13559
|
+
tenantId: t.tenantId,
|
|
13560
|
+
weight: 1 / tenantAges.length
|
|
13561
|
+
})) : tenantAges.map((t) => ({
|
|
13531
13562
|
tenantId: t.tenantId,
|
|
13532
13563
|
weight: t.avgAge / maxAge
|
|
13533
13564
|
}));
|
|
@@ -13570,11 +13601,11 @@ var WeightedScheduler = class extends BaseScheduler {
|
|
|
13570
13601
|
const tenant = snapshot.tenants.get(tenantId);
|
|
13571
13602
|
let weight = 1;
|
|
13572
13603
|
if (concurrencyLimitBias > 0) {
|
|
13573
|
-
const normalizedLimit = tenant.concurrency.limit / maxLimit;
|
|
13604
|
+
const normalizedLimit = maxLimit > 0 ? tenant.concurrency.limit / maxLimit : 0;
|
|
13574
13605
|
weight *= 1 + Math.pow(normalizedLimit * concurrencyLimitBias, 2);
|
|
13575
13606
|
}
|
|
13576
13607
|
if (availableCapacityBias > 0) {
|
|
13577
|
-
const usedPercentage = tenant.concurrency.current / tenant.concurrency.limit;
|
|
13608
|
+
const usedPercentage = tenant.concurrency.limit > 0 ? tenant.concurrency.current / tenant.concurrency.limit : 1;
|
|
13578
13609
|
const availableBonus = 1 - usedPercentage;
|
|
13579
13610
|
weight *= 1 + Math.pow(availableBonus * availableCapacityBias, 2);
|
|
13580
13611
|
}
|
|
@@ -13593,9 +13624,10 @@ var WeightedScheduler = class extends BaseScheduler {
|
|
|
13593
13624
|
return queues.sort((a, b) => b.age - a.age).map((q) => q.queueId);
|
|
13594
13625
|
}
|
|
13595
13626
|
const maxAge = Math.max(...queues.map((q) => q.age));
|
|
13627
|
+
const ageDenom = maxAge === 0 ? 1 : maxAge;
|
|
13596
13628
|
const weightedQueues = queues.map((q) => ({
|
|
13597
13629
|
queue: q,
|
|
13598
|
-
weight: 1 + q.age /
|
|
13630
|
+
weight: 1 + q.age / ageDenom * queueAgeRandomization
|
|
13599
13631
|
}));
|
|
13600
13632
|
const result = [];
|
|
13601
13633
|
let remaining = [...weightedQueues];
|
|
@@ -13878,6 +13910,7 @@ var FairQueue = class {
|
|
|
13878
13910
|
this.cooloffEnabled = options.cooloff?.enabled ?? true;
|
|
13879
13911
|
this.cooloffThreshold = options.cooloff?.threshold ?? 10;
|
|
13880
13912
|
this.cooloffPeriodMs = options.cooloff?.periodMs ?? 1e4;
|
|
13913
|
+
this.globalRateLimiter = options.globalRateLimiter;
|
|
13881
13914
|
this.telemetry = new FairQueueTelemetry({
|
|
13882
13915
|
tracer: options.tracer,
|
|
13883
13916
|
meter: options.meter,
|
|
@@ -13948,6 +13981,8 @@ var FairQueue = class {
|
|
|
13948
13981
|
cooloffThreshold;
|
|
13949
13982
|
cooloffPeriodMs;
|
|
13950
13983
|
queueCooloffStates = /* @__PURE__ */ new Map();
|
|
13984
|
+
// Global rate limiter
|
|
13985
|
+
globalRateLimiter;
|
|
13951
13986
|
// Runtime state
|
|
13952
13987
|
messageHandler;
|
|
13953
13988
|
isRunning = false;
|
|
@@ -13958,6 +13993,30 @@ var FairQueue = class {
|
|
|
13958
13993
|
// Queue descriptor cache for message processing
|
|
13959
13994
|
queueDescriptorCache = /* @__PURE__ */ new Map();
|
|
13960
13995
|
// ============================================================================
|
|
13996
|
+
// Public API - Telemetry
|
|
13997
|
+
// ============================================================================
|
|
13998
|
+
/**
|
|
13999
|
+
* Register observable gauge callbacks for telemetry.
|
|
14000
|
+
* Call this after FairQueue is created to enable gauge metrics.
|
|
14001
|
+
*
|
|
14002
|
+
* @param options.observedTenants - List of tenant IDs to observe for DLQ metrics
|
|
14003
|
+
*/
|
|
14004
|
+
registerTelemetryGauges(options) {
|
|
14005
|
+
this.telemetry.registerGaugeCallbacks({
|
|
14006
|
+
getMasterQueueLength: async (shardId) => {
|
|
14007
|
+
return await this.masterQueue.getShardQueueCount(shardId);
|
|
14008
|
+
},
|
|
14009
|
+
getInflightCount: async (shardId) => {
|
|
14010
|
+
return await this.visibilityManager.getInflightCount(shardId);
|
|
14011
|
+
},
|
|
14012
|
+
getDLQLength: async (tenantId) => {
|
|
14013
|
+
return await this.getDeadLetterQueueLength(tenantId);
|
|
14014
|
+
},
|
|
14015
|
+
shardCount: this.shardCount,
|
|
14016
|
+
observedTenants: options?.observedTenants
|
|
14017
|
+
});
|
|
14018
|
+
}
|
|
14019
|
+
// ============================================================================
|
|
13961
14020
|
// Public API - Message Handler
|
|
13962
14021
|
// ============================================================================
|
|
13963
14022
|
/**
|
|
@@ -14409,6 +14468,16 @@ var FairQueue = class {
|
|
|
14409
14468
|
return false;
|
|
14410
14469
|
}
|
|
14411
14470
|
}
|
|
14471
|
+
if (this.globalRateLimiter) {
|
|
14472
|
+
const result = await this.globalRateLimiter.limit();
|
|
14473
|
+
if (!result.allowed && result.resetAt) {
|
|
14474
|
+
const waitMs = Math.max(0, result.resetAt - Date.now());
|
|
14475
|
+
if (waitMs > 0) {
|
|
14476
|
+
this.logger.debug("Global rate limit reached, waiting", { waitMs, loopId });
|
|
14477
|
+
await new Promise((resolve) => setTimeout(resolve, waitMs));
|
|
14478
|
+
}
|
|
14479
|
+
}
|
|
14480
|
+
}
|
|
14412
14481
|
const claimResult = await this.visibilityManager.claim(
|
|
14413
14482
|
queueId,
|
|
14414
14483
|
queueKey,
|
|
@@ -14534,18 +14603,33 @@ var FairQueue = class {
|
|
|
14534
14603
|
return;
|
|
14535
14604
|
}
|
|
14536
14605
|
for (const { tenantId, queues } of tenantQueues) {
|
|
14537
|
-
|
|
14538
|
-
|
|
14539
|
-
|
|
14606
|
+
let availableSlots = 1;
|
|
14607
|
+
if (this.concurrencyManager) {
|
|
14608
|
+
const [current, limit] = await Promise.all([
|
|
14609
|
+
this.concurrencyManager.getCurrentConcurrency("tenant", tenantId),
|
|
14610
|
+
this.concurrencyManager.getConcurrencyLimit("tenant", tenantId)
|
|
14611
|
+
]);
|
|
14612
|
+
availableSlots = Math.max(1, limit - current);
|
|
14613
|
+
}
|
|
14614
|
+
let slotsUsed = 0;
|
|
14615
|
+
queueLoop: for (const queueId of queues) {
|
|
14616
|
+
while (slotsUsed < availableSlots) {
|
|
14617
|
+
if (this.cooloffEnabled && this.#isInCooloff(queueId)) {
|
|
14618
|
+
break;
|
|
14619
|
+
}
|
|
14620
|
+
const processed = await this.#processOneMessage(loopId, queueId, tenantId, shardId);
|
|
14621
|
+
if (processed) {
|
|
14622
|
+
await this.scheduler.recordProcessed?.(tenantId, queueId);
|
|
14623
|
+
this.#resetCooloff(queueId);
|
|
14624
|
+
slotsUsed++;
|
|
14625
|
+
} else {
|
|
14626
|
+
this.#incrementCooloff(queueId);
|
|
14627
|
+
break;
|
|
14628
|
+
}
|
|
14540
14629
|
}
|
|
14541
|
-
|
|
14542
|
-
|
|
14543
|
-
await this.scheduler.recordProcessed?.(tenantId, queueId);
|
|
14544
|
-
this.#resetCooloff(queueId);
|
|
14545
|
-
} else {
|
|
14546
|
-
this.#incrementCooloff(queueId);
|
|
14630
|
+
if (slotsUsed >= availableSlots) {
|
|
14631
|
+
break queueLoop;
|
|
14547
14632
|
}
|
|
14548
|
-
break;
|
|
14549
14633
|
}
|
|
14550
14634
|
}
|
|
14551
14635
|
}
|
|
@@ -14564,6 +14648,16 @@ var FairQueue = class {
|
|
|
14564
14648
|
return false;
|
|
14565
14649
|
}
|
|
14566
14650
|
}
|
|
14651
|
+
if (this.globalRateLimiter) {
|
|
14652
|
+
const result = await this.globalRateLimiter.limit();
|
|
14653
|
+
if (!result.allowed && result.resetAt) {
|
|
14654
|
+
const waitMs = Math.max(0, result.resetAt - Date.now());
|
|
14655
|
+
if (waitMs > 0) {
|
|
14656
|
+
this.logger.debug("Global rate limit reached, waiting", { waitMs, loopId });
|
|
14657
|
+
await new Promise((resolve) => setTimeout(resolve, waitMs));
|
|
14658
|
+
}
|
|
14659
|
+
}
|
|
14660
|
+
}
|
|
14567
14661
|
const claimResult = await this.visibilityManager.claim(
|
|
14568
14662
|
queueId,
|
|
14569
14663
|
queueKey,
|
|
@@ -14610,6 +14704,17 @@ var FairQueue = class {
|
|
|
14610
14704
|
error: result.error.message
|
|
14611
14705
|
});
|
|
14612
14706
|
await this.#moveToDeadLetterQueue(storedMessage, "Payload validation failed");
|
|
14707
|
+
if (this.concurrencyManager) {
|
|
14708
|
+
try {
|
|
14709
|
+
await this.concurrencyManager.release(descriptor, storedMessage.id);
|
|
14710
|
+
} catch (releaseError) {
|
|
14711
|
+
this.logger.error("Failed to release concurrency slot after payload validation failure", {
|
|
14712
|
+
messageId: storedMessage.id,
|
|
14713
|
+
queueId,
|
|
14714
|
+
error: releaseError instanceof Error ? releaseError.message : String(releaseError)
|
|
14715
|
+
});
|
|
14716
|
+
}
|
|
14717
|
+
}
|
|
14613
14718
|
return;
|
|
14614
14719
|
}
|
|
14615
14720
|
payload = result.data;
|
|
@@ -14786,7 +14891,6 @@ var FairQueue = class {
|
|
|
14786
14891
|
}
|
|
14787
14892
|
async #moveToDeadLetterQueue(storedMessage, errorMessage) {
|
|
14788
14893
|
if (!this.deadLetterQueueEnabled) {
|
|
14789
|
-
this.masterQueue.getShardForQueue(storedMessage.queueId);
|
|
14790
14894
|
await this.visibilityManager.complete(storedMessage.id, storedMessage.queueId);
|
|
14791
14895
|
return;
|
|
14792
14896
|
}
|
|
@@ -14888,6 +14992,11 @@ var FairQueue = class {
|
|
|
14888
14992
|
tag: "cooloff",
|
|
14889
14993
|
expiresAt: Date.now() + this.cooloffPeriodMs
|
|
14890
14994
|
});
|
|
14995
|
+
this.logger.debug("Queue entered cooloff", {
|
|
14996
|
+
queueId,
|
|
14997
|
+
cooloffPeriodMs: this.cooloffPeriodMs,
|
|
14998
|
+
consecutiveFailures: newFailures
|
|
14999
|
+
});
|
|
14891
15000
|
} else {
|
|
14892
15001
|
this.queueCooloffStates.set(queueId, {
|
|
14893
15002
|
tag: "normal",
|