slachiewicz commented on code in PR #314:
URL: 
https://github.com/apache/flink-connector-kafka/pull/314#discussion_r4054034633


##########
flink-connector-kafka/src/main/java/org/apache/flink/connector/kafka/sink/internal/TransactionAbortStrategyImpl.java:
##########
@@ -143,6 +143,18 @@ public void abortTransactions(Context context) {
             TransactionAborter transactionAborter = 
context.getTransactionAborter();
             for (String name : openTransactionsForSubtask) {
                 if (context.getPrecommittedTransactionalIds().contains(name)) {
+                    if (context.isPrecommittedTransactionSuperseded(name)) {
+                        // The broker holds a later transaction under this id 
than the one the
+                        // committer is about to commit. That commit will be 
fenced, and nobody
+                        // owns the open transaction; abort it so that it does 
not pin the last
+                        // stable offset until the transaction timeout.
+                        LOG.warn(
+                                "Aborting open transaction {}: the recovered 
transaction under this id was superseded by a newer epoch",
+                                name);
+                        context.abandonPrecommittedTransaction(name);

Review Comment:
   Yes, it could, and #322 catches exactly that: on the previous revision only 
record 0 survived the restore. Fixed by keeping the id reserved; details in the 
thread on `ProducerPoolImpl` below.



##########
flink-connector-kafka/src/test/java/org/apache/flink/connector/kafka/sink/ExactlyOnceKafkaWriterITCase.java:
##########
@@ -275,6 +275,61 @@ void shouldNotAbortPrecommittedTransactions(int 
numCheckpointed) throws Exceptio
         }
     }
 
+    /**
+     * With {@code POOLING}, a committed transactional id is reused for a 
later checkpoint under a
+     * newer epoch. A recovery from the earlier checkpoint still lists the id 
as precommitted, and
+     * the committer's commit will be fenced. The open transaction under the 
newer epoch has no
+     * owner and must be aborted on recovery, also when the prefix changed and 
the id is never
+     * reused again (FLINK-40626).
+     */
+    @Test
+    void shouldAbortSupersededPrecommittedTransactionOnRecovery() throws 
Exception {
+        final KafkaWriterState stateOfCheckpoint1;
+        final CheckpointTransaction precommitted;
+        try (final ExactlyOnceKafkaWriter<Integer> failedWriter =
+                createWriter(this::withPooling, createInitContext())) {
+            Tuple2<KafkaWriterState, KafkaCommittable> checkpoint1 =
+                    onCheckpointBarrier(failedWriter, 1);
+            stateOfCheckpoint1 = checkpoint1.f0;
+            precommitted =
+                    
Iterables.getOnlyElement(stateOfCheckpoint1.getPrecommittedTransactionalIds());
+            assertThat(precommitted.hasKnownEpoch()).isTrue();
+            
assertThat(precommitted.getEpoch()).isEqualTo(checkpoint1.f1.getEpoch());
+
+            // the committer commits checkpoint 1 and hands the id back to the 
pool
+            checkpoint1.f1.getProducer().get().commitTransaction();
+            try (WritableBackchannel<TransactionFinished> backchannel =
+                    getBackchannel(failedWriter)) {
+                
backchannel.send(TransactionFinished.successful(precommitted.getTransactionalId()));
+            }
+            onCheckpointBarrier(failedWriter, 2);
+            // checkpoint 3 reuses the id of checkpoint 1 under a bumped epoch
+            KafkaCommittable checkpoint3 = onCheckpointBarrier(failedWriter, 
3).f1;
+            assertThat(checkpoint3.getTransactionalId())
+                    .isEqualTo(precommitted.getTransactionalId());
+            
assertThat(checkpoint3.getEpoch()).isGreaterThan(precommitted.getEpoch());
+            // the job fails here; the transactions of checkpoints 2 and 3 
linger on the broker
+        }
+
+        try (AdminClient admin = 
AdminClient.create(getKafkaClientConfiguration())) {
+            assertThat(AdminUtils.getOpenTransactionsForTopics(admin, 
Collections.singleton(topic)))
+                    .hasSize(2);
+
+            // recovery from checkpoint 1; the new writer gets a new prefix, 
so the old id is never

Review Comment:
   Changed: the test now restores with the same prefix, which is what makes 
POOLING contend for the reserved id, and it asserts that the first new 
transaction does not take it and that the fenced report leaves the new 
committable intact. The restored committer end to end is 
`KafkaSinkRecoveryITCase` from #322.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to