@@ -259,10 +259,11 @@ export class RunQueue {
259259 return this . #trace(
260260 "dequeueMessageInSharedQueue" ,
261261 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+ ) ;
266267
267268 span . setAttribute ( "environment_count" , envQueues . length ) ;
268269
@@ -275,76 +276,57 @@ export class RunQueue {
275276
276277 const messages : DequeuedMessage [ ] = [ ] ;
277278
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 [ ] > = { } ;
294281
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+ }
296286
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+ }
298299
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 ( ) ! ;
302302 attemptedQueues ++ ;
303303
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+ } ) ;
329317
330- // Process results
331- let foundAnyMessage = false ;
332- for ( const { queue, message } of results ) {
333318 if ( message ) {
334319 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 ) ;
339322 }
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
344324
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+ }
348330 }
349331
350332 span . setAttributes ( {
@@ -635,42 +617,6 @@ export class RunQueue {
635617 ) ;
636618 }
637619
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-
674620 async quit ( ) {
675621 await this . subscriber . unsubscribe ( ) ;
676622 await this . subscriber . quit ( ) ;
@@ -1103,9 +1049,9 @@ local earliestMessage = redis.call('ZRANGE', childQueue, 0, 0, 'WITHSCORES')
11031049for _, parentQueue in ipairs(decodedPayload.masterQueues) do
11041050 local prefixedParentQueue = keyPrefix .. parentQueue
11051051 if #earliestMessage == 0 then
1106- redis.call('ZREM', prefixedParentQueue, childQueue )
1052+ redis.call('ZREM', prefixedParentQueue, childQueueName )
11071053 else
1108- redis.call('ZADD', prefixedParentQueue, earliestMessage[2], childQueue )
1054+ redis.call('ZADD', prefixedParentQueue, earliestMessage[2], childQueueName )
11091055 end
11101056end
11111057
0 commit comments