@@ -259,10 +259,11 @@ export class RunQueue {
259
259
return this . #trace(
260
260
"dequeueMessageInSharedQueue" ,
261
261
async ( span ) => {
262
- const envQueues = await this . queuePriorityStrategy . distributeFairQueuesFromParentQueue (
263
- masterQueue ,
264
- consumerId
265
- ) ;
262
+ const envQueues =
263
+ await this . options . queuePriorityStrategy . distributeFairQueuesFromParentQueue (
264
+ masterQueue ,
265
+ consumerId
266
+ ) ;
266
267
267
268
span . setAttribute ( "environment_count" , envQueues . length ) ;
268
269
@@ -275,76 +276,57 @@ export class RunQueue {
275
276
276
277
const messages : DequeuedMessage [ ] = [ ] ;
277
278
278
- // Keep track of queues we've tried that didn't return a message
279
- const emptyQueues = new Set < string > ( ) ;
280
-
281
- // Continue until we've hit max count or tried all queues
282
- while ( messages . length < maxCount ) {
283
- // Calculate how many more messages we need
284
- const remainingCount = maxCount - messages . length ;
285
- if ( remainingCount <= 0 ) break ;
286
-
287
- // Find all available queues across environments that we haven't marked as empty
288
- const availableEnvQueues = envQueues
289
- . map ( ( env ) => ( {
290
- env : env ,
291
- queues : env . queues . filter ( ( queue ) => ! emptyQueues . has ( queue ) ) ,
292
- } ) )
293
- . filter ( ( env ) => env . queues . length > 0 ) ;
279
+ // Each env starts with its list of candidate queues
280
+ const tenantQueues : Record < string , string [ ] > = { } ;
294
281
295
- if ( availableEnvQueues . length === 0 ) break ;
282
+ // Initialize tenantQueues with the queues for each env
283
+ for ( const env of envQueues ) {
284
+ tenantQueues [ env . envId ] = [ ...env . queues ] ; // Create a copy of the queues array
285
+ }
296
286
297
- attemptedEnvs += availableEnvQueues . length ;
287
+ // Continue until we've hit max count or all tenants have empty queue lists
288
+ while (
289
+ messages . length < maxCount &&
290
+ Object . values ( tenantQueues ) . some ( ( queues ) => queues . length > 0 )
291
+ ) {
292
+ for ( const env of envQueues ) {
293
+ attemptedEnvs ++ ;
294
+
295
+ // Skip if this tenant has no more queues
296
+ if ( tenantQueues [ env . envId ] . length === 0 ) {
297
+ continue ;
298
+ }
298
299
299
- // Create a dequeue operation for each environment, taking one queue from each
300
- const dequeueOperations = availableEnvQueues . map ( ( { env, queues } ) => {
301
- const queue = queues [ 0 ] ;
300
+ // Pop the next queue (using round-robin order)
301
+ const queue = tenantQueues [ env . envId ] . shift ( ) ! ;
302
302
attemptedQueues ++ ;
303
303
304
- return {
305
- queue,
306
- operation : this . #callDequeueMessage( {
307
- messageQueue : queue ,
308
- concurrencyLimitKey : this . keys . concurrencyLimitKeyFromQueue ( queue ) ,
309
- currentConcurrencyKey : this . keys . currentConcurrencyKeyFromQueue ( queue ) ,
310
- envConcurrencyLimitKey : this . keys . envConcurrencyLimitKeyFromQueue ( queue ) ,
311
- envCurrentConcurrencyKey : this . keys . envCurrentConcurrencyKeyFromQueue ( queue ) ,
312
- projectCurrentConcurrencyKey :
313
- this . keys . projectCurrentConcurrencyKeyFromQueue ( queue ) ,
314
- messageKeyPrefix : this . keys . messageKeyPrefixFromQueue ( queue ) ,
315
- envQueueKey : this . keys . envQueueKeyFromQueue ( queue ) ,
316
- taskCurrentConcurrentKeyPrefix :
317
- this . keys . taskIdentifierCurrentConcurrencyKeyPrefixFromQueue ( queue ) ,
318
- } ) ,
319
- } ;
320
- } ) ;
321
-
322
- // Execute all dequeue operations in parallel
323
- const results = await Promise . all (
324
- dequeueOperations . map ( async ( { queue, operation } ) => {
325
- const message = await operation ;
326
- return { queue, message } ;
327
- } )
328
- ) ;
304
+ // Attempt to dequeue from this queue
305
+ const message = await this . #callDequeueMessage( {
306
+ messageQueue : queue ,
307
+ concurrencyLimitKey : this . keys . concurrencyLimitKeyFromQueue ( queue ) ,
308
+ currentConcurrencyKey : this . keys . currentConcurrencyKeyFromQueue ( queue ) ,
309
+ envConcurrencyLimitKey : this . keys . envConcurrencyLimitKeyFromQueue ( queue ) ,
310
+ envCurrentConcurrencyKey : this . keys . envCurrentConcurrencyKeyFromQueue ( queue ) ,
311
+ projectCurrentConcurrencyKey : this . keys . projectCurrentConcurrencyKeyFromQueue ( queue ) ,
312
+ messageKeyPrefix : this . keys . messageKeyPrefixFromQueue ( queue ) ,
313
+ envQueueKey : this . keys . envQueueKeyFromQueue ( queue ) ,
314
+ taskCurrentConcurrentKeyPrefix :
315
+ this . keys . taskIdentifierCurrentConcurrencyKeyPrefixFromQueue ( queue ) ,
316
+ } ) ;
329
317
330
- // Process results
331
- let foundAnyMessage = false ;
332
- for ( const { queue, message } of results ) {
333
318
if ( message ) {
334
319
messages . push ( message ) ;
335
- foundAnyMessage = true ;
336
- } else {
337
- // Mark this queue as empty
338
- emptyQueues . add ( queue ) ;
320
+ // Re-add this queue at the end, since it might have more messages
321
+ tenantQueues [ env . envId ] . push ( queue ) ;
339
322
}
340
- }
341
-
342
- // If we couldn't get a message from any queue in any env, break
343
- if ( ! foundAnyMessage ) break ;
323
+ // If message is null, do not re-add the queue in this cycle
344
324
345
- // If we've marked all queues as empty, break
346
- const totalQueues = envQueues . reduce ( ( sum , env ) => sum + env . queues . length , 0 ) ;
347
- if ( emptyQueues . size >= totalQueues ) break ;
325
+ // If we've reached maxCount, break out of the loop
326
+ if ( messages . length >= maxCount ) {
327
+ break ;
328
+ }
329
+ }
348
330
}
349
331
350
332
span . setAttributes ( {
@@ -635,42 +617,6 @@ export class RunQueue {
635
617
) ;
636
618
}
637
619
638
- queueConcurrencyScanStream (
639
- count : number = 100 ,
640
- onEndCallback ?: ( ) => void ,
641
- onErrorCallback ?: ( error : Error ) => void
642
- ) {
643
- const pattern = this . keys . queueCurrentConcurrencyScanPattern ( ) ;
644
-
645
- this . logger . debug ( "Starting queue concurrency scan stream" , {
646
- pattern,
647
- component : "runqueue" ,
648
- operation : "queueConcurrencyScanStream" ,
649
- service : this . name ,
650
- count,
651
- } ) ;
652
-
653
- const redis = this . redis . duplicate ( ) ;
654
-
655
- const stream = redis . scanStream ( {
656
- match : pattern ,
657
- type : "set" ,
658
- count,
659
- } ) ;
660
-
661
- stream . on ( "end" , ( ) => {
662
- onEndCallback ?.( ) ;
663
- redis . quit ( ) ;
664
- } ) ;
665
-
666
- stream . on ( "error" , ( error ) => {
667
- onErrorCallback ?.( error ) ;
668
- redis . quit ( ) ;
669
- } ) ;
670
-
671
- return { stream, redis } ;
672
- }
673
-
674
620
async quit ( ) {
675
621
await this . subscriber . unsubscribe ( ) ;
676
622
await this . subscriber . quit ( ) ;
@@ -1103,9 +1049,9 @@ local earliestMessage = redis.call('ZRANGE', childQueue, 0, 0, 'WITHSCORES')
1103
1049
for _, parentQueue in ipairs(decodedPayload.masterQueues) do
1104
1050
local prefixedParentQueue = keyPrefix .. parentQueue
1105
1051
if #earliestMessage == 0 then
1106
- redis.call('ZREM', prefixedParentQueue, childQueue )
1052
+ redis.call('ZREM', prefixedParentQueue, childQueueName )
1107
1053
else
1108
- redis.call('ZADD', prefixedParentQueue, earliestMessage[2], childQueue )
1054
+ redis.call('ZADD', prefixedParentQueue, earliestMessage[2], childQueueName )
1109
1055
end
1110
1056
end
1111
1057
0 commit comments