This is an automated email from the ASF dual-hosted git repository.
danny0405 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hudi.git
The following commit(s) were added to refs/heads/master by this push:
new a6b8744d7fb2 fix(flink): defer instant creation until earlier
checkpoints finish (#20389)
a6b8744d7fb2 is described below
commit a6b8744d7fb26c71fd22cb8982d32fdf1b990109
Author: Vova Kolmakov <[email protected]>
AuthorDate: Tue Oct 20 12:04:53 2026 +0700
fix(flink): defer instant creation until earlier checkpoints finish (#20389)
* fix(flink): wait for earlier instants before starting a new one in
blocking mode
* fix(flink): rely on task polling for pending instant creation
* fix(flink): defer instant creation only for earlier checkpoints
---------
Co-authored-by: Vova Kolmakov <[email protected]>
Co-authored-by: danny0405 <[email protected]>
---
.../hudi/sink/StreamWriteOperatorCoordinator.java | 14 ++-
.../org/apache/hudi/sink/utils/CommitGuard.java | 105 -----------------
.../org/apache/hudi/sink/utils/EventBuffers.java | 19 +---
.../sink/TestStreamWriteOperatorCoordinator.java | 125 +++++++++++++++++++++
.../apache/hudi/sink/utils/TestCommitGuard.java | 78 -------------
.../apache/hudi/sink/utils/TestEventBuffers.java | 61 +++++-----
6 files changed, 161 insertions(+), 241 deletions(-)
diff --git
a/hudi-flink-datasource/hudi-flink/src/main/java/org/apache/hudi/sink/StreamWriteOperatorCoordinator.java
b/hudi-flink-datasource/hudi-flink/src/main/java/org/apache/hudi/sink/StreamWriteOperatorCoordinator.java
index e8c5ded34d70..5a56001e6437 100644
---
a/hudi-flink-datasource/hudi-flink/src/main/java/org/apache/hudi/sink/StreamWriteOperatorCoordinator.java
+++
b/hudi-flink-datasource/hudi-flink/src/main/java/org/apache/hudi/sink/StreamWriteOperatorCoordinator.java
@@ -427,11 +427,9 @@ public class StreamWriteOperatorCoordinator
long checkpointId = request.getCheckpointId();
// Existing instants must remain available while another checkpoint's
creation is blocked.
Pair<String, EventBuffer> instantTimeAndEventBuffer =
this.eventBuffers.getInstantAndEventBuffer(checkpointId);
- if (instantTimeAndEventBuffer == null &&
!instantRequestExecutor.hasRunningTasks()) {
+ if (instantTimeAndEventBuffer == null &&
!isInstantCreationBlocked(checkpointId) &&
!instantRequestExecutor.hasRunningTasks()) {
instantRequestExecutor.execute(() -> {
- if (this.eventBuffers.getInstantAndEventBuffer(checkpointId) == null) {
- // Wait until previous instants are committed.
- eventBuffers.awaitAllInstantsToCompleteIfNecessary();
+ if (this.eventBuffers.getInstantAndEventBuffer(checkpointId) == null
&& !isInstantCreationBlocked(checkpointId)) {
this.eventBuffers.initNewEventBuffer(checkpointId, startInstant());
}
}, "request instant time");
@@ -441,6 +439,14 @@ public class StreamWriteOperatorCoordinator
return
CompletableFuture.completedFuture(CoordinationResponseSerDe.wrap(Correspondent.InstantTimeResponse.getInstance(instantTime)));
}
+ /**
+ * Defers new instant creation while an earlier checkpoint has a pending
instant, even without write metadata events.
+ * Later checkpoints belong to an abandoned attempt when a writer requests
an earlier checkpoint after recovery.
+ */
+ private boolean isInstantCreationBlocked(long checkpointId) {
+ return OptionsResolver.isBlockingInstantGeneration(conf) &&
!eventBuffers.getPendingInstantsBefore(checkpointId).isEmpty();
+ }
+
private CompletableFuture<CoordinationResponse>
handleInFlightInstantsRequest(Correspondent.InflightInstantsRequest request) {
CoordinationResponse coordinationResponse =
Correspondent.InflightInstantsResponse.getInstance(eventBuffers.getAllCheckpointIdAndInstants());
return
CompletableFuture.completedFuture(CoordinationResponseSerDe.wrap(coordinationResponse));
diff --git
a/hudi-flink-datasource/hudi-flink/src/main/java/org/apache/hudi/sink/utils/CommitGuard.java
b/hudi-flink-datasource/hudi-flink/src/main/java/org/apache/hudi/sink/utils/CommitGuard.java
deleted file mode 100644
index 76836b6648b9..000000000000
---
a/hudi-flink-datasource/hudi-flink/src/main/java/org/apache/hudi/sink/utils/CommitGuard.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.hudi.sink.utils;
-
-import org.apache.hudi.exception.HoodieException;
-
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-import java.util.function.Supplier;
-
-/**
- * The commit guard used for blocking instant time generation.
- *
- * <p>A new instant time can not be generated until all the pending instants
are committed.
- */
-public class CommitGuard {
- /**
- * A lock used to block instant generation until being signaled after
pending instant is committed.
- */
- private final Lock lock;
- private final Condition condition;
- private final long commitAckTimeout;
-
- private CommitGuard(long commitAckTimeout) {
- this.lock = new ReentrantLock();
- this.condition = lock.newCondition();
- this.commitAckTimeout = commitAckTimeout;
- }
-
- public static CommitGuard create(long commitAckTimeout) {
- return new CommitGuard(commitAckTimeout);
- }
-
- /**
- * Wait until all the pending instants are committed.
- *
- * @param instants Current pending instants
- */
- public void blockFor(String instants) {
- lock.lock();
- try {
- if (!condition.await(commitAckTimeout, TimeUnit.MILLISECONDS)) {
- throw new HoodieException("Timeout(" + commitAckTimeout + "ms) while
waiting for instants [" + instants + "] to commit");
- }
- } catch (InterruptedException e) {
- throw new HoodieException("Blocking for instants completion is
interrupted.", e);
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Wait until the pending instants are committed.
- *
- * @param pendingInstants Supplier to get the pending instants
- */
- public void blockFor(Supplier<List<String>> pendingInstants) {
- lock.lock();
- long nanos = TimeUnit.MILLISECONDS.toNanos(commitAckTimeout);
- try {
- while (!pendingInstants.get().isEmpty()) {
- if (nanos <= 0L) {
- throw new HoodieException("Timeout(" + commitAckTimeout + "ms) while
waiting for instants ["
- + pendingInstants.get() + "] to commit");
- }
- nanos = condition.awaitNanos(nanos);
- }
- } catch (InterruptedException e) {
- throw new HoodieException("Blocking for instants completion is
interrupted.", e);
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Signals all waited threads which are waiting on the condition.
- */
- public void unblock() {
- lock.lock();
- try {
- condition.signalAll();
- } finally {
- lock.unlock();
- }
- }
-}
diff --git
a/hudi-flink-datasource/hudi-flink/src/main/java/org/apache/hudi/sink/utils/EventBuffers.java
b/hudi-flink-datasource/hudi-flink/src/main/java/org/apache/hudi/sink/utils/EventBuffers.java
index 8b0fc3cdbf9f..9985d73fe225 100644
---
a/hudi-flink-datasource/hudi-flink/src/main/java/org/apache/hudi/sink/utils/EventBuffers.java
+++
b/hudi-flink-datasource/hudi-flink/src/main/java/org/apache/hudi/sink/utils/EventBuffers.java
@@ -21,7 +21,6 @@ package org.apache.hudi.sink.utils;
import org.apache.hudi.client.WriteStatus;
import org.apache.hudi.common.util.Option;
import org.apache.hudi.common.util.collection.Pair;
-import org.apache.hudi.configuration.FlinkOptions;
import org.apache.hudi.configuration.OptionsResolver;
import org.apache.hudi.exception.HoodieException;
import org.apache.hudi.sink.event.WriteMetadataEvent;
@@ -48,26 +47,21 @@ public class EventBuffers implements Serializable {
// {checkpointId -> (instant, data write events, index write events)}
private final Map<Long, Pair<String, EventBuffer>> eventBuffers;
- private final Option<CommitGuard> commitGuardOption;
private final int dataWriteParallelism;
private final int indexWriteParallelism;
private EventBuffers(
Map<Long, Pair<String, EventBuffer>> eventBuffers,
- Option<CommitGuard> commitGuardOption,
int dataWriteParallelism,
int indexWriteParallelism) {
this.eventBuffers = eventBuffers;
- this.commitGuardOption = commitGuardOption;
this.dataWriteParallelism = dataWriteParallelism;
this.indexWriteParallelism = indexWriteParallelism;
}
public static EventBuffers getInstance(Configuration conf, int
dataWriteParallelism) {
- final Option<CommitGuard> commitGuardOpt =
OptionsResolver.isBlockingInstantGeneration(conf)
- ?
Option.of(CommitGuard.create(conf.get(FlinkOptions.WRITE_COMMIT_ACK_TIMEOUT)))
: Option.empty();
final int indexWriteParallelism =
OptionsResolver.indexWriteParallelism(conf);
- return new EventBuffers(new ConcurrentSkipListMap<>(), commitGuardOpt,
dataWriteParallelism, indexWriteParallelism);
+ return new EventBuffers(new ConcurrentSkipListMap<>(),
dataWriteParallelism, indexWriteParallelism);
}
public EventBuffer addEventToBuffer(WriteMetadataEvent event) {
@@ -137,15 +131,8 @@ public class EventBuffers implements Serializable {
this.eventBuffers.put(checkpointId, Pair.of(instantTime, new
EventBuffer(dataWriteParallelism, indexWriteParallelism)));
}
- public void awaitAllInstantsToCompleteIfNecessary() {
- if (this.commitGuardOption.isPresent() && nonEmpty()) {
- this.commitGuardOption.get().blockFor(getPendingInstants());
- }
- }
-
public void reset(long checkpointId) {
this.eventBuffers.remove(checkpointId);
- this.commitGuardOption.ifPresent(CommitGuard::unblock);
}
public boolean nonEmpty() {
@@ -162,10 +149,6 @@ public class EventBuffers implements Serializable {
.collect(Collectors.toList());
}
- public String getPendingInstants() {
- return
this.eventBuffers.values().stream().map(Pair::getKey).collect(Collectors.joining(","));
- }
-
/**
* Get write metadata events where there exists no event sent by eager
flushing from writers.
*/
diff --git
a/hudi-flink-datasource/hudi-flink/src/test/java/org/apache/hudi/sink/TestStreamWriteOperatorCoordinator.java
b/hudi-flink-datasource/hudi-flink/src/test/java/org/apache/hudi/sink/TestStreamWriteOperatorCoordinator.java
index 1e68d9f2842e..b32b62d6dfbc 100644
---
a/hudi-flink-datasource/hudi-flink/src/test/java/org/apache/hudi/sink/TestStreamWriteOperatorCoordinator.java
+++
b/hudi-flink-datasource/hudi-flink/src/test/java/org/apache/hudi/sink/TestStreamWriteOperatorCoordinator.java
@@ -1139,6 +1139,7 @@ public class TestStreamWriteOperatorCoordinator {
@Test
void testReadyInstantRemainsAvailableWhileAnotherCreationIsBlocked() throws
Exception {
+
resetToMergeOnRead(TestConfigurations.getDefaultConf(tempFile.getAbsolutePath()));
String existingInstant = requestInstantTime(1L);
MockOperatorCoordinatorContext ctx = (MockOperatorCoordinatorContext)
coordinator.getContext();
CountDownLatch gate = new CountDownLatch(1);
@@ -1162,6 +1163,130 @@ public class TestStreamWriteOperatorCoordinator {
}
}
+ @Test
+ void testEarlierPendingInstantDefersCreationUntilRetry() throws Exception {
+ String instant1 = requestInstantTime(1L);
+ MockOperatorCoordinatorContext ctx = (MockOperatorCoordinatorContext)
coordinator.getContext();
+ NonThrownExecutor worker =
Mockito.spy(NonThrownExecutor.builder(Mockito.mock(Logger.class))
+ .exceptionHook((errMsg, t) -> ctx.failJob(new HoodieException(errMsg,
t)))
+ .build());
+ coordinator.setInstantRequestExecutor(worker);
+
+ // An earlier checkpoint with an empty event buffer still represents a
pending instant.
+ Correspondent.InstantTimeResponse pending =
CoordinationResponseSerDe.unwrap(
+
coordinator.handleCoordinationRequest(Correspondent.InstantTimeRequest.getInstance(2L))
+ .get(1, TimeUnit.SECONDS));
+ assertNull(pending.getInstant());
+ Mockito.verify(worker, Mockito.never()).execute(Mockito.any(),
Mockito.eq("request instant time"));
+ assertEquals(instant1, requestInstantTime(1L), "Matching checkpoint
requests must reuse the pending instant");
+ assertEquals(0,
TestUtils.getCompletedInstantCount(tempFile.getAbsolutePath(),
HoodieTimeline.ROLLBACK_ACTION));
+
+ coordinator.handleEventFromOperator(0, createOperatorEvent(0, 1L,
instant1, "par1", false, true, 0.1));
+ coordinator.handleEventFromOperator(1, createOperatorEvent(1, 1L,
instant1, "par2", false, true, 0.1));
+ coordinator.notifyCheckpointComplete(2L);
+ assertEquals(instant1,
TestUtils.getLastCompleteInstant(tempFile.getAbsolutePath()));
+ assertFalse(worker.hasRunningTasks(), "Commit completion must not create
an instant for a previously deferred request");
+ assertNull(coordinator.getEventBuffer(2L));
+
+ assertNotEquals(instant1, requestInstantTime(2L), "A new task-side poll
must create the next instant");
+ assertEquals(0,
TestUtils.getCompletedInstantCount(tempFile.getAbsolutePath(),
HoodieTimeline.ROLLBACK_ACTION));
+ assertFalse(ctx.isJobFailed());
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"regional", "global"})
+ void testRecoveredWriterReusesPendingInstantAndRetries(String failover)
throws Exception {
+ String instant1 = requestInstantTime(1L);
+ Correspondent.InstantTimeResponse pending =
CoordinationResponseSerDe.unwrap(
+
coordinator.handleCoordinationRequest(Correspondent.InstantTimeRequest.getInstance(2L)).get(1,
TimeUnit.SECONDS));
+ assertNull(pending.getInstant());
+
+ if ("regional".equals(failover)) {
+ coordinator.subtaskReset(0, 1L);
+ } else {
+ CompletableFuture<byte[]> checkpoint = new CompletableFuture<>();
+ coordinator.checkpointCoordinator(1L, checkpoint);
+ coordinator.resetToCheckpoint(1L, checkpoint.get());
+ }
+ assertEquals(instant1, requestInstantTime(1L));
+ assertNull(coordinator.getEventBuffer(2L));
+
+ coordinator.handleEventFromOperator(0, createOperatorEvent(0, 1L,
instant1, "par1", false, true, 0.1));
+ coordinator.handleEventFromOperator(1, createOperatorEvent(1, 1L,
instant1, "par2", false, true, 0.1));
+ coordinator.notifyCheckpointComplete(2L);
+ assertNotEquals(instant1, requestInstantTime(2L));
+ assertEquals(0,
TestUtils.getCompletedInstantCount(tempFile.getAbsolutePath(),
HoodieTimeline.ROLLBACK_ACTION));
+ assertFalse(((MockOperatorCoordinatorContext)
coordinator.getContext()).isJobFailed());
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"regional", "global"})
+ void testRecoveredWriterRollsBackLaterInstantAndContinues(String failover)
throws Exception {
+ CompletableFuture<byte[]> checkpoint = new CompletableFuture<>();
+ coordinator.checkpointCoordinator(1L, checkpoint);
+ String abandonedInstant = requestInstantTime(2L);
+ coordinator.handleEventFromOperator(1, createOperatorEvent(1, 2L,
abandonedInstant, "par2", false, true, 0.1));
+
+ if ("regional".equals(failover)) {
+ coordinator.subtaskReset(0, 1L);
+ coordinator.subtaskReset(1, 1L);
+ } else {
+ coordinator.resetToCheckpoint(1L, checkpoint.get());
+ }
+ // One restored writer requests its instant before the other writer clears
its old metadata.
+ coordinator.handleEventFromOperator(0,
WriteMetadataEvent.emptyBootstrap(0, 1L, false));
+
+ String recoveredInstant = requestInstantTime(1L);
+ assertNotEquals(abandonedInstant, recoveredInstant);
+ assertEquals(recoveredInstant, requestInstantTime(1L));
+ assertEquals(1,
TestUtils.getCompletedInstantCount(tempFile.getAbsolutePath(),
HoodieTimeline.ROLLBACK_ACTION));
+ assertEquals(recoveredInstant,
TestUtils.getLastPendingInstant(tempFile.getAbsolutePath()));
+
+ coordinator.handleEventFromOperator(1,
WriteMetadataEvent.emptyBootstrap(1, 1L, false));
+ coordinator.handleEventFromOperator(0, createOperatorEvent(0, 1L,
recoveredInstant, "par1", false, true, 0.1));
+ coordinator.handleEventFromOperator(1, createOperatorEvent(1, 1L,
recoveredInstant, "par2", false, true, 0.1));
+ coordinator.notifyCheckpointComplete(3L);
+ assertEquals(recoveredInstant,
TestUtils.getLastCompleteInstant(tempFile.getAbsolutePath()));
+ assertNull(coordinator.getEventBuffer(2L), "Checkpoint completion must
remove the abandoned empty buffer");
+ assertNotEquals(recoveredInstant, requestInstantTime(3L));
+ assertFalse(((MockOperatorCoordinatorContext)
coordinator.getContext()).isJobFailed());
+ }
+
+ @Test
+ void testInstantCreationRechecksPendingBuffersOnWorker() throws Exception {
+ MockOperatorCoordinatorContext ctx = (MockOperatorCoordinatorContext)
coordinator.getContext();
+ CountDownLatch gate = new CountDownLatch(1);
+ NonThrownExecutor worker = new
GatedInstantRequestExecutor(Mockito.mock(Logger.class),
+ (errMsg, t) -> ctx.failJob(new HoodieException(errMsg, t)), gate);
+ coordinator.setInstantRequestExecutor(worker);
+ try {
+ Correspondent.InstantTimeResponse pending =
CoordinationResponseSerDe.unwrap(
+
coordinator.handleCoordinationRequest(Correspondent.InstantTimeRequest.getInstance(2L)).get(1,
TimeUnit.SECONDS));
+ assertNull(pending.getInstant());
+ // Bootstrap can restore a pending buffer after admission but before the
creation worker runs.
+ Field buffersField =
StreamWriteOperatorCoordinator.class.getDeclaredField("eventBuffers");
+ buffersField.setAccessible(true);
+ EventBuffers buffers = (EventBuffers) buffersField.get(coordinator);
+ buffers.initNewEventBuffer(1L, "001");
+ } finally {
+ gate.countDown();
+ }
+ worker.executeSync(() -> { }, "drain instant requests");
+ assertNull(coordinator.getEventBuffer(2L));
+ assertNotNull(coordinator.getEventBuffer(1L));
+ assertFalse(ctx.isJobFailed());
+ }
+
+ @Test
+ void testNonblockingInstantGenerationAllowsPendingBuffers() throws Exception
{
+
resetToMergeOnRead(TestConfigurations.getDefaultConf(tempFile.getAbsolutePath()));
+ String instant1 = requestInstantTime(1L);
+ assertNotEquals(instant1, requestInstantTime(2L));
+ assertNotNull(coordinator.getEventBuffer(1L));
+ assertNotNull(coordinator.getEventBuffer(2L));
+ assertFalse(((MockOperatorCoordinatorContext)
coordinator.getContext()).isJobFailed());
+ }
+
// -------------------------------------------------------------------------
// Utilities
// -------------------------------------------------------------------------
diff --git
a/hudi-flink-datasource/hudi-flink/src/test/java/org/apache/hudi/sink/utils/TestCommitGuard.java
b/hudi-flink-datasource/hudi-flink/src/test/java/org/apache/hudi/sink/utils/TestCommitGuard.java
deleted file mode 100644
index 3fc00e76d9ff..000000000000
---
a/hudi-flink-datasource/hudi-flink/src/test/java/org/apache/hudi/sink/utils/TestCommitGuard.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.hudi.sink.utils;
-
-import org.apache.hudi.exception.HoodieException;
-
-import org.junit.jupiter.api.Test;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-/**
- * Test cases for {@link CommitGuard}.
- */
-public class TestCommitGuard {
-
- @Test
- void testBlockForRechecksPendingInstantsAfterSignal() throws Exception {
- CommitGuard guard = CommitGuard.create(5_000L);
- AtomicBoolean pending = new AtomicBoolean(true);
- ExecutorService executor = Executors.newSingleThreadExecutor();
- try {
- CompletableFuture<Void> waitingFuture1 = CompletableFuture.runAsync(
- () -> guard.blockFor(() -> pending.get() ? List.of("001", "002") :
Collections.emptyList()), executor);
- CompletableFuture<Void> waitingFuture2 = CompletableFuture.runAsync(
- () -> guard.blockFor(() -> pending.get() ? List.of("001", "002") :
Collections.emptyList()), executor);
-
- Thread.sleep(100);
- guard.unblock();
- Thread.sleep(100);
- assertFalse(waitingFuture1.isDone(), "Should still block because pending
instants are not empty");
- assertFalse(waitingFuture2.isDone(), "Should still block because pending
instants are not empty");
-
- pending.set(false);
- guard.unblock();
- waitingFuture1.get(2, TimeUnit.SECONDS);
- assertTrue(waitingFuture1.isDone(), "Should finish when pending instants
become empty");
- waitingFuture2.get(2, TimeUnit.SECONDS);
- assertTrue(waitingFuture2.isDone(), "Should finish when pending instants
become empty");
- } finally {
- executor.shutdownNow();
- }
- }
-
- @Test
- void testBlockForTimeoutWhenPendingInstantsRemain() {
- CommitGuard guard = CommitGuard.create(100L);
- HoodieException hoodieException = assertThrows(
- HoodieException.class,
- () -> guard.blockFor(() ->
Collections.singletonList("timeout-instant")));
- assertTrue(hoodieException.getMessage().contains("timeout-instant"));
- }
-}
diff --git
a/hudi-flink-datasource/hudi-flink/src/test/java/org/apache/hudi/sink/utils/TestEventBuffers.java
b/hudi-flink-datasource/hudi-flink/src/test/java/org/apache/hudi/sink/utils/TestEventBuffers.java
index c2c7f7d338f3..580c889bba93 100644
---
a/hudi-flink-datasource/hudi-flink/src/test/java/org/apache/hudi/sink/utils/TestEventBuffers.java
+++
b/hudi-flink-datasource/hudi-flink/src/test/java/org/apache/hudi/sink/utils/TestEventBuffers.java
@@ -18,17 +18,15 @@
package org.apache.hudi.sink.utils;
-import org.apache.hudi.configuration.FlinkOptions;
import org.apache.hudi.sink.event.WriteMetadataEvent;
import org.apache.flink.configuration.Configuration;
import org.junit.jupiter.api.Test;
+import java.util.Arrays;
import java.util.Collections;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -38,42 +36,33 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
public class TestEventBuffers {
@Test
- void testAwaitAllInstantsWaitsUntilAllPendingInstantsReset() throws
Exception {
- Configuration conf = new Configuration();
- conf.set(FlinkOptions.WRITE_COMMIT_ACK_TIMEOUT, 5_000L);
- EventBuffers eventBuffers = EventBuffers.getInstance(conf, 1);
+ void testPendingInstantsBeforeIncludeBuffersWithoutEvents() {
+ EventBuffers buffers = EventBuffers.getInstance(new Configuration(), 1);
+ assertTrue(buffers.getPendingInstantsBefore(3L).isEmpty());
- eventBuffers.initNewEventBuffer(1L, "001");
- eventBuffers.addEventToBuffer(newWriteEvent(1L, "001"));
+ buffers.initNewEventBuffer(1L, "001");
+ assertEquals(Collections.singletonList("001"),
buffers.getPendingInstantsBefore(2L));
+ assertFalse(buffers.nonEmpty());
+ assertTrue(buffers.getPendingInstantsBefore(1L).isEmpty());
- ExecutorService executor = Executors.newSingleThreadExecutor();
- try {
- // two tasks block waiting on commit.
- CompletableFuture<Void> waitingFuture1 = CompletableFuture.runAsync(
- eventBuffers::awaitAllInstantsToCompleteIfNecessary, executor);
- CompletableFuture<Void> waitingFuture2 = CompletableFuture.runAsync(
- eventBuffers::awaitAllInstantsToCompleteIfNecessary, executor);
-
- Thread.sleep(100);
- assertFalse(waitingFuture1.isDone());
- assertFalse(waitingFuture2.isDone());
-
- eventBuffers.reset(1L);
- Thread.sleep(100);
- assertTrue(waitingFuture1.isDone());
- assertTrue(waitingFuture2.isDone());
- } finally {
- executor.shutdownNow();
- }
- }
-
- private static WriteMetadataEvent newWriteEvent(long checkpointId, String
instant) {
- return WriteMetadataEvent.builder()
+ buffers.addEventToBuffer(WriteMetadataEvent.builder()
.taskID(0)
- .checkpointId(checkpointId)
- .instantTime(instant)
+ .checkpointId(1L)
+ .instantTime("001")
.writeStatus(Collections.emptyList())
.lastBatch(true)
- .build();
+ .build());
+ assertEquals(Collections.singletonList("001"),
buffers.getPendingInstantsBefore(2L));
+ assertTrue(buffers.nonEmpty());
+
+ buffers.initNewEventBuffer(2L, "002");
+ assertEquals(Arrays.asList("001", "002"),
buffers.getPendingInstantsBefore(3L));
+ buffers.reset(1L);
+ assertTrue(buffers.getPendingInstantsBefore(2L).isEmpty());
+ assertEquals(Collections.singletonList("002"),
buffers.getPendingInstantsBefore(3L));
+ assertFalse(buffers.nonEmpty());
+
+ buffers.reset(2L);
+ assertTrue(buffers.getPendingInstantsBefore(3L).isEmpty());
}
}