nahidupa commented on code in PR #17925:
URL: https://github.com/apache/iceberg/pull/17925#discussion_r3988324908


##########
kafka-connect/kafka-connect/src/test/java/org/apache/iceberg/connect/channel/TestCoordinator.java:
##########
@@ -129,6 +133,138 @@ public void testCommitNoFiles() {
     assertThat(table.snapshots()).isEmpty();
   }
 
+  @Test
+  public void testControlPartitionsRevokedResetsInFlightCommit() {
+    when(config.commitIntervalMs()).thenReturn(0);
+    when(config.commitTimeoutMs()).thenReturn(Integer.MAX_VALUE);
+
+    SinkTaskContext context = mock(SinkTaskContext.class);
+    Coordinator coordinator =
+        new Coordinator(catalog, config, ImmutableList.of(), clientFactory, 
context);
+    coordinator.start();
+    initConsumer();
+
+    // begin a commit and buffer a worker response, but withhold DATA_COMPLETE 
so the commit
+    // stays in flight
+    coordinator.process();
+    assertThat(producer.history()).hasSize(1);
+    UUID commitId =
+        ((StartCommit) 
AvroUtil.decode(producer.history().get(0).value()).payload()).commitId();
+
+    Event commitResponse =
+        new Event(
+            config.connectGroupId(),
+            new DataWritten(
+                StructType.of(),
+                commitId,
+                TableReference.of("catalog", TableIdentifier.of("db", "tbl"), 
null),
+                ImmutableList.of(EventTestUtil.createDataFile()),
+                ImmutableList.of()));
+    consumer.addRecord(
+        new ConsumerRecord<>(CTL_TOPIC_NAME, 0, 1, "key", 
AvroUtil.encode(commitResponse)));
+    coordinator.process();
+
+    // still mid-commit: no further event emitted
+    assertThat(producer.history()).hasSize(1);
+
+    // a control-topic rebalance revokes the partition; the in-flight commit 
must be discarded
+    consumer.rebalance(ImmutableList.of());
+
+    // with the in-flight commit reset, the coordinator is free to start a 
brand new commit on the
+    // next cycle. Without the reset it would still consider commit `commitId` 
in progress and emit
+    // nothing here.
+    coordinator.process();
+
+    assertThat(producer.history()).hasSize(2);
+    Event newStart = AvroUtil.decode(producer.history().get(1).value());
+    assertThat(newStart.type()).isEqualTo(PayloadType.START_COMMIT);
+    assertThat(((StartCommit) 
newStart.payload()).commitId()).isNotEqualTo(commitId);
+  }
+
+  /**
+   * A control-topic rebalance revokes the coordinator's assignment and, 
because the coordinator's
+   * consumer resumes from its last <em>committed</em> offset, the coordinator 
re-reads every
+   * control-topic record it had already consumed. This test models that 
rewind: after a rebalance,
+   * the same {@code DataWritten}/{@code DataComplete} pair for a commit is 
delivered again.
+   *
+   * <p>The coordinator expects responses from {@code totalPartitionCount} 
partitions (two here).
+   * Before the rebalance it has heard from exactly one (partition 0), so the 
commit is in flight
+   * with a readiness count of one. With the in-flight commit state reset (the 
fix), the re-read
+   * {@code DataComplete} is a stale event for a commit that no longer exists, 
so {@link
+   * CommitState#addReady} ignores it and the coordinator never concludes it 
has heard from both
+   * partitions. Without the reset, the stale {@code DataComplete} carries the 
same commit id as the
+   * still-in-flight commit, so it is counted a second time and the 
coordinator fires a commit that
+   * is missing half its data and stamps a watermark the table does not yet 
satisfy.
+   */
+  @Test
+  public void testControlPartitionsRevokedRewindDoesNotDoubleCount() {
+    when(config.commitIntervalMs()).thenReturn(0);
+    when(config.commitTimeoutMs()).thenReturn(Integer.MAX_VALUE);
+
+    // two source partitions, so a commit is only ready once both have reported
+    MemberAssignment assignment =
+        new MemberAssignment(
+            ImmutableSet.of(
+                new TopicPartition(SRC_TOPIC_NAME, 0), new 
TopicPartition(SRC_TOPIC_NAME, 1)));
+    MemberDescription member =
+        new MemberDescription(null, Optional.empty(), null, null, assignment);
+
+    SinkTaskContext context = mock(SinkTaskContext.class);
+    Coordinator coordinator =
+        new Coordinator(catalog, config, ImmutableList.of(member), 
clientFactory, context);
+    coordinator.start();
+    initConsumer();
+
+    // begin a commit and deliver partition 0's DataWritten + DataComplete, so 
the commit is in
+    // flight with a readiness count of one (of two)
+    coordinator.process();
+    assertThat(producer.history()).hasSize(1);
+    UUID commitId =
+        ((StartCommit) 
AvroUtil.decode(producer.history().get(0).value()).payload()).commitId();
+
+    OffsetDateTime ts = EventTestUtil.now();
+    Event dataWritten =
+        new Event(
+            config.connectGroupId(),
+            new DataWritten(
+                StructType.of(),
+                commitId,
+                TableReference.of("catalog", TableIdentifier.of("db", "tbl"), 
null),
+                ImmutableList.of(EventTestUtil.createDataFile()),
+                ImmutableList.of()));
+    Event dataComplete =
+        new Event(
+            config.connectGroupId(),
+            new DataComplete(
+                commitId, ImmutableList.of(new 
TopicPartitionOffset(SRC_TOPIC_NAME, 0, 3L, ts))));
+    consumer.addRecord(
+        new ConsumerRecord<>(CTL_TOPIC_NAME, 0, 1, "key", 
AvroUtil.encode(dataWritten)));
+    consumer.addRecord(
+        new ConsumerRecord<>(CTL_TOPIC_NAME, 0, 2, "key", 
AvroUtil.encode(dataComplete)));
+    coordinator.process();
+
+    // still mid-commit: only the StartCommit has been emitted
+    assertThat(producer.history()).hasSize(1);
+
+    // a control-topic rebalance revokes the partition; the in-flight commit 
must be discarded
+    consumer.rebalance(ImmutableList.of());
+
+    // the consumer rewinds and re-delivers the same DataWritten + 
DataComplete pair
+    consumer.rebalance(ImmutableList.of(new TopicPartition(CTL_TOPIC_NAME, 
0)));
+    consumer.addRecord(
+        new ConsumerRecord<>(CTL_TOPIC_NAME, 0, 1, "key", 
AvroUtil.encode(dataWritten)));
+    consumer.addRecord(
+        new ConsumerRecord<>(CTL_TOPIC_NAME, 0, 2, "key", 
AvroUtil.encode(dataComplete)));
+    coordinator.process();
+
+    // The coordinator has heard from exactly one partition (partition 0), 
delivered twice. With the
+    // reset, the re-read pair is stale and ignored, so no CommitToTable is 
emitted. Without the
+    // reset, the stale DataComplete would push the readiness count to 2 and a 
CommitToTable (and
+    // CommitComplete) would appear here.
+    assertThat(producer.history())
+        .noneMatch(record -> AvroUtil.decode(record.value()).type() == 
PayloadType.COMMIT_TO_TABLE);

Review Comment:
   Follow-up for the current head, `65320ff8f`: 
`repeatedDataCompleteWaitsForEveryExpectedPartition` now covers both 
same-offset replay and duplicate payloads at new offsets within the same active 
commit. It checks that no snapshot, completion event, or committed-offset 
advance occurs before the missing expected partition reports, then verifies 
exactly one added file in one snapshot, the final checkpoint, and the earlier 
non-null watermark. Details: 
https://github.com/apache/iceberg/pull/17925\#discussion_r3978570075.
   
   This supersedes the same-offset and watermark gaps in my earlier reply. The 
original reset/new-commit design was withdrawn; the replacement test is a 
MockConsumer same-commit simulation, not proof of live-broker rebalance or 
replacement recovery. Resolving the original scenario as superseded with this 
narrower coverage; the broader recovery discussion remains open.



##########
kafka-connect/kafka-connect/src/test/java/org/apache/iceberg/connect/channel/TestCoordinator.java:
##########
@@ -129,6 +137,272 @@ public void testCommitNoFiles() {
     assertThat(table.snapshots()).isEmpty();
   }
 
+  @Test
+  void retainsBufferedFilesWhenRebalanceResetsToLatest() {

Review Comment:
   Updating the coverage gap mentioned in my earlier reply: `65320ff8f` now 
includes both a same-offset rewind and duplicates at new offsets in 
`repeatedDataCompleteWaitsForEveryExpectedPartition`, with pending/final 
snapshot, checkpoint, and non-null watermark assertions. Details: 
https://github.com/apache/iceberg/pull/17925\#discussion_r3978570075.
   
   The retention test discussed here remains removed, and this PR still does 
not add a dispatch guard. The replacement exercises readiness during one active 
commit with MockConsumer, not a live-broker rebalance or replacement recovery. 
Resolving the concern about the withdrawn test, without claiming those broader 
guarantees.



-- 
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]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to