11import { redisTest , StartedRedisContainer } from "@internal/testcontainers" ;
22import { ReleaseConcurrencyTokenBucketQueue } from "../releaseConcurrencyTokenBucketQueue.js" ;
33import { setTimeout } from "node:timers/promises" ;
4+ import { createRedisClient , Redis } from "@internal/redis" ;
45
56type TestQueueDescriptor = {
67 name : string ;
@@ -20,6 +21,7 @@ function createReleaseConcurrencyQueue(
2021 } ,
2122 executor : async ( releaseQueue , runId ) => {
2223 executedRuns . push ( { releaseQueue : releaseQueue . name , runId } ) ;
24+ return true ;
2325 } ,
2426 maxTokens : async ( _ ) => maxTokens ,
2527 keys : {
@@ -221,6 +223,7 @@ describe("ReleaseConcurrencyQueue", () => {
221223 throw new Error ( "Executor failed" ) ;
222224 }
223225 executedRuns . push ( { releaseQueue, runId } ) ;
226+ return true ;
224227 } ,
225228 maxTokens : async ( _ ) => 2 ,
226229 keys : {
@@ -299,6 +302,7 @@ describe("ReleaseConcurrencyQueue", () => {
299302 // Add small delay to simulate work
300303 await setTimeout ( 10 ) ;
301304 executedRuns . push ( { releaseQueue, runId } ) ;
305+ return true ;
302306 } ,
303307 keys : {
304308 fromDescriptor : ( descriptor ) => descriptor ,
@@ -419,6 +423,7 @@ describe("ReleaseConcurrencyQueue", () => {
419423 } ,
420424 executor : async ( releaseQueue , runId ) => {
421425 secondRunAttempted = true ;
426+ return true ;
422427 } ,
423428 keys : {
424429 fromDescriptor : ( descriptor ) => descriptor ,
@@ -516,6 +521,63 @@ describe("ReleaseConcurrencyQueue", () => {
516521 }
517522 } ) ;
518523
524+ redisTest (
525+ "Should return token but not requeue when executor returns false" ,
526+ async ( { redisContainer } ) => {
527+ const executedRuns : { releaseQueue : string ; runId : string } [ ] = [ ] ;
528+ const runResults : Record < string , boolean > = {
529+ run1 : true , // This will succeed
530+ run2 : false , // This will return false, returning the token without requeuing
531+ run3 : true , // This should execute immediately when run2's token is returned
532+ } ;
533+
534+ const queue = new ReleaseConcurrencyTokenBucketQueue < string > ( {
535+ redis : {
536+ keyPrefix : "release-queue:test:" ,
537+ host : redisContainer . getHost ( ) ,
538+ port : redisContainer . getPort ( ) ,
539+ } ,
540+ executor : async ( releaseQueue , runId ) => {
541+ const success = runResults [ runId ] ;
542+
543+ executedRuns . push ( { releaseQueue, runId } ) ;
544+
545+ return success ;
546+ } ,
547+ keys : {
548+ fromDescriptor : ( descriptor ) => descriptor ,
549+ toDescriptor : ( name ) => name ,
550+ } ,
551+ maxTokens : async ( _ ) => 2 , // Only 2 tokens available at a time
552+ pollInterval : 100 ,
553+ } ) ;
554+
555+ try {
556+ // First run should execute and succeed
557+ await queue . attemptToRelease ( "test-queue" , "run1" ) ;
558+ expect ( executedRuns ) . toHaveLength ( 1 ) ;
559+ expect ( executedRuns [ 0 ] ) . toEqual ( { releaseQueue : "test-queue" , runId : "run1" } ) ;
560+
561+ // Second run should execute but return false, returning the token
562+ await queue . attemptToRelease ( "test-queue" , "run2" ) ;
563+ expect ( executedRuns ) . toHaveLength ( 2 ) ;
564+ expect ( executedRuns [ 1 ] ) . toEqual ( { releaseQueue : "test-queue" , runId : "run2" } ) ;
565+
566+ // Third run should be able to execute immediately since run2 returned its token
567+ await queue . attemptToRelease ( "test-queue" , "run3" ) ;
568+
569+ expect ( executedRuns ) . toHaveLength ( 3 ) ;
570+ expect ( executedRuns [ 2 ] ) . toEqual ( { releaseQueue : "test-queue" , runId : "run3" } ) ;
571+
572+ // Verify that run2 was not retried (it should have been skipped)
573+ const run2Attempts = executedRuns . filter ( ( r ) => r . runId === "run2" ) ;
574+ expect ( run2Attempts ) . toHaveLength ( 1 ) ; // Only executed once, not retried
575+ } finally {
576+ await queue . quit ( ) ;
577+ }
578+ }
579+ ) ;
580+
519581 redisTest ( "Should implement exponential backoff between retries" , async ( { redisContainer } ) => {
520582 const executionTimes : number [ ] = [ ] ;
521583 let startTime : number ;
0 commit comments