@@ -59,9 +59,6 @@ const ENV_CONCURRENCY_KEY_PREFIX = "batch:env_concurrency";
5959// then all messages are routed to this queue for BatchQueue's own consumer loop.
6060const BATCH_WORKER_QUEUE_ID = "batch-worker-queue" ;
6161
62- /** How long a claimed batch item stays invisible before the reclaim loop takes it back. */
63- const BATCH_ITEM_VISIBILITY_TIMEOUT_MS = 60_000 ;
64-
6562export class BatchQueue {
6663 private fairQueue : FairQueue < typeof BatchItemPayloadSchema > ;
6764 private workerQueueManager : WorkerQueueManager ;
@@ -70,8 +67,6 @@ export class BatchQueue {
7067 private tracer ?: Tracer ;
7168 private concurrencyRedis : Redis ;
7269 private defaultConcurrency : number ;
73- private heartbeatIntervalMs : number ;
74- private visibilityTimeoutMs : number ;
7570 private maxAttempts : number ;
7671
7772 private processItemCallback ?: ProcessBatchItemCallback ;
@@ -100,8 +95,6 @@ export class BatchQueue {
10095 this . logger = options . logger ?? new Logger ( "BatchQueue" , options . logLevel ?? "info" ) ;
10196 this . tracer = options . tracer ;
10297 this . defaultConcurrency = options . defaultConcurrency ?? 10 ;
103- this . visibilityTimeoutMs = options . visibilityTimeoutMs ?? BATCH_ITEM_VISIBILITY_TIMEOUT_MS ;
104- this . heartbeatIntervalMs = Math . max ( 50 , Math . floor ( this . visibilityTimeoutMs / 3 ) ) ;
10598 this . maxAttempts = options . retry ?. maxAttempts ?? 1 ;
10699 this . abortController = new AbortController ( ) ;
107100 this . workerQueueBlockingTimeoutSeconds = options . workerQueueBlockingTimeoutSeconds ?? 10 ;
@@ -161,8 +154,7 @@ export class BatchQueue {
161154 shardCount : options . shardCount ?? 1 ,
162155 consumerCount : options . consumerCount ,
163156 consumerIntervalMs : options . consumerIntervalMs ,
164- visibilityTimeoutMs : this . visibilityTimeoutMs ,
165- heartbeatIntervalMs : this . visibilityTimeoutMs ,
157+ visibilityTimeoutMs : 60_000 , // 1 minute for batch item processing
166158 startConsumers : false , // We control when to start
167159 cooloff : {
168160 enabled : false ,
@@ -760,44 +752,6 @@ export class BatchQueue {
760752 // Private - Message Handling
761753 // ============================================================================
762754
763- /**
764- * Keep extending a message's visibility deadline while its callback runs, so an item
765- * slower than the visibility timeout is not redelivered and executed a second time.
766- *
767- * `lostLease` reports that an extend found no in-flight entry, which means the item was
768- * reclaimed and is now back on the queue. It is a best-effort signal, not a fence: the
769- * in-flight member is keyed only by message and queue id, so once another consumer
770- * re-claims the item the member exists again and an extend from this consumer succeeds.
771- * Distinguishing owners would need a per-claim token in the member.
772- */
773- #startHeartbeat(
774- messageId : string ,
775- queueId : string
776- ) : { stop : ( ) => void ; lostLease : ( ) => boolean } {
777- let lostLease = false ;
778-
779- const interval = setInterval ( ( ) => {
780- this . fairQueue
781- . heartbeatMessage ( messageId , queueId )
782- . then ( ( stillOwned ) => {
783- if ( ! stillOwned ) {
784- lostLease = true ;
785- }
786- } )
787- . catch ( ( error ) => {
788- this . logger . debug ( "Batch item heartbeat failed" , {
789- messageId,
790- queueId,
791- error : error instanceof Error ? error . message : String ( error ) ,
792- } ) ;
793- } ) ;
794- } , this . heartbeatIntervalMs ) ;
795-
796- interval . unref ?.( ) ;
797-
798- return { stop : ( ) => clearInterval ( interval ) , lostLease : ( ) => lostLease } ;
799- }
800-
801755 async #handleMessage( consumerId : string , messageId : string , queueId : string ) : Promise < void > {
802756 // Get message data from FairQueue's in-flight storage
803757 const storedMessage = await this . fairQueue . getMessageData ( messageId , queueId ) ;
@@ -866,10 +820,9 @@ export class BatchQueue {
866820 let processedCount : number ;
867821
868822 try {
869- const heartbeat = this . #startHeartbeat( messageId , queueId ) ;
870- let result : Awaited < ReturnType < ProcessBatchItemCallback > > ;
871- try {
872- result = await this . #startSpan( "BatchQueue.processItemCallback" , async ( innerSpan ) => {
823+ const result = await this . #startSpan(
824+ "BatchQueue.processItemCallback" ,
825+ async ( innerSpan ) => {
873826 innerSpan ?. setAttributes ( {
874827 "batch.id" : batchId ,
875828 "batch.itemIndex" : itemIndex ,
@@ -884,20 +837,8 @@ export class BatchQueue {
884837 attempt,
885838 isFinalAttempt,
886839 } ) ;
887- } ) ;
888- } finally {
889- heartbeat . stop ( ) ;
890- }
891-
892- if ( heartbeat . lostLease ( ) ) {
893- this . logger . warn ( "Discarding batch item result, another consumer now owns it" , {
894- batchId,
895- itemIndex,
896- messageId,
897- attempt,
898- } ) ;
899- return ;
900- }
840+ }
841+ ) ;
901842
902843 if ( result . success ) {
903844 span ?. setAttribute ( "batch.result" , "success" ) ;
0 commit comments