@@ -186,7 +186,7 @@ private static Map<TopicPartition, MemoryRecords> partitionRecords(ProduceReques
186
186
}));
187
187
return Collections .unmodifiableMap (partitionRecords );
188
188
}
189
-
189
+
190
190
@ Test
191
191
public void testSimple () throws Exception {
192
192
long offset = 0 ;
@@ -3001,7 +3001,51 @@ public void testCustomErrorMessage() throws Exception {
3001
3001
}
3002
3002
3003
3003
@ Test
3004
- public void testSenderShouldRetryWithBackoffOnRetriableError () {
3004
+ public void testSenderShouldTransitionToAbortableAfterRetriesExhausted () throws InterruptedException {
3005
+ ProducerIdAndEpoch producerIdAndEpoch = new ProducerIdAndEpoch (123456L , (short ) 0 );
3006
+ TransactionManager txnManager = new TransactionManager (
3007
+ logContext ,
3008
+ "testRetriableException" ,
3009
+ 60000 ,
3010
+ RETRY_BACKOFF_MS ,
3011
+ apiVersions
3012
+ );
3013
+
3014
+ // Setup with transaction state and initialize transactions with single retry
3015
+ setupWithTransactionState (txnManager , false , null , 1 );
3016
+ doInitTransactions (txnManager , producerIdAndEpoch );
3017
+
3018
+ // Begin transaction and add partition
3019
+ txnManager .beginTransaction ();
3020
+ txnManager .maybeAddPartition (tp0 );
3021
+ client .prepareResponse (buildAddPartitionsToTxnResponseData (0 , Collections .singletonMap (tp0 , Errors .NONE )));
3022
+ sender .runOnce ();
3023
+
3024
+ // First produce request
3025
+ appendToAccumulator (tp0 );
3026
+ client .prepareResponse (produceResponse (tp0 , -1 , Errors .COORDINATOR_LOAD_IN_PROGRESS , -1 ));
3027
+ sender .runOnce ();
3028
+
3029
+ // Sleep for retry backoff
3030
+ time .sleep (RETRY_BACKOFF_MS );
3031
+
3032
+ // Second attempt to process record - PREPARE the response before sending
3033
+ client .prepareResponse (produceResponse (tp0 , -1 , Errors .COORDINATOR_LOAD_IN_PROGRESS , -1 ));
3034
+ sender .runOnce ();
3035
+
3036
+ // Now transaction should be in abortable state after retry is exhausted
3037
+ assertTrue (txnManager .hasAbortableError ());
3038
+
3039
+ // Second produce request - should fail with TransactionAbortableException
3040
+ Future <RecordMetadata > future2 = appendToAccumulator (tp0 );
3041
+ client .prepareResponse (produceResponse (tp0 , -1 , Errors .NONE , -1 ));
3042
+ // Sender will try to send and fail with TransactionAbortableException instead of COORDINATOR_LOAD_IN_PROGRESS, because we're in abortable state
3043
+ sender .runOnce ();
3044
+ assertFutureFailure (future2 , TransactionAbortableException .class );
3045
+ }
3046
+
3047
+ @ Test
3048
+ public void testSenderShouldRetryWithBackoffOnRetriableError () throws InterruptedException {
3005
3049
final long producerId = 343434L ;
3006
3050
TransactionManager transactionManager = createTransactionManager ();
3007
3051
setupWithTransactionState (transactionManager );
@@ -3635,6 +3679,10 @@ private void setupWithTransactionState(TransactionManager transactionManager, bo
3635
3679
setupWithTransactionState (transactionManager , guaranteeOrder , customPool , true , Integer .MAX_VALUE , 0 );
3636
3680
}
3637
3681
3682
+ private void setupWithTransactionState (TransactionManager transactionManager , boolean guaranteeOrder , BufferPool customPool , int retries ) {
3683
+ setupWithTransactionState (transactionManager , guaranteeOrder , customPool , true , retries , 0 );
3684
+ }
3685
+
3638
3686
private void setupWithTransactionState (
3639
3687
TransactionManager transactionManager ,
3640
3688
boolean guaranteeOrder ,
0 commit comments