This is an automated email from the ASF dual-hosted git repository.
JackieTien97 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git
The following commit(s) were added to refs/heads/master by this push:
new 9b2784c3ad4 Fix data exchange failure propagation (#18468)
9b2784c3ad4 is described below
commit 9b2784c3ad47f215d140aa49e25493c89f16a74d
Author: Jackie Tien <[email protected]>
AuthorDate: Fri Aug 14 12:14:44 2026 +0800
Fix data exchange failure propagation (#18468)
---
.../execution/exchange/MPPDataExchangeManager.java | 54 ++-
.../execution/exchange/sink/ShuffleSinkHandle.java | 121 ++++---
.../execution/exchange/sink/SinkChannel.java | 24 +-
.../execution/exchange/source/SourceHandle.java | 39 ++-
.../exchange/MPPDataExchangeManagerTest.java | 80 +++++
.../execution/exchange/ShuffleSinkHandleTest.java | 96 ++++++
.../SinkChannelFailurePropagationTest.java | 364 +++++++++++++++++++++
7 files changed, 699 insertions(+), 79 deletions(-)
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/exchange/MPPDataExchangeManager.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/exchange/MPPDataExchangeManager.java
index 2d3447dee69..5a9371570a9 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/exchange/MPPDataExchangeManager.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/exchange/MPPDataExchangeManager.java
@@ -463,17 +463,21 @@ public class MPPDataExchangeManager implements
IMPPDataExchangeManager {
private final AtomicInteger cnt;
+ private final AtomicBoolean hasChannelFailedOrAborted;
+
private final AtomicBoolean hasDecremented = new AtomicBoolean(false);
public ISinkChannelListenerImpl(
TFragmentInstanceId localFragmentInstanceId,
FragmentInstanceContext context,
IMPPDataExchangeManagerCallback<Throwable> onFailureCallback,
- AtomicInteger cnt) {
+ AtomicInteger cnt,
+ AtomicBoolean hasChannelFailedOrAborted) {
this.shuffleSinkHandleId = localFragmentInstanceId;
this.context = context;
this.onFailureCallback = onFailureCallback;
this.cnt = cnt;
+ this.hasChannelFailedOrAborted = hasChannelFailedOrAborted;
}
@Override
@@ -496,6 +500,7 @@ public class MPPDataExchangeManager implements
IMPPDataExchangeManager {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(DataNodeQueryMessages.SKH_LISTENER_ON_ABORT);
}
+ hasChannelFailedOrAborted.set(true);
decrementCnt();
return context.getFailureCause();
}
@@ -503,21 +508,25 @@ public class MPPDataExchangeManager implements
IMPPDataExchangeManager {
@Override
public void onFailure(ISink sink, Throwable t) {
LOGGER.warn(DataNodeQueryMessages.ISINKCHANNEL_FAILED_DUE_TO, t);
- decrementCnt();
- if (onFailureCallback != null) {
- onFailureCallback.call(t);
+ hasChannelFailedOrAborted.set(true);
+ try {
+ if (onFailureCallback != null) {
+ onFailureCallback.call(t);
+ }
+ } finally {
+ decrementCnt();
}
}
private void decrementCnt() {
if (hasDecremented.compareAndSet(false, true) && (cnt.decrementAndGet()
== 0)) {
- closeShuffleSinkHandle();
+ releaseShuffleSinkHandle();
}
}
- private void closeShuffleSinkHandle() {
+ private void releaseShuffleSinkHandle() {
ISinkHandle sinkHandle = shuffleSinkHandles.remove(shuffleSinkHandleId);
- if (sinkHandle != null) {
+ if (sinkHandle != null && !hasChannelFailedOrAborted.get()) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(DataNodeQueryMessages.CLOSE_SHUFFLE_SINK_HANDLE,
shuffleSinkHandleId);
}
@@ -651,7 +660,8 @@ public class MPPDataExchangeManager implements
IMPPDataExchangeManager {
// TODO: replace with callbacks to decouple MPPDataExchangeManager from
// FragmentInstanceContext
FragmentInstanceContext instanceContext,
- AtomicInteger cnt) {
+ AtomicInteger cnt,
+ AtomicBoolean hasChannelFailedOrAborted) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
@@ -687,7 +697,11 @@ public class MPPDataExchangeManager implements
IMPPDataExchangeManager {
localFragmentInstanceId,
queue,
new ISinkChannelListenerImpl(
- localFragmentInstanceId, instanceContext, instanceContext::failed,
cnt));
+ localFragmentInstanceId,
+ instanceContext,
+ instanceContext::failed,
+ cnt,
+ hasChannelFailedOrAborted));
}
/**
@@ -723,7 +737,8 @@ public class MPPDataExchangeManager implements
IMPPDataExchangeManager {
// TODO: replace with callbacks to decouple MPPDataExchangeManager from
// FragmentInstanceContext
FragmentInstanceContext instanceContext,
- AtomicInteger cnt) {
+ AtomicInteger cnt,
+ AtomicBoolean hasChannelFailedOrAborted) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
@@ -743,7 +758,11 @@ public class MPPDataExchangeManager implements
IMPPDataExchangeManager {
executorService,
tsBlockSerdeFactory.get(),
new ISinkChannelListenerImpl(
- localFragmentInstanceId, instanceContext, instanceContext::failed,
cnt),
+ localFragmentInstanceId,
+ instanceContext,
+ instanceContext::failed,
+ cnt,
+ hasChannelFailedOrAborted),
instanceContext.isHighestPriority(),
mppDataExchangeServiceClientManager);
}
@@ -767,6 +786,7 @@ public class MPPDataExchangeManager implements
IMPPDataExchangeManager {
int channelNum = downStreamChannelLocationList.size();
AtomicInteger cnt = new AtomicInteger(channelNum);
+ AtomicBoolean hasChannelFailedOrAborted = new AtomicBoolean(false);
List<ISinkChannel> downStreamChannelList =
downStreamChannelLocationList.stream()
.map(
@@ -776,7 +796,8 @@ public class MPPDataExchangeManager implements
IMPPDataExchangeManager {
localPlanNodeId,
downStreamChannelLocation,
instanceContext,
- cnt))
+ cnt,
+ hasChannelFailedOrAborted))
.collect(Collectors.toList());
ShuffleSinkHandle shuffleSinkHandle =
@@ -795,7 +816,8 @@ public class MPPDataExchangeManager implements
IMPPDataExchangeManager {
String localPlanNodeId,
DownStreamChannelLocation downStreamChannelLocation,
FragmentInstanceContext instanceContext,
- AtomicInteger cnt) {
+ AtomicInteger cnt,
+ AtomicBoolean hasChannelFailedOrAborted) {
if (isSameNode(downStreamChannelLocation.getRemoteEndpoint())) {
return createLocalSinkChannel(
localFragmentInstanceId,
@@ -803,7 +825,8 @@ public class MPPDataExchangeManager implements
IMPPDataExchangeManager {
downStreamChannelLocation.getRemotePlanNodeId(),
localPlanNodeId,
instanceContext,
- cnt);
+ cnt,
+ hasChannelFailedOrAborted);
} else {
return createSinkChannel(
localFragmentInstanceId,
@@ -812,7 +835,8 @@ public class MPPDataExchangeManager implements
IMPPDataExchangeManager {
downStreamChannelLocation.getRemotePlanNodeId(),
localPlanNodeId,
instanceContext,
- cnt);
+ cnt,
+ hasChannelFailedOrAborted);
}
}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/exchange/sink/ShuffleSinkHandle.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/exchange/sink/ShuffleSinkHandle.java
index 11c9d4d36bf..78e4928fdc3 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/exchange/sink/ShuffleSinkHandle.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/exchange/sink/ShuffleSinkHandle.java
@@ -33,6 +33,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@@ -63,6 +64,10 @@ public class ShuffleSinkHandle implements ISinkHandle {
private volatile boolean closed = false;
+ // close() and abort() invoke channel callbacks, so they cannot be protected
by this handle's
+ // lock.
+ private final AtomicBoolean terminationClaimed = new AtomicBoolean(false);
+
private static final DataExchangeCostMetricSet DATA_EXCHANGE_COST_METRIC_SET
=
DataExchangeCostMetricSet.getInstance();
private final Lock lock = new ReentrantLock();
@@ -201,37 +206,43 @@ public class ShuffleSinkHandle implements ISinkHandle {
@Override
public boolean abort() {
- if (aborted || closed) {
+ if (aborted || closed || !terminationClaimed.compareAndSet(false, true)) {
return false;
}
- if (LOGGER.isDebugEnabled()) {
- LOGGER.debug(DataNodeQueryMessages.START_ABORT_SHUFFLE_SINK_HANDLE);
- }
- boolean meetError = false;
- Exception firstException = null;
- boolean selfAborted = true;
- for (ISink channel : downStreamChannelList) {
- try {
- selfAborted = channel.abort();
- } catch (Exception e) {
- if (!meetError) {
- firstException = e;
- meetError = true;
+ try {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug(DataNodeQueryMessages.START_ABORT_SHUFFLE_SINK_HANDLE);
+ }
+ boolean meetError = false;
+ Exception firstException = null;
+ boolean selfAborted = true;
+ for (ISink channel : downStreamChannelList) {
+ try {
+ selfAborted = channel.abort();
+ } catch (Exception e) {
+ if (!meetError) {
+ firstException = e;
+ meetError = true;
+ }
}
}
- }
- if (meetError) {
-
LOGGER.warn(DataNodeQueryMessages.ERROR_OCCURRED_WHEN_TRY_TO_ABORT_CHANNEL,
firstException);
- }
- if (selfAborted) {
- sinkListener.onAborted(this);
- aborted = true;
- if (LOGGER.isDebugEnabled()) {
- LOGGER.debug(DataNodeQueryMessages.END_ABORT_SHUFFLE_SINK_HANDLE);
+ if (meetError) {
+
LOGGER.warn(DataNodeQueryMessages.ERROR_OCCURRED_WHEN_TRY_TO_ABORT_CHANNEL,
firstException);
+ }
+ if (selfAborted) {
+ sinkListener.onAborted(this);
+ aborted = true;
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug(DataNodeQueryMessages.END_ABORT_SHUFFLE_SINK_HANDLE);
+ }
+ return true;
+ } else {
+ return false;
+ }
+ } finally {
+ if (!aborted) {
+ terminationClaimed.set(false);
}
- return true;
- } else {
- return false;
}
}
@@ -241,37 +252,43 @@ public class ShuffleSinkHandle implements ISinkHandle {
// Lock ShuffleSinkHandle and wait to lock LocalSinkChannel
@Override
public boolean close() {
- if (closed || aborted) {
+ if (closed || aborted || !terminationClaimed.compareAndSet(false, true)) {
return false;
}
- if (LOGGER.isDebugEnabled()) {
- LOGGER.debug(DataNodeQueryMessages.START_CLOSE_SHUFFLE_SINK_HANDLE);
- }
- boolean meetError = false;
- Exception firstException = null;
- boolean selfClosed = true;
- for (ISink channel : downStreamChannelList) {
- try {
- selfClosed = channel.close();
- } catch (Exception e) {
- if (!meetError) {
- firstException = e;
- meetError = true;
+ try {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug(DataNodeQueryMessages.START_CLOSE_SHUFFLE_SINK_HANDLE);
+ }
+ boolean meetError = false;
+ Exception firstException = null;
+ boolean selfClosed = true;
+ for (ISink channel : downStreamChannelList) {
+ try {
+ selfClosed = channel.close();
+ } catch (Exception e) {
+ if (!meetError) {
+ firstException = e;
+ meetError = true;
+ }
}
}
- }
- if (meetError) {
-
LOGGER.warn(DataNodeQueryMessages.ERROR_OCCURRED_WHEN_TRY_TO_CLOSE_CHANNEL,
firstException);
- }
- if (selfClosed) {
- sinkListener.onFinish(this);
- closed = true;
- if (LOGGER.isDebugEnabled()) {
- LOGGER.debug(DataNodeQueryMessages.END_CLOSE_SHUFFLE_SINK_HANDLE);
+ if (meetError) {
+
LOGGER.warn(DataNodeQueryMessages.ERROR_OCCURRED_WHEN_TRY_TO_CLOSE_CHANNEL,
firstException);
+ }
+ if (selfClosed) {
+ sinkListener.onFinish(this);
+ closed = true;
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug(DataNodeQueryMessages.END_CLOSE_SHUFFLE_SINK_HANDLE);
+ }
+ return true;
+ } else {
+ return false;
+ }
+ } finally {
+ if (!closed) {
+ terminationClaimed.set(false);
}
- return true;
- } else {
- return false;
}
}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/exchange/sink/SinkChannel.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/exchange/sink/SinkChannel.java
index ec6c9c680a1..daaff9fdbcd 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/exchange/sink/SinkChannel.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/exchange/sink/SinkChannel.java
@@ -39,6 +39,7 @@ import org.apache.iotdb.mpp.rpc.thrift.TNewDataBlockEvent;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.ListenableFuture;
+import org.apache.thrift.TApplicationException;
import org.apache.tsfile.common.conf.TSFileDescriptor;
import org.apache.tsfile.external.commons.lang3.Validate;
import org.apache.tsfile.read.common.block.TsBlock;
@@ -523,6 +524,26 @@ public class SinkChannel implements ISinkChannel {
// endregion
+ /**
+ * A {@link TApplicationException} means the downstream received the RPC but
failed while handling
+ * it. Retrying the same data-block notification cannot recover that
failure. The sync client may
+ * wrap it in multiple {@code TException}s, so inspect the complete cause
chain.
+ */
+ private static boolean containsTApplicationException(Throwable throwable) {
+ Throwable current = throwable;
+ while (current != null) {
+ if (current instanceof TApplicationException) {
+ return true;
+ }
+ Throwable cause = current.getCause();
+ if (cause == current) {
+ return false;
+ }
+ current = cause;
+ }
+ return false;
+ }
+
// region ============ TestOnly ============
@TestOnly
public void setRetryIntervalInMs(long retryIntervalInMs) {
@@ -581,8 +602,9 @@ public class SinkChannel implements ISinkChannel {
} catch (Exception e) {
LOGGER.warn(
DataNodeQueryMessages.FAILED_TO_SEND_NEW_DATA_BLOCK_EVENT_ATTEMPT, attempt, e);
- if (attempt == MAX_ATTEMPT_TIMES) {
+ if (containsTApplicationException(e) || attempt ==
MAX_ATTEMPT_TIMES) {
sinkListener.onFailure(SinkChannel.this, e);
+ return;
}
try {
Thread.sleep(retryIntervalInMs);
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/exchange/source/SourceHandle.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/exchange/source/SourceHandle.java
index 4f90db0caed..2cd33b2d769 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/exchange/source/SourceHandle.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/exchange/source/SourceHandle.java
@@ -384,18 +384,35 @@ public class SourceHandle implements ISourceHandle {
public synchronized void updatePendingDataBlockInfo(
int startSequenceId, List<Long> dataBlockSizes) {
- if (LOGGER.isDebugEnabled()) {
- LOGGER.debug(
-
DataNodeQueryMessages.RECEIVENEWTSBLOCKNOTIFICATION_ARG_ARG_EACH_SIZE_IS_ARG,
- startSequenceId,
- startSequenceId + dataBlockSizes.size(),
- dataBlockSizes);
- }
- for (int i = 0; i < dataBlockSizes.size(); i++) {
- sequenceIdToDataBlockSize.put(i + startSequenceId,
dataBlockSizes.get(i));
+ try {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug(
+
DataNodeQueryMessages.RECEIVENEWTSBLOCKNOTIFICATION_ARG_ARG_EACH_SIZE_IS_ARG,
+ startSequenceId,
+ startSequenceId + dataBlockSizes.size(),
+ dataBlockSizes);
+ }
+ for (int i = 0; i < dataBlockSizes.size(); i++) {
+ sequenceIdToDataBlockSize.put(i + startSequenceId,
dataBlockSizes.get(i));
+ }
+ if (canGetTsBlockFromRemote) {
+ trySubmitGetDataBlocksTask();
+ }
+ } catch (RuntimeException | Error t) {
+ // This method runs in the inbound RPC thread. Mark the local FI failed
before the same
+ // exception is returned to the upstream FI through the existing Thrift
error channel.
+ notifyFailure(t);
+ throw t;
}
- if (canGetTsBlockFromRemote) {
- trySubmitGetDataBlocksTask();
+ }
+
+ private void notifyFailure(Throwable t) {
+ try {
+ sourceHandleListener.onFailure(this, t);
+ } catch (Throwable callbackFailure) {
+ if (callbackFailure != t) {
+ t.addSuppressed(callbackFailure);
+ }
}
}
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/execution/exchange/MPPDataExchangeManagerTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/execution/exchange/MPPDataExchangeManagerTest.java
index 448a094c046..ce491042481 100644
---
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/execution/exchange/MPPDataExchangeManagerTest.java
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/execution/exchange/MPPDataExchangeManagerTest.java
@@ -25,6 +25,9 @@ import org.apache.iotdb.commons.client.IClientManager;
import
org.apache.iotdb.commons.client.sync.SyncDataNodeMPPDataExchangeServiceClient;
import org.apache.iotdb.commons.memory.MemoryManager;
import org.apache.iotdb.db.conf.IoTDBDescriptor;
+import org.apache.iotdb.db.queryengine.common.FragmentInstanceId;
+import org.apache.iotdb.db.queryengine.common.PlanFragmentId;
+import org.apache.iotdb.db.queryengine.common.QueryId;
import
org.apache.iotdb.db.queryengine.execution.exchange.sink.DownStreamChannelIndex;
import
org.apache.iotdb.db.queryengine.execution.exchange.sink.DownStreamChannelLocation;
import org.apache.iotdb.db.queryengine.execution.exchange.sink.ISinkHandle;
@@ -33,18 +36,95 @@ import
org.apache.iotdb.db.queryengine.execution.exchange.sink.ShuffleSinkHandle
import org.apache.iotdb.db.queryengine.execution.exchange.source.ISourceHandle;
import
org.apache.iotdb.db.queryengine.execution.exchange.source.LocalSourceHandle;
import
org.apache.iotdb.db.queryengine.execution.fragment.FragmentInstanceContext;
+import
org.apache.iotdb.db.queryengine.execution.fragment.FragmentInstanceState;
+import
org.apache.iotdb.db.queryengine.execution.fragment.FragmentInstanceStateMachine;
import org.apache.iotdb.db.queryengine.execution.memory.LocalMemoryManager;
import org.apache.iotdb.db.queryengine.execution.memory.MemoryPool;
import org.apache.iotdb.mpp.rpc.thrift.TFragmentInstanceId;
+import org.apache.iotdb.mpp.rpc.thrift.TNewDataBlockEvent;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mockito;
import java.util.Collections;
+import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MPPDataExchangeManagerTest {
+ @Test
+ public void testNewDataBlockMemoryFailureMarksTargetFragmentFailed() throws
Exception {
+ final String queryId = "q_memory_reservation_failure";
+ final String targetPlanNodeId = "root-exchange";
+ final long dataBlockSize = 1024L;
+ final TFragmentInstanceId targetFragmentInstanceId = new
TFragmentInstanceId(queryId, 0, "0");
+ final TFragmentInstanceId sourceFragmentInstanceId = new
TFragmentInstanceId(queryId, 1, "0");
+ final TEndPoint sourceEndpoint = new TEndPoint("remote-exchange", 10740);
+ ExecutorService exchangeExecutor = Executors.newSingleThreadExecutor();
+ ExecutorService notificationExecutor = Executors.newSingleThreadExecutor();
+
+ try {
+ LocalMemoryManager localMemoryManager =
Mockito.mock(LocalMemoryManager.class);
+ MemoryPool memoryPool = Mockito.mock(MemoryPool.class);
+ Mockito.when(localMemoryManager.getQueryPool()).thenReturn(memoryPool);
+ IllegalArgumentException expectedFailure =
+ new IllegalArgumentException("injected memory reservation failure");
+ Mockito.when(
+ memoryPool.reserveWithPriority(
+ Mockito.eq(queryId),
+ Mockito.anyString(),
+ Mockito.eq(targetPlanNodeId),
+ Mockito.eq(dataBlockSize),
+ Mockito.anyLong(),
+ Mockito.eq(false)))
+ .thenThrow(expectedFailure);
+
+ FragmentInstanceId targetId =
+ new FragmentInstanceId(new PlanFragmentId(new QueryId(queryId), 0),
"0");
+ FragmentInstanceStateMachine stateMachine =
+ new FragmentInstanceStateMachine(targetId, notificationExecutor);
+ FragmentInstanceContext context =
+ FragmentInstanceContext.createFragmentInstanceContext(targetId,
stateMachine);
+ MPPDataExchangeManager exchangeManager =
+ new MPPDataExchangeManager(
+ localMemoryManager,
+ new TsBlockSerdeFactory(),
+ exchangeExecutor,
+ Mockito.mock(IClientManager.class));
+ ISourceHandle sourceHandle =
+ exchangeManager.createSourceHandle(
+ targetFragmentInstanceId,
+ targetPlanNodeId,
+ 0,
+ sourceEndpoint,
+ sourceFragmentInstanceId,
+ context::failed);
+ sourceHandle.isBlocked();
+
+ TNewDataBlockEvent event =
+ new TNewDataBlockEvent(
+ targetFragmentInstanceId,
+ targetPlanNodeId,
+ sourceFragmentInstanceId,
+ 0,
+ Collections.singletonList(dataBlockSize));
+ IllegalArgumentException actualFailure =
+ Assert.assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ exchangeManager
+ .getOrCreateMPPDataExchangeServiceImpl()
+ .onNewDataBlockEvent(event));
+
+ Assert.assertSame(expectedFailure, actualFailure);
+ Assert.assertEquals(FragmentInstanceState.FAILED,
stateMachine.getState());
+ Assert.assertSame(expectedFailure,
stateMachine.getFailureCauses().peek());
+ } finally {
+ exchangeExecutor.shutdownNow();
+ notificationExecutor.shutdownNow();
+ }
+ }
+
@Test
public void testCreateLocalSinkHandle() {
final TFragmentInstanceId localFragmentInstanceId = new
TFragmentInstanceId("q0", 1, "0");
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/execution/exchange/ShuffleSinkHandleTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/execution/exchange/ShuffleSinkHandleTest.java
index 75d204acade..01725611b6e 100644
---
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/execution/exchange/ShuffleSinkHandleTest.java
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/execution/exchange/ShuffleSinkHandleTest.java
@@ -22,6 +22,7 @@ package org.apache.iotdb.db.queryengine.execution.exchange;
import org.apache.iotdb.commons.memory.MemoryManager;
import org.apache.iotdb.db.queryengine.common.FragmentInstanceId;
import
org.apache.iotdb.db.queryengine.execution.exchange.sink.DownStreamChannelIndex;
+import org.apache.iotdb.db.queryengine.execution.exchange.sink.ISinkChannel;
import
org.apache.iotdb.db.queryengine.execution.exchange.sink.LocalSinkChannel;
import
org.apache.iotdb.db.queryengine.execution.exchange.sink.ShuffleSinkHandle;
import
org.apache.iotdb.db.queryengine.execution.exchange.source.LocalSourceHandle;
@@ -35,10 +36,105 @@ import org.junit.Test;
import org.mockito.Mockito;
import java.util.Collections;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
import static
com.google.common.util.concurrent.MoreExecutors.newDirectExecutorService;
public class ShuffleSinkHandleTest {
+ @Test
+ public void testClosePreventsConcurrentAbort() throws Exception {
+ TFragmentInstanceId fragmentInstanceId = new TFragmentInstanceId("q0", 0,
"0");
+ ISinkChannel channel = Mockito.mock(ISinkChannel.class);
+ MPPDataExchangeManager.SinkListener sinkListener =
+ Mockito.mock(MPPDataExchangeManager.SinkListener.class);
+ CountDownLatch closeStarted = new CountDownLatch(1);
+ CountDownLatch allowCloseToFinish = new CountDownLatch(1);
+ Mockito.when(channel.close())
+ .thenAnswer(
+ invocation -> {
+ closeStarted.countDown();
+ Assert.assertTrue(allowCloseToFinish.await(5, TimeUnit.SECONDS));
+ return true;
+ });
+
+ ShuffleSinkHandle shuffleSinkHandle =
+ new ShuffleSinkHandle(
+ fragmentInstanceId,
+ Collections.singletonList(channel),
+ new DownStreamChannelIndex(0),
+ ShuffleSinkHandle.ShuffleStrategyEnum.PLAIN,
+ sinkListener);
+ ExecutorService executor = Executors.newSingleThreadExecutor();
+
+ try {
+ Future<Boolean> closeResult = executor.submit(shuffleSinkHandle::close);
+ Assert.assertTrue(closeStarted.await(5, TimeUnit.SECONDS));
+
+ Assert.assertFalse(shuffleSinkHandle.abort());
+ allowCloseToFinish.countDown();
+
+ Assert.assertTrue(closeResult.get(5, TimeUnit.SECONDS));
+ Assert.assertTrue(shuffleSinkHandle.isClosed());
+ Assert.assertFalse(shuffleSinkHandle.isAborted());
+ Mockito.verify(channel).close();
+ Mockito.verify(channel, Mockito.never()).abort();
+ Mockito.verify(sinkListener).onFinish(shuffleSinkHandle);
+ Mockito.verify(sinkListener,
Mockito.never()).onAborted(shuffleSinkHandle);
+ } finally {
+ allowCloseToFinish.countDown();
+ executor.shutdownNow();
+ }
+ }
+
+ @Test
+ public void testAbortPreventsConcurrentClose() throws Exception {
+ TFragmentInstanceId fragmentInstanceId = new TFragmentInstanceId("q0", 0,
"0");
+ ISinkChannel channel = Mockito.mock(ISinkChannel.class);
+ MPPDataExchangeManager.SinkListener sinkListener =
+ Mockito.mock(MPPDataExchangeManager.SinkListener.class);
+ CountDownLatch abortStarted = new CountDownLatch(1);
+ CountDownLatch allowAbortToFinish = new CountDownLatch(1);
+ Mockito.when(channel.abort())
+ .thenAnswer(
+ invocation -> {
+ abortStarted.countDown();
+ Assert.assertTrue(allowAbortToFinish.await(5, TimeUnit.SECONDS));
+ return true;
+ });
+
+ ShuffleSinkHandle shuffleSinkHandle =
+ new ShuffleSinkHandle(
+ fragmentInstanceId,
+ Collections.singletonList(channel),
+ new DownStreamChannelIndex(0),
+ ShuffleSinkHandle.ShuffleStrategyEnum.PLAIN,
+ sinkListener);
+ ExecutorService executor = Executors.newSingleThreadExecutor();
+
+ try {
+ Future<Boolean> abortResult = executor.submit(shuffleSinkHandle::abort);
+ Assert.assertTrue(abortStarted.await(5, TimeUnit.SECONDS));
+
+ Assert.assertFalse(shuffleSinkHandle.close());
+ allowAbortToFinish.countDown();
+
+ Assert.assertTrue(abortResult.get(5, TimeUnit.SECONDS));
+ Assert.assertTrue(shuffleSinkHandle.isAborted());
+ Assert.assertFalse(shuffleSinkHandle.isClosed());
+ Mockito.verify(channel).abort();
+ Mockito.verify(channel, Mockito.never()).close();
+ Mockito.verify(sinkListener).onAborted(shuffleSinkHandle);
+ Mockito.verify(sinkListener,
Mockito.never()).onFinish(shuffleSinkHandle);
+ } finally {
+ allowAbortToFinish.countDown();
+ executor.shutdownNow();
+ }
+ }
+
@Test
public void testAbort() {
final String queryId = "q0";
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/execution/exchange/SinkChannelFailurePropagationTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/execution/exchange/SinkChannelFailurePropagationTest.java
new file mode 100644
index 00000000000..9564b104766
--- /dev/null
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/execution/exchange/SinkChannelFailurePropagationTest.java
@@ -0,0 +1,364 @@
+/*
+ * 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.iotdb.db.queryengine.execution.exchange;
+
+import org.apache.iotdb.common.rpc.thrift.TDataNodeLocation;
+import org.apache.iotdb.common.rpc.thrift.TEndPoint;
+import org.apache.iotdb.commons.client.IClientManager;
+import org.apache.iotdb.commons.client.sync.SyncDataNodeInternalServiceClient;
+import
org.apache.iotdb.commons.client.sync.SyncDataNodeMPPDataExchangeServiceClient;
+import org.apache.iotdb.db.queryengine.common.FragmentInstanceId;
+import org.apache.iotdb.db.queryengine.common.PlanFragmentId;
+import org.apache.iotdb.db.queryengine.common.QueryId;
+import org.apache.iotdb.db.queryengine.execution.QueryState;
+import org.apache.iotdb.db.queryengine.execution.QueryStateMachine;
+import
org.apache.iotdb.db.queryengine.execution.exchange.sink.DownStreamChannelIndex;
+import
org.apache.iotdb.db.queryengine.execution.exchange.sink.DownStreamChannelLocation;
+import org.apache.iotdb.db.queryengine.execution.exchange.sink.ISink;
+import org.apache.iotdb.db.queryengine.execution.exchange.sink.ISinkHandle;
+import
org.apache.iotdb.db.queryengine.execution.exchange.sink.ShuffleSinkHandle;
+import org.apache.iotdb.db.queryengine.execution.exchange.sink.SinkChannel;
+import
org.apache.iotdb.db.queryengine.execution.fragment.FragmentInstanceContext;
+import
org.apache.iotdb.db.queryengine.execution.fragment.FragmentInstanceFailureInfo;
+import org.apache.iotdb.db.queryengine.execution.fragment.FragmentInstanceInfo;
+import
org.apache.iotdb.db.queryengine.execution.fragment.FragmentInstanceState;
+import
org.apache.iotdb.db.queryengine.execution.fragment.FragmentInstanceStateMachine;
+import org.apache.iotdb.db.queryengine.execution.memory.LocalMemoryManager;
+import org.apache.iotdb.db.queryengine.execution.memory.MemoryPool;
+import org.apache.iotdb.db.queryengine.plan.planner.plan.FragmentInstance;
+import
org.apache.iotdb.db.queryengine.plan.scheduler.FixedRateFragInsStateTracker;
+import org.apache.iotdb.mpp.rpc.thrift.TFetchFragmentInstanceInfoReq;
+import org.apache.iotdb.mpp.rpc.thrift.TFragmentInstanceId;
+import org.apache.iotdb.mpp.rpc.thrift.TFragmentInstanceInfoResp;
+import org.apache.iotdb.mpp.rpc.thrift.TNewDataBlockEvent;
+
+import org.apache.thrift.TApplicationException;
+import org.apache.thrift.TException;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class SinkChannelFailurePropagationTest {
+
+ @Test
+ public void testFailureCallbackRunsBeforeChannelAccounting() {
+ ExecutorService exchangeExecutor = Executors.newSingleThreadExecutor();
+ try {
+ MPPDataExchangeManager exchangeManager =
+ new MPPDataExchangeManager(
+ Mockito.mock(LocalMemoryManager.class),
+ new TsBlockSerdeFactory(),
+ exchangeExecutor,
+ Mockito.mock(IClientManager.class));
+ AtomicInteger remainingChannels = new AtomicInteger(1);
+ AtomicBoolean hasChannelFailedOrAborted = new AtomicBoolean(false);
+ AtomicBoolean callbackInvoked = new AtomicBoolean(false);
+ IllegalStateException callbackFailure =
+ new IllegalStateException("injected callback failure");
+ MPPDataExchangeManager.ISinkChannelListenerImpl listener =
+ exchangeManager
+ .new ISinkChannelListenerImpl(
+ new TFragmentInstanceId("q_failure_callback_order", 0, "0"),
+ Mockito.mock(FragmentInstanceContext.class),
+ failure -> {
+ Assert.assertEquals(1, remainingChannels.get());
+ callbackInvoked.set(true);
+ throw callbackFailure;
+ },
+ remainingChannels,
+ hasChannelFailedOrAborted);
+
+ IllegalStateException actualFailure =
+ Assert.assertThrows(
+ IllegalStateException.class,
+ () ->
+ listener.onFailure(
+ Mockito.mock(ISink.class), new TException("injected
channel failure")));
+
+ Assert.assertSame(callbackFailure, actualFailure);
+ Assert.assertTrue(callbackInvoked.get());
+ Assert.assertTrue(hasChannelFailedOrAborted.get());
+ Assert.assertEquals(0, remainingChannels.get());
+ } finally {
+ exchangeExecutor.shutdownNow();
+ }
+ }
+
+ @Test
+ public void
testApplicationExceptionFailsWithoutRetryAndPreventsNormalClose() throws
Exception {
+ final String queryId = "q_failure_with_two_channels";
+ final TFragmentInstanceId upstreamThriftId = new
TFragmentInstanceId(queryId, 1, "0");
+ final FragmentInstanceId upstreamId =
+ new FragmentInstanceId(new PlanFragmentId(new QueryId(queryId), 1),
"0");
+ final TEndPoint firstEndpoint = new TEndPoint("remote-exchange-0", 10740);
+ final TEndPoint secondEndpoint = new TEndPoint("remote-exchange-1", 10740);
+
+ ExecutorService exchangeExecutor = Executors.newSingleThreadExecutor();
+ ExecutorService fragmentNotificationExecutor =
Executors.newSingleThreadExecutor();
+
+ try {
+ LocalMemoryManager localMemoryManager =
Mockito.mock(LocalMemoryManager.class);
+ MemoryPool memoryPool = Utils.createMockNonBlockedMemoryPool();
+ Mockito.when(localMemoryManager.getQueryPool()).thenReturn(memoryPool);
+
+ IClientManager<TEndPoint, SyncDataNodeMPPDataExchangeServiceClient>
exchangeClientManager =
+ Mockito.mock(IClientManager.class);
+ SyncDataNodeMPPDataExchangeServiceClient exchangeClient =
+ Mockito.mock(SyncDataNodeMPPDataExchangeServiceClient.class);
+ TException expectedFailure =
+ new TException(
+ "wrapped application failure",
+ new TException(
+ "wrapped receive failure",
+ new TApplicationException(
+ TApplicationException.INTERNAL_ERROR,
+ "Internal error processing onNewDataBlockEvent")));
+
Mockito.when(exchangeClientManager.borrowClient(secondEndpoint)).thenReturn(exchangeClient);
+ Mockito.doThrow(expectedFailure)
+ .when(exchangeClient)
+ .onNewDataBlockEvent(Mockito.any(TNewDataBlockEvent.class));
+
+ FragmentInstanceStateMachine fragmentStateMachine =
+ new FragmentInstanceStateMachine(upstreamId,
fragmentNotificationExecutor);
+ FragmentInstanceContext fragmentContext =
+ FragmentInstanceContext.createFragmentInstanceContext(upstreamId,
fragmentStateMachine);
+ MPPDataExchangeManager exchangeManager =
+ new MPPDataExchangeManager(
+ localMemoryManager,
+ new TsBlockSerdeFactory(),
+ exchangeExecutor,
+ exchangeClientManager);
+ ISinkHandle sinkHandle =
+ exchangeManager.createShuffleSinkHandle(
+ Arrays.asList(
+ new DownStreamChannelLocation(
+ firstEndpoint, new TFragmentInstanceId(queryId, 0, "0"),
"root-exchange-0"),
+ new DownStreamChannelLocation(
+ secondEndpoint, new TFragmentInstanceId(queryId, 0,
"1"), "root-exchange-1")),
+ new DownStreamChannelIndex(1),
+ ShuffleSinkHandle.ShuffleStrategyEnum.PLAIN,
+ upstreamThriftId,
+ "upstream-sink",
+ fragmentContext);
+ SinkChannel normallyFinishedChannel = (SinkChannel)
sinkHandle.getChannel(0);
+ SinkChannel failedChannel = (SinkChannel) sinkHandle.getChannel(1);
+ failedChannel.setRetryIntervalInMs(0);
+
+ Assert.assertTrue(sinkHandle.isFull().isDone());
+ sinkHandle.send(Utils.createMockTsBlocks(1, 1024).get(0));
+ exchangeExecutor.submit(() -> {}).get(5, TimeUnit.SECONDS);
+ Mockito.verify(exchangeClient, Mockito.times(1))
+ .onNewDataBlockEvent(Mockito.any(TNewDataBlockEvent.class));
+ long waitStartNanos = System.nanoTime();
+ while (!fragmentStateMachine.getState().isDone()
+ && TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() -
waitStartNanos) < 5) {
+ Thread.sleep(10);
+ }
+
+ Assert.assertEquals(FragmentInstanceState.FAILED,
fragmentStateMachine.getState());
+ Assert.assertEquals(expectedFailure,
fragmentStateMachine.getFailureCauses().peek());
+ Assert.assertTrue(normallyFinishedChannel.close());
+
+ Assert.assertEquals(0, exchangeManager.getShuffleSinkHandleSize());
+ Assert.assertFalse(sinkHandle.isClosed());
+ Assert.assertFalse(sinkHandle.isAborted());
+
+ Assert.assertTrue(sinkHandle.abort());
+ Assert.assertTrue(sinkHandle.isAborted());
+ Assert.assertFalse(sinkHandle.isClosed());
+ Assert.assertTrue(failedChannel.isAborted());
+ } finally {
+ exchangeExecutor.shutdownNow();
+ fragmentNotificationExecutor.shutdownNow();
+ }
+ }
+
+ @Test
+ public void testSingleChannelFailurePropagation() throws Exception {
+ final String queryId = "q_failure_propagation";
+ final TFragmentInstanceId upstreamThriftId = new
TFragmentInstanceId(queryId, 1, "0");
+ final TFragmentInstanceId rootThriftId = new TFragmentInstanceId(queryId,
0, "0");
+ final FragmentInstanceId upstreamId =
+ new FragmentInstanceId(new PlanFragmentId(new QueryId(queryId), 1),
"0");
+ final FragmentInstanceId rootId =
+ new FragmentInstanceId(new PlanFragmentId(new QueryId(queryId), 0),
"0");
+ final TEndPoint exchangeEndpoint = new TEndPoint("remote-exchange", 10740);
+ final TEndPoint stateEndpoint = new TEndPoint("remote-state", 10730);
+
+ ExecutorService exchangeExecutor = Executors.newSingleThreadExecutor();
+ ExecutorService fragmentNotificationExecutor =
Executors.newSingleThreadExecutor();
+ ExecutorService queryNotificationExecutor =
Executors.newSingleThreadExecutor();
+ ScheduledExecutorService stateTrackerExecutor =
Executors.newSingleThreadScheduledExecutor();
+ FixedRateFragInsStateTracker stateTracker = null;
+
+ try {
+ LocalMemoryManager localMemoryManager =
Mockito.mock(LocalMemoryManager.class);
+ MemoryPool memoryPool = Utils.createMockNonBlockedMemoryPool();
+ Mockito.when(localMemoryManager.getQueryPool()).thenReturn(memoryPool);
+
+ IClientManager<TEndPoint, SyncDataNodeMPPDataExchangeServiceClient>
exchangeClientManager =
+ Mockito.mock(IClientManager.class);
+ SyncDataNodeMPPDataExchangeServiceClient exchangeClient =
+ Mockito.mock(SyncDataNodeMPPDataExchangeServiceClient.class);
+ TException expectedFailure = new TException("injected
onNewDataBlockEvent failure");
+
Mockito.when(exchangeClientManager.borrowClient(exchangeEndpoint)).thenReturn(exchangeClient);
+ Mockito.doThrow(expectedFailure)
+ .when(exchangeClient)
+ .onNewDataBlockEvent(Mockito.any(TNewDataBlockEvent.class));
+
+ FragmentInstanceStateMachine fragmentStateMachine =
+ new FragmentInstanceStateMachine(upstreamId,
fragmentNotificationExecutor);
+ FragmentInstanceContext fragmentContext =
+ FragmentInstanceContext.createFragmentInstanceContext(upstreamId,
fragmentStateMachine);
+
+ MPPDataExchangeManager exchangeManager =
+ new MPPDataExchangeManager(
+ localMemoryManager,
+ new TsBlockSerdeFactory(),
+ exchangeExecutor,
+ exchangeClientManager);
+ ISinkHandle sinkHandle =
+ exchangeManager.createShuffleSinkHandle(
+ Collections.singletonList(
+ new DownStreamChannelLocation(exchangeEndpoint,
rootThriftId, "root-exchange")),
+ new DownStreamChannelIndex(0),
+ ShuffleSinkHandle.ShuffleStrategyEnum.PLAIN,
+ upstreamThriftId,
+ "upstream-sink",
+ fragmentContext);
+ SinkChannel sinkChannel = (SinkChannel) sinkHandle.getChannel(0);
+ sinkChannel.setRetryIntervalInMs(0);
+ fragmentStateMachine.addStateChangeListener(
+ newState -> {
+ if (newState.isFailed()) {
+ sinkHandle.abort();
+ } else if (newState.isDone()) {
+ sinkHandle.close();
+ }
+ });
+
+ QueryStateMachine queryStateMachine =
+ new QueryStateMachine(new QueryId(queryId),
queryNotificationExecutor);
+ queryStateMachine.transitionToRunning();
+
+ FragmentInstance upstreamInstance = Mockito.mock(FragmentInstance.class);
+ FragmentInstance rootInstance = Mockito.mock(FragmentInstance.class);
+ TDataNodeLocation stateLocation =
+ new
TDataNodeLocation().setDataNodeId(1).setInternalEndPoint(stateEndpoint);
+ Mockito.when(upstreamInstance.getId()).thenReturn(upstreamId);
+
Mockito.when(upstreamInstance.getHostDataNode()).thenReturn(stateLocation);
+ Mockito.when(upstreamInstance.isRoot()).thenReturn(false);
+ Mockito.when(rootInstance.getId()).thenReturn(rootId);
+ Mockito.when(rootInstance.getHostDataNode()).thenReturn(stateLocation);
+ Mockito.when(rootInstance.isRoot()).thenReturn(true);
+
+ IClientManager<TEndPoint, SyncDataNodeInternalServiceClient>
stateClientManager =
+ Mockito.mock(IClientManager.class);
+ SyncDataNodeInternalServiceClient stateClient =
+ Mockito.mock(SyncDataNodeInternalServiceClient.class);
+
Mockito.when(stateClientManager.borrowClient(stateEndpoint)).thenReturn(stateClient);
+ Mockito.when(
+ stateClient.fetchFragmentInstanceInfo(
+ Mockito.any(TFetchFragmentInstanceInfoReq.class)))
+ .thenAnswer(
+ invocation -> {
+ TFetchFragmentInstanceInfoReq request =
invocation.getArgument(0);
+ if (upstreamThriftId.equals(request.getFragmentInstanceId())) {
+ return toThriftResponse(fragmentContext.getInstanceInfo());
+ }
+ return new
TFragmentInstanceInfoResp(FragmentInstanceState.RUNNING.toString());
+ });
+
+ stateTracker =
+ new FixedRateFragInsStateTracker(
+ queryStateMachine,
+ stateTrackerExecutor,
+ Arrays.asList(upstreamInstance, rootInstance),
+ stateClientManager);
+ stateTracker.start();
+
+ Assert.assertTrue(sinkHandle.isFull().isDone());
+ long failureStartNanos = System.nanoTime();
+ sinkHandle.send(Utils.createMockTsBlocks(1, 1024).get(0));
+
+ Mockito.verify(exchangeClient,
Mockito.timeout(5_000).times(SinkChannel.MAX_ATTEMPT_TIMES))
+ .onNewDataBlockEvent(Mockito.any(TNewDataBlockEvent.class));
+ while (!fragmentStateMachine.getState().isDone()
+ && TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() -
failureStartNanos) < 5) {
+ Thread.sleep(10);
+ }
+
+ Assert.assertEquals(FragmentInstanceState.FAILED,
fragmentStateMachine.getState());
+ Assert.assertEquals(expectedFailure,
fragmentStateMachine.getFailureCauses().peek());
+ while (!sinkChannel.isAborted()
+ && TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() -
failureStartNanos) < 5) {
+ Thread.sleep(10);
+ }
+ Assert.assertTrue(sinkHandle.isAborted());
+ Assert.assertFalse(sinkHandle.isClosed());
+ Assert.assertTrue(sinkChannel.isAborted());
+ Assert.assertFalse(sinkChannel.isClosed());
+ Assert.assertEquals(0, exchangeManager.getShuffleSinkHandleSize());
+
+ while (!queryStateMachine.getState().isDone()
+ && TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() -
failureStartNanos) < 5) {
+ Thread.sleep(10);
+ }
+ Mockito.verify(stateClient, Mockito.timeout(3_000).atLeastOnce())
+
.fetchFragmentInstanceInfo(Mockito.any(TFetchFragmentInstanceInfoReq.class));
+ Assert.assertEquals(QueryState.FAILED, queryStateMachine.getState());
+ } finally {
+ if (stateTracker != null) {
+ stateTracker.abort();
+ }
+ exchangeExecutor.shutdownNow();
+ fragmentNotificationExecutor.shutdownNow();
+ queryNotificationExecutor.shutdownNow();
+ stateTrackerExecutor.shutdownNow();
+ }
+ }
+
+ private static TFragmentInstanceInfoResp
toThriftResponse(FragmentInstanceInfo info)
+ throws IOException {
+ TFragmentInstanceInfoResp response = new
TFragmentInstanceInfoResp(info.getState().toString());
+ response.setEndTime(info.getEndTime());
+ response.setFailedMessages(Collections.singletonList(info.getMessage()));
+ List<ByteBuffer> failureInfoList = new ArrayList<>();
+ for (FragmentInstanceFailureInfo failureInfo : info.getFailureInfoList()) {
+ failureInfoList.add(failureInfo.serialize());
+ }
+ response.setFailureInfoList(failureInfoList);
+ info.getErrorCode().ifPresent(response::setErrorCode);
+ return response;
+ }
+}