This is an automated email from the ASF dual-hosted git repository.
jt2594838 pushed a commit to branch dev/1.3
in repository https://gitbox.apache.org/repos/asf/iotdb.git
The following commit(s) were added to refs/heads/dev/1.3 by this push:
new 79c94598031 [To dev/1.3] Pipe: Handle transient resource failures
locally (#18381) (#18430)
79c94598031 is described below
commit 79c945980319bf0c9b7208d9b0e946d634093bf3
Author: Caideyipi <[email protected]>
AuthorDate: Wed Aug 12 09:32:47 2026 +0800
[To dev/1.3] Pipe: Handle transient resource failures locally (#18381)
(#18430)
* Pipe: Handle transient resource failures locally (#18381)
* Fix pipe auto-drop IT expected header
---
.../pipe/it/autocreate/IoTDBPipeAutoDropIT.java | 2 +-
.../heartbeat/DataNodeHeartbeatHandler.java | 3 +-
.../response/pipe/task/PipeTableResp.java | 1 +
.../iotdb/confignode/manager/ConfigManager.java | 3 +-
.../pipe/agent/task/PipeConfigNodeSubtask.java | 8 ++
.../pipe/agent/task/PipeConfigNodeTaskAgent.java | 5 ++
.../runtime/PipeRuntimeCoordinator.java | 7 +-
.../runtime/heartbeat/PipeHeartbeat.java | 27 +++++++
.../runtime/heartbeat/PipeHeartbeatParser.java | 1 +
.../runtime/heartbeat/PipeHeartbeatScheduler.java | 6 +-
.../consensus/response/pipe/PipeTableRespTest.java | 21 +++++
.../runtime/heartbeat/PipeHeartbeatParserTest.java | 55 +++++++++++++
.../db/pipe/agent/task/PipeDataNodeTaskAgent.java | 9 +++
.../subtask/processor/PipeProcessorSubtask.java | 12 +++
.../agent/task/subtask/sink/PipeSinkSubtask.java | 8 ++
.../thrift/async/IoTDBDataRegionAsyncSink.java | 57 ++++++++++++-
.../common/header/ColumnHeaderConstant.java | 4 +-
.../execution/config/sys/pipe/ShowPipeTask.java | 9 +++
.../task/subtask/sink/PipeSinkSubtaskTest.java | 49 +++++++++++
.../config/sys/pipe/ShowPipeTaskTest.java | 73 +++++++++++++++++
.../pipe/PipeRuntimeSinkResourceException.java | 51 ++++++++++++
.../commons/pipe/agent/task/PipeTaskAgent.java | 10 +++
.../agent/task/meta/PipeTemporaryMetaInAgent.java | 16 +++-
.../task/meta/PipeTemporaryMetaInCoordinator.java | 84 ++++++++++++++++++-
.../task/subtask/PipeAbstractSinkSubtask.java | 28 +++++--
.../pipe/receiver/PipeReceiverStatusHandler.java | 21 ++---
.../pipe/resource/PipeRecentFailureCounter.java | 94 ++++++++++++++++++++++
.../pipe/resource/PipeResourceFailureType.java | 36 +++++++++
.../commons/pipe/resource/PipeStopStrategy.java | 76 +++++++++++++++++
.../agent/task/meta/PipeTemporaryMetaTest.java | 58 +++++++++++++
.../resource/PipeRecentFailureCounterTest.java | 48 +++++++++++
.../pipe/resource/PipeStopStrategyTest.java | 83 +++++++++++++++++++
.../thrift-commons/src/main/thrift/common.thrift | 1 +
.../src/main/thrift/confignode.thrift | 2 +-
.../src/main/thrift/datanode.thrift | 3 +-
35 files changed, 939 insertions(+), 32 deletions(-)
diff --git
a/integration-test/src/test/java/org/apache/iotdb/pipe/it/autocreate/IoTDBPipeAutoDropIT.java
b/integration-test/src/test/java/org/apache/iotdb/pipe/it/autocreate/IoTDBPipeAutoDropIT.java
index 09657e2deb2..8f3ca8895c7 100644
---
a/integration-test/src/test/java/org/apache/iotdb/pipe/it/autocreate/IoTDBPipeAutoDropIT.java
+++
b/integration-test/src/test/java/org/apache/iotdb/pipe/it/autocreate/IoTDBPipeAutoDropIT.java
@@ -145,7 +145,7 @@ public class IoTDBPipeAutoDropIT extends
AbstractPipeDualAutoIT {
TestUtils.assertDataEventuallyOnEnv(
senderEnv,
"show pipes",
-
"ID,CreationTime,State,PipeSource,PipeProcessor,PipeSink,ExceptionMessage,RemainingEventCount,EstimatedRemainingSeconds,",
+
"ID,CreationTime,State,PipeSource,PipeProcessor,PipeSink,ExceptionMessage,RemainingEventCount,EstimatedRemainingSeconds,RecentFailures,",
Collections.emptySet());
}
}
diff --git
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/handlers/heartbeat/DataNodeHeartbeatHandler.java
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/handlers/heartbeat/DataNodeHeartbeatHandler.java
index 3613c337862..0acfb9ec7c2 100644
---
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/handlers/heartbeat/DataNodeHeartbeatHandler.java
+++
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/handlers/heartbeat/DataNodeHeartbeatHandler.java
@@ -147,7 +147,8 @@ public class DataNodeHeartbeatHandler implements
AsyncMethodCallback<TDataNodeHe
heartbeatResp.getPipeMetaList(),
heartbeatResp.getPipeCompletedList(),
heartbeatResp.getPipeRemainingEventCountList(),
- heartbeatResp.getPipeRemainingTimeList());
+ heartbeatResp.getPipeRemainingTimeList(),
+ heartbeatResp.getPipeRecentFailureList());
}
if (heartbeatResp.isSetConfirmedConfigNodeEndPoints()) {
loadManager
diff --git
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/consensus/response/pipe/task/PipeTableResp.java
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/consensus/response/pipe/task/PipeTableResp.java
index f7a76b6174c..9293a57c208 100644
---
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/consensus/response/pipe/task/PipeTableResp.java
+++
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/consensus/response/pipe/task/PipeTableResp.java
@@ -204,6 +204,7 @@ public class PipeTableResp implements DataSet {
canCalculateOnLocal ? -1 : temporaryMeta.getGlobalRemainingEvents());
showPipeInfo.setEstimatedRemainingTime(
canCalculateOnLocal ? -1 : temporaryMeta.getGlobalRemainingTime());
+ showPipeInfo.setRecentFailures(temporaryMeta.getGlobalRecentFailures());
showPipeInfoList.add(showPipeInfo);
}
diff --git
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ConfigManager.java
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ConfigManager.java
index 2d34c20e8ae..61819bd6509 100644
---
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ConfigManager.java
+++
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ConfigManager.java
@@ -2576,7 +2576,8 @@ public class ConfigManager implements IManager {
resp.getPipeMetaList(),
resp.getPipeCompletedList(),
resp.getPipeRemainingEventCountList(),
- resp.getPipeRemainingTimeList());
+ resp.getPipeRemainingTimeList(),
+ resp.getPipeRecentFailureList());
return StatusUtils.OK;
}
diff --git
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/agent/task/PipeConfigNodeSubtask.java
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/agent/task/PipeConfigNodeSubtask.java
index 6daf3160c0c..bd11e27d9ee 100644
---
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/agent/task/PipeConfigNodeSubtask.java
+++
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/agent/task/PipeConfigNodeSubtask.java
@@ -32,6 +32,7 @@ import
org.apache.iotdb.commons.pipe.config.plugin.env.PipeTaskSinkRuntimeEnviro
import
org.apache.iotdb.commons.pipe.config.plugin.env.PipeTaskSourceRuntimeEnvironment;
import org.apache.iotdb.commons.pipe.event.EnrichedEvent;
import org.apache.iotdb.commons.pipe.event.ProgressReportEvent;
+import org.apache.iotdb.commons.pipe.resource.PipeResourceFailureType;
import org.apache.iotdb.confignode.manager.pipe.agent.PipeConfigNodeAgent;
import
org.apache.iotdb.confignode.manager.pipe.metric.sink.PipeConfigRegionSinkMetrics;
import org.apache.iotdb.confignode.manager.pipe.source.IoTDBConfigRegionSource;
@@ -240,6 +241,13 @@ public class PipeConfigNodeSubtask extends
PipeAbstractSinkSubtask {
PipeConfigNodeAgent.runtime().report(event, exception);
}
+ @Override
+ protected void reportResourceFailure(
+ final EnrichedEvent event, final PipeResourceFailureType failureType) {
+ PipeConfigNodeAgent.task()
+ .recordPipeResourceFailure(event.getPipeName(),
event.getCreationTime(), failureType);
+ }
+
//////////////////////////// APIs provided for metric framework
////////////////////////////
public String getPipeName() {
diff --git
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/agent/task/PipeConfigNodeTaskAgent.java
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/agent/task/PipeConfigNodeTaskAgent.java
index 3fe0b1757cf..d60b8b3b4c7 100644
---
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/agent/task/PipeConfigNodeTaskAgent.java
+++
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/agent/task/PipeConfigNodeTaskAgent.java
@@ -28,6 +28,7 @@ import org.apache.iotdb.commons.pipe.agent.task.PipeTaskAgent;
import org.apache.iotdb.commons.pipe.agent.task.meta.PipeMeta;
import org.apache.iotdb.commons.pipe.agent.task.meta.PipeStaticMeta;
import org.apache.iotdb.commons.pipe.agent.task.meta.PipeTaskMeta;
+import org.apache.iotdb.commons.pipe.agent.task.meta.PipeTemporaryMetaInAgent;
import org.apache.iotdb.commons.pipe.config.PipeConfig;
import org.apache.iotdb.confignode.conf.ConfigNodeDescriptor;
import org.apache.iotdb.confignode.manager.pipe.agent.PipeConfigNodeAgent;
@@ -218,6 +219,7 @@ public class PipeConfigNodeTaskAgent extends PipeTaskAgent {
final List<ByteBuffer> pipeMetaBinaryList = new ArrayList<>();
final List<Long> pipeRemainingEventCountList = new ArrayList<>();
final List<Double> pipeRemainingTimeList = new ArrayList<>();
+ final List<Map<String, Long>> pipeRecentFailureList = new ArrayList<>();
try {
for (final PipeMeta pipeMeta : pipeMetaKeeper.getPipeMetaList()) {
pipeMetaBinaryList.add(pipeMeta.serialize());
@@ -232,6 +234,8 @@ public class PipeConfigNodeTaskAgent extends PipeTaskAgent {
pipeRemainingEventCountList.add(remainingEventCount);
pipeRemainingTimeList.add(estimatedRemainingTime);
+ pipeRecentFailureList.add(
+ ((PipeTemporaryMetaInAgent)
pipeMeta.getTemporaryMeta()).getRecentFailures());
logger.ifPresent(
l ->
@@ -248,6 +252,7 @@ public class PipeConfigNodeTaskAgent extends PipeTaskAgent {
resp.setPipeMetaList(pipeMetaBinaryList);
resp.setPipeRemainingEventCountList(pipeRemainingEventCountList);
resp.setPipeRemainingTimeList(pipeRemainingTimeList);
+ resp.setPipeRecentFailureList(pipeRecentFailureList);
}
@Override
diff --git
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/PipeRuntimeCoordinator.java
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/PipeRuntimeCoordinator.java
index eae99126523..83ee13d6407 100644
---
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/PipeRuntimeCoordinator.java
+++
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/PipeRuntimeCoordinator.java
@@ -31,6 +31,7 @@ import
org.apache.iotdb.confignode.manager.pipe.coordinator.runtime.heartbeat.Pi
import java.nio.ByteBuffer;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicReference;
@@ -107,13 +108,15 @@ public class PipeRuntimeCoordinator implements
IClusterStatusSubscriber {
final List<ByteBuffer> pipeMetaByteBufferListFromDataNode,
/* @Nullable */ final List<Boolean> pipeCompletedListFromAgent,
/* @Nullable */ final List<Long> pipeRemainingEventCountListFromAgent,
- /* @Nullable */ final List<Double> pipeRemainingTimeListFromAgent) {
+ /* @Nullable */ final List<Double> pipeRemainingTimeListFromAgent,
+ /* @Nullable */ final List<Map<String, Long>>
pipeRecentFailureListFromAgent) {
pipeHeartbeatScheduler.parseHeartbeat(
dataNodeId,
new PipeHeartbeat(
pipeMetaByteBufferListFromDataNode,
pipeCompletedListFromAgent,
pipeRemainingEventCountListFromAgent,
- pipeRemainingTimeListFromAgent));
+ pipeRemainingTimeListFromAgent,
+ pipeRecentFailureListFromAgent));
}
}
diff --git
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/heartbeat/PipeHeartbeat.java
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/heartbeat/PipeHeartbeat.java
index e7e9d2cd97d..933e7f04e1d 100644
---
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/heartbeat/PipeHeartbeat.java
+++
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/heartbeat/PipeHeartbeat.java
@@ -23,6 +23,7 @@ import org.apache.iotdb.commons.pipe.agent.task.meta.PipeMeta;
import org.apache.iotdb.commons.pipe.agent.task.meta.PipeStaticMeta;
import java.nio.ByteBuffer;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -33,12 +34,27 @@ public class PipeHeartbeat {
private final Map<PipeStaticMeta, Boolean> isCompletedMap = new HashMap<>();
private final Map<PipeStaticMeta, Long> remainingEventCountMap = new
HashMap<>();
private final Map<PipeStaticMeta, Double> remainingTimeMap = new HashMap<>();
+ private final Map<PipeStaticMeta, Map<String, Long>> recentFailuresMap = new
HashMap<>();
public PipeHeartbeat(
final List<ByteBuffer> pipeMetaByteBufferListFromAgent,
/* @Nullable */ final List<Boolean> pipeCompletedListFromAgent,
/* @Nullable */ final List<Long> pipeRemainingEventCountListFromAgent,
/* @Nullable */ final List<Double> pipeRemainingTimeListFromAgent) {
+ this(
+ pipeMetaByteBufferListFromAgent,
+ pipeCompletedListFromAgent,
+ pipeRemainingEventCountListFromAgent,
+ pipeRemainingTimeListFromAgent,
+ null);
+ }
+
+ public PipeHeartbeat(
+ final List<ByteBuffer> pipeMetaByteBufferListFromAgent,
+ /* @Nullable */ final List<Boolean> pipeCompletedListFromAgent,
+ /* @Nullable */ final List<Long> pipeRemainingEventCountListFromAgent,
+ /* @Nullable */ final List<Double> pipeRemainingTimeListFromAgent,
+ /* @Nullable */ final List<Map<String, Long>>
pipeRecentFailureListFromAgent) {
// Shall not reach here, just in case
if (Objects.isNull(pipeMetaByteBufferListFromAgent)) {
return;
@@ -63,6 +79,13 @@ public class PipeHeartbeat {
Objects.nonNull(pipeRemainingTimeListFromAgent)
? pipeRemainingTimeListFromAgent.get(i)
: 0d);
+ recentFailuresMap.put(
+ pipeMeta.getStaticMeta(),
+ Objects.nonNull(pipeRecentFailureListFromAgent)
+ && i < pipeRecentFailureListFromAgent.size()
+ && Objects.nonNull(pipeRecentFailureListFromAgent.get(i))
+ ? new HashMap<>(pipeRecentFailureListFromAgent.get(i))
+ : Collections.emptyMap());
}
}
@@ -86,6 +109,10 @@ public class PipeHeartbeat {
return remainingTimeMap.get(pipeStaticMeta);
}
+ public Map<String, Long> getRecentFailures(final PipeStaticMeta
pipeStaticMeta) {
+ return recentFailuresMap.get(pipeStaticMeta);
+ }
+
public boolean isEmpty() {
return pipeMetaMap.isEmpty();
}
diff --git
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/heartbeat/PipeHeartbeatParser.java
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/heartbeat/PipeHeartbeatParser.java
index e67a832ea3e..55bb018f6d0 100644
---
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/heartbeat/PipeHeartbeatParser.java
+++
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/heartbeat/PipeHeartbeatParser.java
@@ -191,6 +191,7 @@ public class PipeHeartbeatParser {
// Record statistics
temporaryMeta.setRemainingEvent(nodeId,
pipeHeartbeat.getRemainingEventCount(staticMeta));
temporaryMeta.setRemainingTime(nodeId,
pipeHeartbeat.getRemainingTime(staticMeta));
+ temporaryMeta.setRecentFailures(nodeId,
pipeHeartbeat.getRecentFailures(staticMeta));
final Map<Integer, PipeTaskMeta> pipeTaskMetaMapFromCoordinator =
pipeMetaFromCoordinator.getRuntimeMeta().getConsensusGroupId2TaskMetaMap();
diff --git
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/heartbeat/PipeHeartbeatScheduler.java
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/heartbeat/PipeHeartbeatScheduler.java
index d4611bd2f2e..9e41ab771f5 100644
---
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/heartbeat/PipeHeartbeatScheduler.java
+++
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/heartbeat/PipeHeartbeatScheduler.java
@@ -112,7 +112,8 @@ public class PipeHeartbeatScheduler {
resp.getPipeMetaList(),
resp.getPipeCompletedList(),
resp.getPipeRemainingEventCountList(),
- resp.getPipeRemainingTimeList())));
+ resp.getPipeRemainingTimeList(),
+ resp.getPipeRecentFailureList())));
// config node heartbeat
try {
@@ -124,7 +125,8 @@ public class PipeHeartbeatScheduler {
configNodeResp.getPipeMetaList(),
null,
configNodeResp.getPipeRemainingEventCountList(),
- configNodeResp.getPipeRemainingTimeList()));
+ configNodeResp.getPipeRemainingTimeList(),
+ configNodeResp.getPipeRecentFailureList()));
} catch (final Exception e) {
LOGGER.warn("Failed to collect pipe meta list from config node task
agent", e);
}
diff --git
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/consensus/response/pipe/PipeTableRespTest.java
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/consensus/response/pipe/PipeTableRespTest.java
index 072f98a7500..068f85a0a3d 100644
---
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/consensus/response/pipe/PipeTableRespTest.java
+++
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/consensus/response/pipe/PipeTableRespTest.java
@@ -25,9 +25,11 @@ import
org.apache.iotdb.commons.pipe.agent.task.meta.PipeRuntimeMeta;
import org.apache.iotdb.commons.pipe.agent.task.meta.PipeStaticMeta;
import org.apache.iotdb.commons.pipe.agent.task.meta.PipeStatus;
import org.apache.iotdb.commons.pipe.agent.task.meta.PipeTaskMeta;
+import
org.apache.iotdb.commons.pipe.agent.task.meta.PipeTemporaryMetaInCoordinator;
import org.apache.iotdb.confignode.consensus.response.pipe.task.PipeTableResp;
import org.apache.iotdb.confignode.manager.ConfigManager;
import org.apache.iotdb.confignode.manager.node.NodeManager;
+import org.apache.iotdb.confignode.rpc.thrift.TShowPipeInfo;
import org.apache.iotdb.confignode.service.ConfigNode;
import org.apache.iotdb.rpc.TSStatusCode;
@@ -146,6 +148,25 @@ public class PipeTableRespTest {
Assert.assertEquals(3, allPipeTableResp.getAllPipeMeta().size());
}
+ @Test
+ public void testConvertToTShowPipeRespAggregatesRecentFailures() {
+ final PipeTableResp pipeTableResp = constructPipeTableResp();
+ final PipeTemporaryMetaInCoordinator temporaryMeta =
+ (PipeTemporaryMetaInCoordinator)
pipeTableResp.getAllPipeMeta().get(0).getTemporaryMeta();
+ final Map<String, Long> firstNodeFailures = new HashMap<>();
+ firstNodeFailures.put("network_timeout", 10L);
+ firstNodeFailures.put("memory_timeout", 15L);
+ temporaryMeta.setRecentFailures(1, firstNodeFailures);
+ final Map<String, Long> secondNodeFailures = new HashMap<>();
+ secondNodeFailures.put("network_timeout", 2L);
+ temporaryMeta.setRecentFailures(2, secondNodeFailures);
+
+ final TShowPipeInfo showPipeInfo =
+ pipeTableResp.convertToTShowPipeResp().getPipeInfoList().get(0);
+ Assert.assertEquals(Long.valueOf(12),
showPipeInfo.getRecentFailures().get("network_timeout"));
+ Assert.assertEquals(Long.valueOf(15),
showPipeInfo.getRecentFailures().get("memory_timeout"));
+ }
+
@Test
public void testConvertToTShowPipeRespIncludesPreDeleteStatus() {
final PipeTableResp pipeTableResp = constructPipeTableResp();
diff --git
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/heartbeat/PipeHeartbeatParserTest.java
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/heartbeat/PipeHeartbeatParserTest.java
index b080f8f51b1..ebe8cc19573 100644
---
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/heartbeat/PipeHeartbeatParserTest.java
+++
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/heartbeat/PipeHeartbeatParserTest.java
@@ -27,6 +27,7 @@ import
org.apache.iotdb.commons.pipe.agent.task.meta.PipeRuntimeMeta;
import org.apache.iotdb.commons.pipe.agent.task.meta.PipeStaticMeta;
import org.apache.iotdb.commons.pipe.agent.task.meta.PipeStatus;
import org.apache.iotdb.commons.pipe.agent.task.meta.PipeTaskMeta;
+import
org.apache.iotdb.commons.pipe.agent.task.meta.PipeTemporaryMetaInCoordinator;
import
org.apache.iotdb.confignode.consensus.request.write.pipe.task.CreatePipePlanV2;
import org.apache.iotdb.confignode.manager.ConfigManager;
import org.apache.iotdb.confignode.manager.ProcedureManager;
@@ -243,6 +244,50 @@ public class PipeHeartbeatParserTest {
verify(context.procedureManager,
never()).pipeHandleMetaChange(anyBoolean(), anyBoolean());
}
+ @Test
+ public void testParseHeartbeatAggregatesRecentFailuresFromAllDataNodes()
throws Exception {
+
CommonDescriptor.getInstance().getConfig().setSeperatedPipeHeartbeatEnabled(false);
+
+ final String pipeName = "resourceFailurePipe";
+ final PipeTaskInfo pipeTaskInfo = new PipeTaskInfo();
+ createPipe(pipeTaskInfo, pipeName, PipeStatus.RUNNING);
+ final PipeMeta pipeMeta = pipeTaskInfo.getPipeMetaByPipeName(pipeName);
+ final ParserTestContext context = createParserTestContext(2, pipeTaskInfo);
+
+ final Map<String, Long> firstNodeFailures = new HashMap<>();
+ firstNodeFailures.put("network_timeout", 10L);
+ final Map<String, Long> secondNodeFailures = new HashMap<>();
+ secondNodeFailures.put("network_timeout", 2L);
+ secondNodeFailures.put("memory_timeout", 15L);
+
+ context.parser.parseHeartbeat(1, createPipeHeartbeat(pipeMeta,
firstNodeFailures));
+ context.parser.parseHeartbeat(2, createPipeHeartbeat(pipeMeta,
secondNodeFailures));
+
+ final PipeTemporaryMetaInCoordinator temporaryMeta =
+ (PipeTemporaryMetaInCoordinator) pipeMeta.getTemporaryMeta();
+ Assert.assertEquals(
+ Long.valueOf(12),
temporaryMeta.getGlobalRecentFailures().get("network_timeout"));
+ Assert.assertEquals(
+ Long.valueOf(15),
temporaryMeta.getGlobalRecentFailures().get("memory_timeout"));
+ verify(context.procedureManager,
never()).pipeHandleMetaChange(anyBoolean(), anyBoolean());
+ }
+
+ @Test
+ public void testPipeHeartbeatTreatsNullRecentFailureMapAsEmpty() throws
Exception {
+ final PipeTaskInfo pipeTaskInfo = new PipeTaskInfo();
+ createPipe(pipeTaskInfo, "nullFailureMapPipe", PipeStatus.RUNNING);
+ final PipeMeta pipeMeta =
pipeTaskInfo.getPipeMetaByPipeName("nullFailureMapPipe");
+ final PipeHeartbeat heartbeat =
+ new PipeHeartbeat(
+ Collections.singletonList(pipeMeta.serialize()),
+ Collections.singletonList(false),
+ Collections.singletonList(0L),
+ Collections.singletonList(0d),
+ Collections.<Map<String, Long>>singletonList(null));
+
+
Assert.assertTrue(heartbeat.getRecentFailures(pipeMeta.getStaticMeta()).isEmpty());
+ }
+
private ParserTestContext createParserTestContext(final int
registeredDataNodeCount) {
return createParserTestContext(registeredDataNodeCount, new
PipeTaskInfo());
}
@@ -331,6 +376,16 @@ public class PipeHeartbeatParserTest {
return new PipeHeartbeat(Collections.emptyList(), null, null, null);
}
+ private PipeHeartbeat createPipeHeartbeat(
+ final PipeMeta pipeMeta, final Map<String, Long> recentFailures) throws
Exception {
+ return new PipeHeartbeat(
+ Collections.singletonList(pipeMeta.serialize()),
+ Collections.singletonList(false),
+ Collections.singletonList(0L),
+ Collections.singletonList(0d),
+ Collections.singletonList(recentFailures));
+ }
+
private static class ParserTestContext {
private final PipeHeartbeatParser parser;
private final ProcedureManager procedureManager;
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/PipeDataNodeTaskAgent.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/PipeDataNodeTaskAgent.java
index 54e8f1752ea..a28cdb95211 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/PipeDataNodeTaskAgent.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/PipeDataNodeTaskAgent.java
@@ -39,6 +39,7 @@ import
org.apache.iotdb.commons.pipe.agent.task.meta.PipeRuntimeMeta;
import org.apache.iotdb.commons.pipe.agent.task.meta.PipeStaticMeta;
import org.apache.iotdb.commons.pipe.agent.task.meta.PipeStatus;
import org.apache.iotdb.commons.pipe.agent.task.meta.PipeTaskMeta;
+import org.apache.iotdb.commons.pipe.agent.task.meta.PipeTemporaryMetaInAgent;
import org.apache.iotdb.commons.pipe.agent.task.meta.PipeType;
import org.apache.iotdb.commons.pipe.config.PipeConfig;
import org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant;
@@ -405,6 +406,7 @@ public class PipeDataNodeTaskAgent extends PipeTaskAgent {
final List<Boolean> pipeCompletedList = new ArrayList<>();
final List<Long> pipeRemainingEventCountList = new ArrayList<>();
final List<Double> pipeRemainingTimeList = new ArrayList<>();
+ final List<Map<String, Long>> pipeRecentFailureList = new ArrayList<>();
try {
for (final PipeMeta pipeMeta : pipeMetaKeeper.getPipeMetaList()) {
pipeMetaBinaryList.add(pipeMeta.serialize());
@@ -438,6 +440,8 @@ public class PipeDataNodeTaskAgent extends PipeTaskAgent {
pipeCompletedList.add(isCompleted);
pipeRemainingEventCountList.add(remainingEventAndTime.getLeft());
pipeRemainingTimeList.add(remainingEventAndTime.getRight());
+ pipeRecentFailureList.add(
+ ((PipeTemporaryMetaInAgent)
pipeMeta.getTemporaryMeta()).getRecentFailures());
logger.ifPresent(
l ->
@@ -457,6 +461,7 @@ public class PipeDataNodeTaskAgent extends PipeTaskAgent {
resp.setPipeCompletedList(pipeCompletedList);
resp.setPipeRemainingEventCountList(pipeRemainingEventCountList);
resp.setPipeRemainingTimeList(pipeRemainingTimeList);
+ resp.setPipeRecentFailureList(pipeRecentFailureList);
PipeInsertionDataNodeListener.getInstance().listenToHeartbeat(true);
}
@@ -486,6 +491,7 @@ public class PipeDataNodeTaskAgent extends PipeTaskAgent {
final List<Boolean> pipeCompletedList = new ArrayList<>();
final List<Long> pipeRemainingEventCountList = new ArrayList<>();
final List<Double> pipeRemainingTimeList = new ArrayList<>();
+ final List<Map<String, Long>> pipeRecentFailureList = new ArrayList<>();
try {
for (final PipeMeta pipeMeta : pipeMetaKeeper.getPipeMetaList()) {
pipeMetaBinaryList.add(pipeMeta.serialize());
@@ -519,6 +525,8 @@ public class PipeDataNodeTaskAgent extends PipeTaskAgent {
pipeCompletedList.add(isCompleted);
pipeRemainingEventCountList.add(remainingEventAndTime.getLeft());
pipeRemainingTimeList.add(remainingEventAndTime.getRight());
+ pipeRecentFailureList.add(
+ ((PipeTemporaryMetaInAgent)
pipeMeta.getTemporaryMeta()).getRecentFailures());
logger.ifPresent(
l ->
@@ -538,6 +546,7 @@ public class PipeDataNodeTaskAgent extends PipeTaskAgent {
resp.setPipeCompletedList(pipeCompletedList);
resp.setPipeRemainingEventCountList(pipeRemainingEventCountList);
resp.setPipeRemainingTimeList(pipeRemainingTimeList);
+ resp.setPipeRecentFailureList(pipeRecentFailureList);
PipeInsertionDataNodeListener.getInstance().listenToHeartbeat(true);
}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/subtask/processor/PipeProcessorSubtask.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/subtask/processor/PipeProcessorSubtask.java
index 193693c5a95..3c4b3d55019 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/subtask/processor/PipeProcessorSubtask.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/subtask/processor/PipeProcessorSubtask.java
@@ -27,6 +27,7 @@ import
org.apache.iotdb.commons.pipe.agent.task.execution.PipeSubtaskScheduler;
import
org.apache.iotdb.commons.pipe.agent.task.progress.PipeEventCommitManager;
import org.apache.iotdb.commons.pipe.agent.task.subtask.PipeReportableSubtask;
import org.apache.iotdb.commons.pipe.event.EnrichedEvent;
+import org.apache.iotdb.commons.pipe.resource.PipeResourceFailureType;
import org.apache.iotdb.commons.pipe.resource.log.PipeLogger;
import org.apache.iotdb.commons.utils.ErrorHandlingCommonUtils;
import org.apache.iotdb.db.pipe.agent.PipeDataNodeAgent;
@@ -189,6 +190,7 @@ public class PipeProcessorSubtask extends
PipeReportableSubtask {
}
decreaseReferenceCountAndReleaseLastEvent(event, shouldReport);
} catch (final PipeRuntimeOutOfMemoryCriticalException e) {
+ recordResourceFailure(event, PipeResourceFailureType.MEMORY_TIMEOUT);
PipeLogger.log(
LOGGER::info,
"Temporarily out of memory in pipe event processing, will wait for
the memory to release. Message: %s",
@@ -196,6 +198,7 @@ public class PipeProcessorSubtask extends
PipeReportableSubtask {
return false;
} catch (final Exception e) {
if (ExceptionUtils.getRootCause(e) instanceof
PipeRuntimeOutOfMemoryCriticalException) {
+ recordResourceFailure(event, PipeResourceFailureType.MEMORY_TIMEOUT);
PipeLogger.log(
LOGGER::info,
"Temporarily out of memory in pipe event processing, will wait for
the memory to release. Message: %s",
@@ -298,4 +301,13 @@ public class PipeProcessorSubtask extends
PipeReportableSubtask {
protected void report(final EnrichedEvent event, final PipeRuntimeException
exception) {
PipeDataNodeAgent.runtime().report(event, exception);
}
+
+ private void recordResourceFailure(final Event event, final
PipeResourceFailureType failureType) {
+ if (event instanceof EnrichedEvent) {
+ final EnrichedEvent enrichedEvent = (EnrichedEvent) event;
+ PipeDataNodeAgent.task()
+ .recordPipeResourceFailure(
+ enrichedEvent.getPipeName(), enrichedEvent.getCreationTime(),
failureType);
+ }
+ }
}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/subtask/sink/PipeSinkSubtask.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/subtask/sink/PipeSinkSubtask.java
index 91a588ce699..aa87f0850c0 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/subtask/sink/PipeSinkSubtask.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/subtask/sink/PipeSinkSubtask.java
@@ -27,6 +27,7 @@ import
org.apache.iotdb.commons.pipe.agent.task.progress.CommitterKey;
import
org.apache.iotdb.commons.pipe.agent.task.subtask.PipeAbstractSinkSubtask;
import org.apache.iotdb.commons.pipe.config.PipeConfig;
import org.apache.iotdb.commons.pipe.event.EnrichedEvent;
+import org.apache.iotdb.commons.pipe.resource.PipeResourceFailureType;
import org.apache.iotdb.commons.pipe.sink.protocol.IoTDBSink;
import
org.apache.iotdb.commons.pipe.sink.protocol.PipeConnectorWithEventDiscard;
import org.apache.iotdb.commons.utils.ErrorHandlingCommonUtils;
@@ -512,4 +513,11 @@ public class PipeSinkSubtask extends
PipeAbstractSinkSubtask {
lastExceptionTime = Long.MAX_VALUE;
PipeDataNodeAgent.runtime().report(event, exception);
}
+
+ @Override
+ protected void reportResourceFailure(
+ final EnrichedEvent event, final PipeResourceFailureType failureType) {
+ PipeDataNodeAgent.task()
+ .recordPipeResourceFailure(event.getPipeName(),
event.getCreationTime(), failureType);
+ }
}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/thrift/async/IoTDBDataRegionAsyncSink.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/thrift/async/IoTDBDataRegionAsyncSink.java
index 3d5d6af26a0..52d22ac9774 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/thrift/async/IoTDBDataRegionAsyncSink.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/thrift/async/IoTDBDataRegionAsyncSink.java
@@ -24,11 +24,15 @@ import org.apache.iotdb.common.rpc.thrift.TSStatus;
import org.apache.iotdb.commons.client.ThriftClient;
import
org.apache.iotdb.commons.client.async.AsyncPipeDataTransferServiceClient;
import
org.apache.iotdb.commons.exception.pipe.PipeRuntimeSinkNonReportTimeConfigurableException;
+import
org.apache.iotdb.commons.exception.pipe.PipeRuntimeSinkResourceException;
import org.apache.iotdb.commons.pipe.agent.task.progress.CommitterKey;
import org.apache.iotdb.commons.pipe.config.PipeConfig;
import org.apache.iotdb.commons.pipe.event.EnrichedEvent;
+import org.apache.iotdb.commons.pipe.resource.PipeResourceFailureType;
+import org.apache.iotdb.commons.pipe.resource.PipeStopStrategy;
import org.apache.iotdb.commons.pipe.resource.log.PipeLogger;
import org.apache.iotdb.commons.pipe.sink.protocol.IoTDBSink;
+import org.apache.iotdb.db.pipe.agent.PipeDataNodeAgent;
import org.apache.iotdb.db.pipe.event.common.heartbeat.PipeHeartbeatEvent;
import
org.apache.iotdb.db.pipe.event.common.schema.PipeSchemaRegionWritePlanEvent;
import
org.apache.iotdb.db.pipe.event.common.tablet.PipeInsertNodeTabletInsertionEvent;
@@ -76,6 +80,8 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -113,6 +119,9 @@ public class IoTDBDataRegionAsyncSink extends IoTDBSink {
private final BlockingQueue<TsFileInsertionEvent> retryTsFileQueue = new
LinkedBlockingQueue<>();
private final PipeDataRegionEventCounter retryEventQueueEventCounter =
new PipeDataRegionEventCounter();
+ // Guarded by this. Events need identity semantics because the same payload
may compare equal.
+ private final Map<Event, PipeResourceFailureType>
retryEvent2ResourceFailureType =
+ new IdentityHashMap<>();
private IoTDBDataNodeAsyncClientManager clientManager;
private IoTDBDataNodeAsyncClientManager transferTsFileClientManager;
@@ -566,6 +575,7 @@ public class IoTDBDataRegionAsyncSink extends IoTDBSink {
final Event polledEvent;
if (!retryEventQueue.isEmpty()) {
peekedEvent = retryEventQueue.peek();
+ retryEvent2ResourceFailureType.remove(peekedEvent);
if (peekedEvent instanceof PipeInsertNodeTabletInsertionEvent) {
retryTransfer((PipeInsertNodeTabletInsertionEvent) peekedEvent);
@@ -584,6 +594,7 @@ public class IoTDBDataRegionAsyncSink extends IoTDBSink {
return;
}
peekedEvent = retryTsFileQueue.peek();
+ retryEvent2ResourceFailureType.remove(peekedEvent);
retryTransfer((PipeTsFileInsertionEvent) peekedEvent);
polledEvent = retryTsFileQueue.poll();
}
@@ -624,6 +635,12 @@ public class IoTDBDataRegionAsyncSink extends IoTDBSink {
+ ", tsfile events: "
+ retryEventQueueEventCounter.getTsFileInsertionEventCount()
+ ").";
+ final PipeResourceFailureType retryQueueResourceFailureType =
+ getRetryQueueResourceFailureType();
+ if (retryQueueResourceFailureType != null) {
+ throw new PipeRuntimeSinkResourceException(
+ message, retryQueueResourceFailureType, true);
+ }
throw isConnectionException
? new PipeConnectionException(message)
: new PipeException(message);
@@ -686,6 +703,13 @@ public class IoTDBDataRegionAsyncSink extends IoTDBSink {
*/
@SuppressWarnings("java:S899")
public void addFailureEventToRetryQueue(final Event event, final Exception
e) {
+ addFailureEventToRetryQueue(event, e, null);
+ }
+
+ private synchronized void addFailureEventToRetryQueue(
+ final Event event, final Exception e, final Set<Pair<String, Long>>
failureRecordedPipes) {
+ final PipeResourceFailureType resourceFailureType =
+ PipeStopStrategy.getResourceFailureType(e, null);
isConnectionException =
e instanceof PipeConnectionException ||
ThriftClient.isConnectionBroken(e);
if (event instanceof EnrichedEvent) {
@@ -706,6 +730,23 @@ public class IoTDBDataRegionAsyncSink extends IoTDBSink {
return;
}
+ if (resourceFailureType != null && event instanceof EnrichedEvent) {
+ final EnrichedEvent enrichedEvent = (EnrichedEvent) event;
+ final Pair<String, Long> pipeKey =
+ new Pair<>(enrichedEvent.getPipeName(),
enrichedEvent.getCreationTime());
+ if (failureRecordedPipes == null || failureRecordedPipes.add(pipeKey)) {
+ PipeDataNodeAgent.task()
+ .recordPipeResourceFailure(
+ enrichedEvent.getPipeName(), enrichedEvent.getCreationTime(),
resourceFailureType);
+ }
+ }
+
+ if (resourceFailureType == null) {
+ retryEvent2ResourceFailureType.remove(event);
+ } else {
+ retryEvent2ResourceFailureType.put(event, resourceFailureType);
+ }
+
if (event instanceof PipeTsFileInsertionEvent) {
retryTsFileQueue.offer((PipeTsFileInsertionEvent) event);
retryEventQueueEventCounter.increaseEventCount(event);
@@ -732,7 +773,17 @@ public class IoTDBDataRegionAsyncSink extends IoTDBSink {
*/
public void addFailureEventsToRetryQueue(
final Iterable<EnrichedEvent> events, final Exception e) {
- events.forEach(event -> addFailureEventToRetryQueue(event, e));
+ final Set<Pair<String, Long>> failureRecordedPipes = new HashSet<>();
+ events.forEach(event -> addFailureEventToRetryQueue(event, e,
failureRecordedPipes));
+ }
+
+ private synchronized PipeResourceFailureType
getRetryQueueResourceFailureType() {
+ for (final PipeResourceFailureType failureType :
PipeResourceFailureType.values()) {
+ if (retryEvent2ResourceFailureType.containsValue(failureType)) {
+ return failureType;
+ }
+ }
+ return null;
}
public boolean isEnableSendTsFileLimit() {
@@ -875,6 +926,7 @@ public class IoTDBDataRegionAsyncSink extends IoTDBSink {
&& isDroppedPipe((EnrichedEvent) event, committerKey)) {
((EnrichedEvent)
event).clearReferenceCount(IoTDBDataRegionAsyncSink.class.getName());
retryEventQueueEventCounter.decreaseEventCount(event);
+ retryEvent2ResourceFailureType.remove(event);
return true;
}
return false;
@@ -886,6 +938,7 @@ public class IoTDBDataRegionAsyncSink extends IoTDBSink {
&& isDroppedPipe((EnrichedEvent) event, committerKey)) {
((EnrichedEvent)
event).clearReferenceCount(IoTDBDataRegionAsyncSink.class.getName());
retryEventQueueEventCounter.decreaseEventCount(event);
+ retryEvent2ResourceFailureType.remove(event);
return true;
}
return false;
@@ -938,10 +991,12 @@ public class IoTDBDataRegionAsyncSink extends IoTDBSink {
final Event event =
retryTsFileQueue.isEmpty() ? retryEventQueue.poll() :
retryTsFileQueue.poll();
retryEventQueueEventCounter.decreaseEventCount(event);
+ retryEvent2ResourceFailureType.remove(event);
if (event instanceof EnrichedEvent) {
((EnrichedEvent)
event).clearReferenceCount(IoTDBDataRegionAsyncSink.class.getName());
}
}
+ retryEvent2ResourceFailureType.clear();
}
//////////////////////// APIs provided for metric framework
////////////////////////
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/common/header/ColumnHeaderConstant.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/common/header/ColumnHeaderConstant.java
index 24d9509e0f3..92712daee52 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/common/header/ColumnHeaderConstant.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/common/header/ColumnHeaderConstant.java
@@ -181,6 +181,7 @@ public class ColumnHeaderConstant {
public static final String EXCEPTION_MESSAGE = "ExceptionMessage";
public static final String REMAINING_EVENT_COUNT = "RemainingEventCount";
public static final String ESTIMATED_REMAINING_SECONDS =
"EstimatedRemainingSeconds";
+ public static final String RECENT_FAILURES = "RecentFailures";
// column names for select into
public static final String SOURCE_DEVICE = "SourceDevice";
@@ -450,7 +451,8 @@ public class ColumnHeaderConstant {
new ColumnHeader(PIPE_CONNECTOR, TSDataType.TEXT),
new ColumnHeader(EXCEPTION_MESSAGE, TSDataType.TEXT),
new ColumnHeader(REMAINING_EVENT_COUNT, TSDataType.TEXT),
- new ColumnHeader(ESTIMATED_REMAINING_SECONDS, TSDataType.TEXT));
+ new ColumnHeader(ESTIMATED_REMAINING_SECONDS, TSDataType.TEXT),
+ new ColumnHeader(RECENT_FAILURES, TSDataType.TEXT));
public static final List<ColumnHeader> showTopicColumnHeaders =
ImmutableList.of(
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/sys/pipe/ShowPipeTask.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/sys/pipe/ShowPipeTask.java
index 16ee005a089..4703c4ebca1 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/sys/pipe/ShowPipeTask.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/sys/pipe/ShowPipeTask.java
@@ -41,6 +41,7 @@ import org.apache.tsfile.utils.Binary;
import org.apache.tsfile.utils.Pair;
import java.util.List;
+import java.util.TreeMap;
import java.util.stream.Collectors;
public class ShowPipeTask implements IConfigTask {
@@ -119,6 +120,14 @@ public class ShowPipeTask implements IConfigTask {
? String.format("%.2f", remainingTime)
: "Unknown",
TSFileConfig.STRING_CHARSET));
+ builder
+ .getColumnBuilder(9)
+ .writeBinary(
+ new Binary(
+ tPipeInfo.isSetRecentFailures()
+ ? new TreeMap<>(tPipeInfo.getRecentFailures()).toString()
+ : "{}",
+ TSFileConfig.STRING_CHARSET));
builder.declarePosition();
}
final DatasetHeader datasetHeader =
DatasetHeaderFactory.getShowPipeHeader();
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/agent/task/subtask/sink/PipeSinkSubtaskTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/agent/task/subtask/sink/PipeSinkSubtaskTest.java
index 8960e7fea96..6c5e79ff68b 100644
---
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/agent/task/subtask/sink/PipeSinkSubtaskTest.java
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/agent/task/subtask/sink/PipeSinkSubtaskTest.java
@@ -19,6 +19,7 @@
package org.apache.iotdb.db.pipe.agent.task.subtask.sink;
+import org.apache.iotdb.commons.client.exception.ClientManagerException;
import org.apache.iotdb.commons.conf.CommonDescriptor;
import org.apache.iotdb.commons.exception.pipe.PipeRuntimeException;
import
org.apache.iotdb.commons.exception.pipe.PipeRuntimeSinkCriticalException;
@@ -26,6 +27,7 @@ import
org.apache.iotdb.commons.exception.pipe.PipeRuntimeSinkNonReportTimeConfi
import
org.apache.iotdb.commons.pipe.agent.task.connection.UnboundedBlockingPendingQueue;
import org.apache.iotdb.commons.pipe.agent.task.progress.CommitterKey;
import org.apache.iotdb.commons.pipe.event.EnrichedEvent;
+import org.apache.iotdb.commons.pipe.resource.PipeResourceFailureType;
import
org.apache.iotdb.commons.pipe.sink.protocol.PipeConnectorWithEventDiscard;
import org.apache.iotdb.commons.utils.ErrorHandlingCommonUtils;
import org.apache.iotdb.db.pipe.event.common.heartbeat.PipeHeartbeatEvent;
@@ -328,9 +330,42 @@ public class PipeSinkSubtaskTest {
}
}
+ @Test
+ public void
testClientBorrowFailureRetriesLocallyWithoutReportingCriticalException() {
+ final long originalSleepIntervalInitMs =
+
CommonDescriptor.getInstance().getConfig().getPipeSinkSubtaskSleepIntervalInitMs();
+ final long originalSleepIntervalMaxMs =
+
CommonDescriptor.getInstance().getConfig().getPipeSinkSubtaskSleepIntervalMaxMs();
+
CommonDescriptor.getInstance().getConfig().setPipeSinkSubtaskSleepIntervalInitMs(1);
+
CommonDescriptor.getInstance().getConfig().setPipeSinkSubtaskSleepIntervalMaxMs(2);
+
+ final PipeConnector connector = mock(PipeConnector.class);
+ final UnboundedBlockingPendingQueue<Event> pendingQueue =
+ mock(UnboundedBlockingPendingQueue.class);
+ final CapturingPipeSinkSubtask subtask = new
CapturingPipeSinkSubtask(pendingQueue, connector);
+
+ try {
+ subtask.handleExceptionForTest(
+ mock(EnrichedEvent.class), new ClientManagerException("client pool
exhausted"));
+
+ Assert.assertEquals(
+ PipeResourceFailureType.NETWORK_TIMEOUT,
subtask.getReportedResourceFailureType());
+ Assert.assertNull(subtask.getReportedException());
+ } finally {
+ subtask.close();
+ CommonDescriptor.getInstance()
+ .getConfig()
+ .setPipeSinkSubtaskSleepIntervalInitMs(originalSleepIntervalInitMs);
+ CommonDescriptor.getInstance()
+ .getConfig()
+ .setPipeSinkSubtaskSleepIntervalMaxMs(originalSleepIntervalMaxMs);
+ }
+ }
+
private static class CapturingPipeSinkSubtask extends PipeSinkSubtask {
private PipeRuntimeException reportedException;
+ private PipeResourceFailureType reportedResourceFailureType;
private CapturingPipeSinkSubtask(
final UnboundedBlockingPendingQueue<Event> pendingQueue, final
PipeConnector connector) {
@@ -353,10 +388,24 @@ public class PipeSinkSubtaskTest {
return reportedException;
}
+ private PipeResourceFailureType getReportedResourceFailureType() {
+ return reportedResourceFailureType;
+ }
+
+ private void handleExceptionForTest(final EnrichedEvent event, final
Exception exception) {
+ handleException(event, exception);
+ }
+
@Override
protected void report(final EnrichedEvent event, final
PipeRuntimeException exception) {
reportedException = exception;
}
+
+ @Override
+ protected void reportResourceFailure(
+ final EnrichedEvent event, final PipeResourceFailureType failureType) {
+ reportedResourceFailureType = failureType;
+ }
}
private static class BlockingHandshakeConnector
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/execution/config/sys/pipe/ShowPipeTaskTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/execution/config/sys/pipe/ShowPipeTaskTest.java
new file mode 100644
index 00000000000..6bd8676199c
--- /dev/null
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/execution/config/sys/pipe/ShowPipeTaskTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.plan.execution.config.sys.pipe;
+
+import org.apache.iotdb.confignode.rpc.thrift.TShowPipeInfo;
+import org.apache.iotdb.db.queryengine.common.header.ColumnHeaderConstant;
+import org.apache.iotdb.db.queryengine.plan.execution.config.ConfigTaskResult;
+import org.apache.iotdb.rpc.TSStatusCode;
+
+import com.google.common.util.concurrent.SettableFuture;
+import org.apache.tsfile.read.common.block.TsBlock;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+
+public class ShowPipeTaskTest {
+
+ @Test
+ public void testBuildTSBlockWritesRecentFailures() throws Exception {
+ final TShowPipeInfo failedPipe = createPipeInfo("failed_pipe");
+ final Map<String, Long> recentFailures = new HashMap<>();
+ recentFailures.put("network_timeout", 10L);
+ recentFailures.put("memory_timeout", 15L);
+ failedPipe.setRecentFailures(recentFailures);
+ final TShowPipeInfo normalPipe = createPipeInfo("normal_pipe");
+
+ final SettableFuture<ConfigTaskResult> future = SettableFuture.create();
+ ShowPipeTask.buildTSBlock(Arrays.asList(failedPipe, normalPipe), future);
+
+ final ConfigTaskResult result = future.get();
+ final TsBlock resultSet = result.getResultSet();
+
+ assertEquals(TSStatusCode.SUCCESS_STATUS, result.getStatusCode());
+ assertEquals(
+ ColumnHeaderConstant.RECENT_FAILURES,
result.getResultSetHeader().getRespColumns().get(9));
+ assertEquals(2, resultSet.getPositionCount());
+ assertEquals(
+ "{memory_timeout=15, network_timeout=10}",
resultSet.getColumn(9).getBinary(0).toString());
+ assertEquals("{}", resultSet.getColumn(9).getBinary(1).toString());
+ }
+
+ private TShowPipeInfo createPipeInfo(final String pipeName) {
+ return new TShowPipeInfo(
+ pipeName,
+ 1L,
+ "RUNNING",
+ "{source=iotdb-source}",
+ "{processor=do-nothing-processor}",
+ "{sink=iotdb-thrift-sink}",
+ "");
+ }
+}
diff --git
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/exception/pipe/PipeRuntimeSinkResourceException.java
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/exception/pipe/PipeRuntimeSinkResourceException.java
new file mode 100644
index 00000000000..efc41d88654
--- /dev/null
+++
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/exception/pipe/PipeRuntimeSinkResourceException.java
@@ -0,0 +1,51 @@
+/*
+ * 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.commons.exception.pipe;
+
+import org.apache.iotdb.commons.pipe.resource.PipeResourceFailureType;
+
+public class PipeRuntimeSinkResourceException
+ extends PipeRuntimeSinkNonReportTimeConfigurableException {
+
+ private final PipeResourceFailureType failureType;
+ private final boolean failureRecorded;
+
+ public PipeRuntimeSinkResourceException(
+ final String message, final PipeResourceFailureType failureType) {
+ this(message, failureType, false);
+ }
+
+ public PipeRuntimeSinkResourceException(
+ final String message,
+ final PipeResourceFailureType failureType,
+ final boolean failureRecorded) {
+ super(message, Long.MAX_VALUE);
+ this.failureType = failureType;
+ this.failureRecorded = failureRecorded;
+ }
+
+ public PipeResourceFailureType getFailureType() {
+ return failureType;
+ }
+
+ public boolean isFailureRecorded() {
+ return failureRecorded;
+ }
+}
diff --git
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/PipeTaskAgent.java
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/PipeTaskAgent.java
index 1eef328bc7e..21cd8369b01 100644
---
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/PipeTaskAgent.java
+++
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/PipeTaskAgent.java
@@ -35,6 +35,7 @@ import
org.apache.iotdb.commons.pipe.agent.task.meta.PipeTemporaryMetaInAgent;
import org.apache.iotdb.commons.pipe.agent.task.progress.CommitterKey;
import
org.apache.iotdb.commons.pipe.agent.task.progress.PipeEventCommitManager;
import org.apache.iotdb.commons.pipe.config.PipeConfig;
+import org.apache.iotdb.commons.pipe.resource.PipeResourceFailureType;
import org.apache.iotdb.commons.pipe.resource.log.PipeLogger;
import org.apache.iotdb.commons.pipe.sink.limiter.PipeEndPointRateLimiter;
import org.apache.iotdb.commons.subscription.config.SubscriptionConfig;
@@ -1248,6 +1249,15 @@ public abstract class PipeTaskAgent {
}
}
+ public void recordPipeResourceFailure(
+ final String pipeName, final long creationTime, final
PipeResourceFailureType failureType) {
+ final PipeMeta pipeMeta = pipeMetaKeeper.getPipeMeta(pipeName);
+ // Avoid recording a failure for a stale pipe before alter.
+ if (Objects.nonNull(pipeMeta) &&
pipeMeta.getStaticMeta().getCreationTime() == creationTime) {
+ ((PipeTemporaryMetaInAgent)
pipeMeta.getTemporaryMeta()).recordResourceFailure(failureType);
+ }
+ }
+
public int getPipeCount() {
return pipeMetaKeeper.getPipeMetaCount();
}
diff --git
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/meta/PipeTemporaryMetaInAgent.java
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/meta/PipeTemporaryMetaInAgent.java
index 23914fa8d84..0f7b49225e0 100644
---
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/meta/PipeTemporaryMetaInAgent.java
+++
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/meta/PipeTemporaryMetaInAgent.java
@@ -20,6 +20,8 @@
package org.apache.iotdb.commons.pipe.agent.task.meta;
import org.apache.iotdb.commons.pipe.agent.task.progress.CommitterKey;
+import org.apache.iotdb.commons.pipe.resource.PipeRecentFailureCounter;
+import org.apache.iotdb.commons.pipe.resource.PipeResourceFailureType;
import java.util.Map;
import java.util.Objects;
@@ -30,6 +32,7 @@ public class PipeTemporaryMetaInAgent implements
PipeTemporaryMeta {
// Statistics
private final AtomicLong floatingMemoryUsageInByte = new AtomicLong(0L);
+ private final PipeRecentFailureCounter recentFailureCounter = new
PipeRecentFailureCounter();
// Object pool
private final String pipeNameWithCreationTime;
@@ -53,6 +56,14 @@ public class PipeTemporaryMetaInAgent implements
PipeTemporaryMeta {
return floatingMemoryUsageInByte.get();
}
+ public void recordResourceFailure(final PipeResourceFailureType failureType)
{
+ recentFailureCounter.record(failureType);
+ }
+
+ public Map<String, Long> getRecentFailures() {
+ return recentFailureCounter.getRecentFailures();
+ }
+
public String getPipeNameWithCreationTime() {
return pipeNameWithCreationTime;
}
@@ -87,12 +98,13 @@ public class PipeTemporaryMetaInAgent implements
PipeTemporaryMeta {
final PipeTemporaryMetaInAgent that = (PipeTemporaryMetaInAgent) o;
return Objects.equals(
this.floatingMemoryUsageInByte.get(),
that.floatingMemoryUsageInByte.get())
+ && Objects.equals(this.getRecentFailures(), that.getRecentFailures())
&& Objects.equals(this.regionId2CommitterKeyMap,
that.regionId2CommitterKeyMap);
}
@Override
public int hashCode() {
- return Objects.hash(floatingMemoryUsageInByte, regionId2CommitterKeyMap);
+ return Objects.hash(floatingMemoryUsageInByte, getRecentFailures(),
regionId2CommitterKeyMap);
}
@Override
@@ -100,6 +112,8 @@ public class PipeTemporaryMetaInAgent implements
PipeTemporaryMeta {
return "PipeTemporaryMeta{"
+ "floatingMemoryUsage="
+ floatingMemoryUsageInByte
+ + ", recentFailures="
+ + getRecentFailures()
+ ", regionId2CommitterKeyMap="
+ regionId2CommitterKeyMap
+ '}';
diff --git
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/meta/PipeTemporaryMetaInCoordinator.java
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/meta/PipeTemporaryMetaInCoordinator.java
index ee127bbae44..2bac38f81bd 100644
---
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/meta/PipeTemporaryMetaInCoordinator.java
+++
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/meta/PipeTemporaryMetaInCoordinator.java
@@ -19,9 +19,14 @@
package org.apache.iotdb.commons.pipe.agent.task.meta;
+import org.apache.iotdb.commons.pipe.resource.PipeRecentFailureCounter;
+
import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@@ -32,6 +37,8 @@ public class PipeTemporaryMetaInCoordinator implements
PipeTemporaryMeta {
Collections.newSetFromMap(new ConcurrentHashMap<>());
private final ConcurrentMap<Integer, Long> nodeId2RemainingEventMap = new
ConcurrentHashMap<>();
private final ConcurrentMap<Integer, Double> nodeId2RemainingTimeMap = new
ConcurrentHashMap<>();
+ private final ConcurrentMap<Integer, RecentFailureSnapshot>
nodeId2RecentFailuresMap =
+ new ConcurrentHashMap<>();
public void markDataNodeCompleted(final int dataNodeId) {
completedDataNodeIds.add(dataNodeId);
@@ -49,6 +56,27 @@ public class PipeTemporaryMetaInCoordinator implements
PipeTemporaryMeta {
nodeId2RemainingTimeMap.put(dataNodeId, remainingTime);
}
+ public void setRecentFailures(final int dataNodeId, final Map<String, Long>
recentFailures) {
+ if (Objects.isNull(recentFailures) || recentFailures.isEmpty()) {
+ nodeId2RecentFailuresMap.remove(dataNodeId);
+ return;
+ }
+
+ final Map<String, Long> sanitizedFailures = new HashMap<>();
+ recentFailures.forEach(
+ (failureType, count) -> {
+ if (Objects.nonNull(failureType) && Objects.nonNull(count) && count
> 0) {
+ sanitizedFailures.put(failureType, count);
+ }
+ });
+ if (sanitizedFailures.isEmpty()) {
+ nodeId2RecentFailuresMap.remove(dataNodeId);
+ } else {
+ nodeId2RecentFailuresMap.put(
+ dataNodeId, new RecentFailureSnapshot(sanitizedFailures,
System.currentTimeMillis()));
+ }
+ }
+
public Set<Integer> getCompletedDataNodeIds() {
return completedDataNodeIds;
}
@@ -61,6 +89,23 @@ public class PipeTemporaryMetaInCoordinator implements
PipeTemporaryMeta {
return
nodeId2RemainingTimeMap.values().stream().reduce(Math::max).orElse(0d);
}
+ public Map<String, Long> getGlobalRecentFailures() {
+ final long earliestIncludedTime =
+ System.currentTimeMillis() - PipeRecentFailureCounter.WINDOW_MILLIS;
+ nodeId2RecentFailuresMap
+ .entrySet()
+ .removeIf(entry -> entry.getValue().reportTime < earliestIncludedTime);
+
+ final Map<String, Long> result = new TreeMap<>();
+ nodeId2RecentFailuresMap
+ .values()
+ .forEach(
+ snapshot ->
+ snapshot.recentFailures.forEach(
+ (failureType, count) -> result.merge(failureType, count,
Long::sum)));
+ return result;
+ }
+
@Override
public boolean equals(final Object o) {
if (this == o) {
@@ -72,12 +117,17 @@ public class PipeTemporaryMetaInCoordinator implements
PipeTemporaryMeta {
final PipeTemporaryMetaInCoordinator that =
(PipeTemporaryMetaInCoordinator) o;
return Objects.equals(this.completedDataNodeIds, that.completedDataNodeIds)
&& Objects.equals(this.nodeId2RemainingEventMap,
that.nodeId2RemainingEventMap)
- && Objects.equals(this.nodeId2RemainingTimeMap,
that.nodeId2RemainingTimeMap);
+ && Objects.equals(this.nodeId2RemainingTimeMap,
that.nodeId2RemainingTimeMap)
+ && Objects.equals(this.nodeId2RecentFailuresMap,
that.nodeId2RecentFailuresMap);
}
@Override
public int hashCode() {
- return Objects.hash(completedDataNodeIds, nodeId2RemainingEventMap,
nodeId2RemainingTimeMap);
+ return Objects.hash(
+ completedDataNodeIds,
+ nodeId2RemainingEventMap,
+ nodeId2RemainingTimeMap,
+ nodeId2RecentFailuresMap);
}
@Override
@@ -89,6 +139,36 @@ public class PipeTemporaryMetaInCoordinator implements
PipeTemporaryMeta {
+ nodeId2RemainingEventMap
+ ", nodeId2RemainingTimeMap"
+ nodeId2RemainingTimeMap
+ + ", nodeId2RecentFailuresMap="
+ + nodeId2RecentFailuresMap
+ '}';
}
+
+ private static class RecentFailureSnapshot {
+
+ private final Map<String, Long> recentFailures;
+ private final long reportTime;
+
+ private RecentFailureSnapshot(final Map<String, Long> recentFailures,
final long reportTime) {
+ this.recentFailures = recentFailures;
+ this.reportTime = reportTime;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final RecentFailureSnapshot that = (RecentFailureSnapshot) o;
+ return Objects.equals(recentFailures, that.recentFailures);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(recentFailures);
+ }
+ }
}
diff --git
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/subtask/PipeAbstractSinkSubtask.java
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/subtask/PipeAbstractSinkSubtask.java
index f3c1d72605c..df43914494e 100644
---
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/subtask/PipeAbstractSinkSubtask.java
+++
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/subtask/PipeAbstractSinkSubtask.java
@@ -19,12 +19,13 @@
package org.apache.iotdb.commons.pipe.agent.task.subtask;
-import
org.apache.iotdb.commons.exception.pipe.PipeRuntimeOutOfMemoryCriticalException;
import
org.apache.iotdb.commons.exception.pipe.PipeRuntimeSinkCriticalException;
import
org.apache.iotdb.commons.exception.pipe.PipeRuntimeSinkNonReportTimeConfigurableException;
import org.apache.iotdb.commons.pipe.agent.task.execution.PipeSubtaskScheduler;
import org.apache.iotdb.commons.pipe.config.PipeConfig;
import org.apache.iotdb.commons.pipe.event.EnrichedEvent;
+import org.apache.iotdb.commons.pipe.resource.PipeResourceFailureType;
+import org.apache.iotdb.commons.pipe.resource.PipeStopStrategy;
import org.apache.iotdb.commons.pipe.resource.log.PipeLogger;
import org.apache.iotdb.commons.utils.ErrorHandlingCommonUtils;
import org.apache.iotdb.pipe.api.PipeConnector;
@@ -35,7 +36,6 @@ import org.apache.iotdb.pipe.api.exception.PipeException;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
-import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -321,12 +321,19 @@ public abstract class PipeAbstractSinkSubtask extends
PipeReportableSubtask {
@SuppressWarnings("squid:S3776") // Suppress high Cognitive Complexity
warning
protected void handleException(final Event event, final Exception e) {
- if (e instanceof PipeRuntimeOutOfMemoryCriticalException
- || ExceptionUtils.getRootCause(e) instanceof
PipeRuntimeOutOfMemoryCriticalException) {
- PipeLogger.log(
- LOGGER::info,
- e,
- "Temporarily out of memory in pipe event transferring, will wait for
the memory to release.");
+ if (!PipeStopStrategy.accept(e, null)) {
+ final PipeResourceFailureType failureType =
PipeStopStrategy.getResourceFailureType(e, null);
+ if (event instanceof EnrichedEvent &&
!PipeStopStrategy.isResourceFailureRecorded(e)) {
+ reportResourceFailure((EnrichedEvent) event, failureType);
+ }
+ if (failureType == PipeResourceFailureType.MEMORY_TIMEOUT) {
+ PipeLogger.log(
+ LOGGER::info,
+ e,
+ "Temporarily out of memory in pipe event transferring, will wait
for the memory to release.");
+ } else {
+ sleep4NonReportException();
+ }
} else if (e instanceof PipeRuntimeSinkNonReportTimeConfigurableException)
{
if (lastExceptionTime == Long.MAX_VALUE) {
lastExceptionTime = System.currentTimeMillis();
@@ -372,4 +379,9 @@ public abstract class PipeAbstractSinkSubtask extends
PipeReportableSubtask {
clearReferenceCountAndReleaseLastEvent(event);
}
}
+
+ protected void reportResourceFailure(
+ final EnrichedEvent event, final PipeResourceFailureType failureType) {
+ // Do nothing by default for subtasks that do not expose resource failure
metrics.
+ }
}
diff --git
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/receiver/PipeReceiverStatusHandler.java
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/receiver/PipeReceiverStatusHandler.java
index 604ad044dd6..273e04c1c00 100644
---
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/receiver/PipeReceiverStatusHandler.java
+++
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/receiver/PipeReceiverStatusHandler.java
@@ -21,7 +21,10 @@ package org.apache.iotdb.commons.pipe.receiver;
import org.apache.iotdb.common.rpc.thrift.TSStatus;
import
org.apache.iotdb.commons.exception.pipe.PipeRuntimeSinkNonReportTimeConfigurableException;
+import
org.apache.iotdb.commons.exception.pipe.PipeRuntimeSinkResourceException;
import org.apache.iotdb.commons.pipe.config.PipeConfig;
+import org.apache.iotdb.commons.pipe.resource.PipeResourceFailureType;
+import org.apache.iotdb.commons.pipe.resource.PipeStopStrategy;
import org.apache.iotdb.commons.pipe.resource.log.PipeLogger;
import org.apache.iotdb.commons.utils.TestOnly;
import org.apache.iotdb.pipe.api.event.Event;
@@ -105,6 +108,14 @@ public class PipeReceiverStatusHandler {
final @Nullable String exceptionMessage,
final String recordMessage,
final boolean log4NoPrivileges) {
+ if (!PipeStopStrategy.accept(null, status)) {
+ PipeLogger.log(
+ LOGGER::info, "Temporary unavailable exception: will retry forever.
status: %s", status);
+ final PipeResourceFailureType failureType =
+ PipeStopStrategy.getResourceFailureType(null, status);
+ throw new PipeRuntimeSinkResourceException(exceptionMessage,
failureType);
+ }
+
switch (status.getCode()) {
case 200: // SUCCESS_STATUS
case 400: // REDIRECTION_RECOMMEND
@@ -118,16 +129,6 @@ public class PipeReceiverStatusHandler {
return;
}
- case 1808: // PIPE_RECEIVER_TEMPORARY_UNAVAILABLE_EXCEPTION
- {
- PipeLogger.log(
- LOGGER::info,
- "Temporary unavailable exception: will retry forever. status:
%s",
- status);
- throw new PipeRuntimeSinkNonReportTimeConfigurableException(
- exceptionMessage, Long.MAX_VALUE);
- }
-
case 1810: // PIPE_RECEIVER_USER_CONFLICT_EXCEPTION
case 1815: // PIPE_RECEIVER_PARALLEL_OR_USER_CONFLICT_EXCEPTION
if (!isRetryAllowedWhenConflictOccurs) {
diff --git
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/resource/PipeRecentFailureCounter.java
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/resource/PipeRecentFailureCounter.java
new file mode 100644
index 00000000000..974e647e02b
--- /dev/null
+++
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/resource/PipeRecentFailureCounter.java
@@ -0,0 +1,94 @@
+/*
+ * 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.commons.pipe.resource;
+
+import org.apache.iotdb.commons.utils.TestOnly;
+
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+public class PipeRecentFailureCounter {
+
+ public static final long WINDOW_MILLIS = TimeUnit.MINUTES.toMillis(1);
+ private static final long BUCKET_MILLIS = TimeUnit.SECONDS.toMillis(1);
+ // Keep one extra slot for a failure exactly WINDOW_MILLIS old.
+ private static final int BUCKET_COUNT = (int) (WINDOW_MILLIS /
BUCKET_MILLIS) + 1;
+
+ private final Map<PipeResourceFailureType, FailureBucket[]> failureBuckets =
+ new EnumMap<>(PipeResourceFailureType.class);
+
+ public PipeRecentFailureCounter() {
+ for (final PipeResourceFailureType failureType :
PipeResourceFailureType.values()) {
+ final FailureBucket[] buckets = new FailureBucket[BUCKET_COUNT];
+ for (int i = 0; i < BUCKET_COUNT; ++i) {
+ buckets[i] = new FailureBucket();
+ }
+ failureBuckets.put(failureType, buckets);
+ }
+ }
+
+ public void record(final PipeResourceFailureType failureType) {
+ record(failureType, System.currentTimeMillis());
+ }
+
+ @TestOnly
+ synchronized void record(final PipeResourceFailureType failureType, final
long timestamp) {
+ final long bucketStartTime = Math.floorDiv(timestamp, BUCKET_MILLIS) *
BUCKET_MILLIS;
+ final int bucketIndex =
+ (int) Math.floorMod(Math.floorDiv(timestamp, BUCKET_MILLIS),
BUCKET_COUNT);
+ final FailureBucket bucket = failureBuckets.get(failureType)[bucketIndex];
+ if (bucket.startTime != bucketStartTime) {
+ bucket.startTime = bucketStartTime;
+ bucket.count = 0;
+ }
+ ++bucket.count;
+ }
+
+ public Map<String, Long> getRecentFailures() {
+ return getRecentFailures(System.currentTimeMillis());
+ }
+
+ @TestOnly
+ synchronized Map<String, Long> getRecentFailures(final long currentTime) {
+ final Map<String, Long> result = new LinkedHashMap<>();
+ final long earliestIncludedTime = currentTime - WINDOW_MILLIS;
+ for (final PipeResourceFailureType failureType :
PipeResourceFailureType.values()) {
+ long count = 0;
+ for (final FailureBucket bucket : failureBuckets.get(failureType)) {
+ if (bucket.startTime >= earliestIncludedTime && bucket.startTime <=
currentTime) {
+ count += bucket.count;
+ }
+ }
+ if (count > 0) {
+ result.put(failureType.getDisplayName(), count);
+ }
+ }
+ return Collections.unmodifiableMap(result);
+ }
+
+ private static class FailureBucket {
+
+ private long startTime = Long.MIN_VALUE;
+ private long count;
+ }
+}
diff --git
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/resource/PipeResourceFailureType.java
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/resource/PipeResourceFailureType.java
new file mode 100644
index 00000000000..722da021cbd
--- /dev/null
+++
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/resource/PipeResourceFailureType.java
@@ -0,0 +1,36 @@
+/*
+ * 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.commons.pipe.resource;
+
+public enum PipeResourceFailureType {
+ NETWORK_TIMEOUT("network_timeout"),
+ MEMORY_TIMEOUT("memory_timeout"),
+ RECEIVER_UNAVAILABLE("receiver_unavailable");
+
+ private final String displayName;
+
+ PipeResourceFailureType(final String displayName) {
+ this.displayName = displayName;
+ }
+
+ public String getDisplayName() {
+ return displayName;
+ }
+}
diff --git
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/resource/PipeStopStrategy.java
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/resource/PipeStopStrategy.java
new file mode 100644
index 00000000000..4535bf57fe5
--- /dev/null
+++
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/resource/PipeStopStrategy.java
@@ -0,0 +1,76 @@
+/*
+ * 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.commons.pipe.resource;
+
+import org.apache.iotdb.common.rpc.thrift.TSStatus;
+import org.apache.iotdb.commons.client.exception.ClientManagerException;
+import
org.apache.iotdb.commons.exception.pipe.PipeRuntimeOutOfMemoryCriticalException;
+import
org.apache.iotdb.commons.exception.pipe.PipeRuntimeSinkResourceException;
+import org.apache.iotdb.rpc.TSStatusCode;
+
+import javax.annotation.Nullable;
+
+public final class PipeStopStrategy {
+
+ private PipeStopStrategy() {}
+
+ /**
+ * @return {@code true} if the failure may follow the normal stop/report
path, or {@code false} if
+ * it is a transient resource failure that must only be retried locally
+ */
+ public static boolean accept(
+ final @Nullable Exception exception, final @Nullable TSStatus status) {
+ return getResourceFailureType(exception, status) == null;
+ }
+
+ public static PipeResourceFailureType getResourceFailureType(
+ final @Nullable Exception exception, final @Nullable TSStatus status) {
+ Throwable current = exception;
+ while (current != null) {
+ if (current instanceof PipeRuntimeSinkResourceException) {
+ return ((PipeRuntimeSinkResourceException) current).getFailureType();
+ }
+ if (current instanceof PipeRuntimeOutOfMemoryCriticalException) {
+ return PipeResourceFailureType.MEMORY_TIMEOUT;
+ }
+ if (current instanceof ClientManagerException) {
+ return PipeResourceFailureType.NETWORK_TIMEOUT;
+ }
+ current = current.getCause();
+ }
+
+ return status != null
+ && status.getCode()
+ ==
TSStatusCode.PIPE_RECEIVER_TEMPORARY_UNAVAILABLE_EXCEPTION.getStatusCode()
+ ? PipeResourceFailureType.RECEIVER_UNAVAILABLE
+ : null;
+ }
+
+ public static boolean isResourceFailureRecorded(final @Nullable Exception
exception) {
+ Throwable current = exception;
+ while (current != null) {
+ if (current instanceof PipeRuntimeSinkResourceException) {
+ return ((PipeRuntimeSinkResourceException)
current).isFailureRecorded();
+ }
+ current = current.getCause();
+ }
+ return false;
+ }
+}
diff --git
a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/agent/task/meta/PipeTemporaryMetaTest.java
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/agent/task/meta/PipeTemporaryMetaTest.java
new file mode 100644
index 00000000000..6e29ee267a5
--- /dev/null
+++
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/agent/task/meta/PipeTemporaryMetaTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.commons.pipe.agent.task.meta;
+
+import org.apache.iotdb.commons.pipe.resource.PipeResourceFailureType;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class PipeTemporaryMetaTest {
+
+ @Test
+ public void testRecentFailuresAreRecordedAndAggregated() {
+ final PipeTemporaryMetaInAgent agentMeta = new
PipeTemporaryMetaInAgent("test_pipe", 1L);
+ agentMeta.recordResourceFailure(PipeResourceFailureType.NETWORK_TIMEOUT);
+ agentMeta.recordResourceFailure(PipeResourceFailureType.NETWORK_TIMEOUT);
+ agentMeta.recordResourceFailure(PipeResourceFailureType.MEMORY_TIMEOUT);
+
+ Assert.assertEquals(Long.valueOf(2),
agentMeta.getRecentFailures().get("network_timeout"));
+ Assert.assertEquals(Long.valueOf(1),
agentMeta.getRecentFailures().get("memory_timeout"));
+
+ final PipeTemporaryMetaInCoordinator coordinatorMeta = new
PipeTemporaryMetaInCoordinator();
+ coordinatorMeta.setRecentFailures(1, agentMeta.getRecentFailures());
+ final Map<String, Long> secondNodeFailures = new HashMap<>();
+ secondNodeFailures.put("network_timeout", 3L);
+ coordinatorMeta.setRecentFailures(2, secondNodeFailures);
+
+ Assert.assertEquals(
+ Long.valueOf(5),
coordinatorMeta.getGlobalRecentFailures().get("network_timeout"));
+ Assert.assertEquals(
+ Long.valueOf(1),
coordinatorMeta.getGlobalRecentFailures().get("memory_timeout"));
+
+ coordinatorMeta.setRecentFailures(1, null);
+ Assert.assertEquals(
+ Long.valueOf(3),
coordinatorMeta.getGlobalRecentFailures().get("network_timeout"));
+
Assert.assertFalse(coordinatorMeta.getGlobalRecentFailures().containsKey("memory_timeout"));
+ }
+}
diff --git
a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/resource/PipeRecentFailureCounterTest.java
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/resource/PipeRecentFailureCounterTest.java
new file mode 100644
index 00000000000..422dcbf2633
--- /dev/null
+++
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/resource/PipeRecentFailureCounterTest.java
@@ -0,0 +1,48 @@
+/*
+ * 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.commons.pipe.resource;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.Map;
+
+public class PipeRecentFailureCounterTest {
+
+ @Test
+ public void testCountsOnlyFailuresWithinOneMinute() {
+ final PipeRecentFailureCounter counter = new PipeRecentFailureCounter();
+ final long now = 100_000L;
+
+ counter.record(
+ PipeResourceFailureType.NETWORK_TIMEOUT, now -
PipeRecentFailureCounter.WINDOW_MILLIS - 1);
+ counter.record(
+ PipeResourceFailureType.NETWORK_TIMEOUT, now -
PipeRecentFailureCounter.WINDOW_MILLIS);
+ counter.record(PipeResourceFailureType.NETWORK_TIMEOUT, now);
+ counter.record(PipeResourceFailureType.MEMORY_TIMEOUT, now);
+
+ final Map<String, Long> failures = counter.getRecentFailures(now);
+ Assert.assertEquals(Long.valueOf(2), failures.get("network_timeout"));
+ Assert.assertEquals(Long.valueOf(1), failures.get("memory_timeout"));
+
+ Assert.assertTrue(
+ counter.getRecentFailures(now + PipeRecentFailureCounter.WINDOW_MILLIS
+ 1).isEmpty());
+ }
+}
diff --git
a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/resource/PipeStopStrategyTest.java
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/resource/PipeStopStrategyTest.java
new file mode 100644
index 00000000000..b68da403a42
--- /dev/null
+++
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/resource/PipeStopStrategyTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.commons.pipe.resource;
+
+import org.apache.iotdb.common.rpc.thrift.TSStatus;
+import org.apache.iotdb.commons.client.exception.ClientManagerException;
+import
org.apache.iotdb.commons.exception.pipe.PipeRuntimeOutOfMemoryCriticalException;
+import
org.apache.iotdb.commons.exception.pipe.PipeRuntimeSinkResourceException;
+import org.apache.iotdb.pipe.api.exception.PipeConnectionException;
+import org.apache.iotdb.rpc.TSStatusCode;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.IOException;
+
+public class PipeStopStrategyTest {
+
+ @Test
+ public void testClientBorrowFailureDoesNotStopPipe() {
+ final Exception failure =
+ new PipeConnectionException(
+ "transfer failed",
+ new IOException(new ClientManagerException(new IOException("client
pool exhausted"))));
+
+ Assert.assertFalse(PipeStopStrategy.accept(failure, null));
+ Assert.assertEquals(
+ PipeResourceFailureType.NETWORK_TIMEOUT,
+ PipeStopStrategy.getResourceFailureType(failure, null));
+ }
+
+ @Test
+ public void testMemoryFailuresDoNotStopPipe() {
+ final PipeRuntimeOutOfMemoryCriticalException exception =
+ new PipeRuntimeOutOfMemoryCriticalException("memory unavailable");
+ Assert.assertFalse(PipeStopStrategy.accept(exception, null));
+ Assert.assertEquals(
+ PipeResourceFailureType.MEMORY_TIMEOUT,
+ PipeStopStrategy.getResourceFailureType(exception, null));
+
+ final TSStatus status =
+ new
TSStatus(TSStatusCode.PIPE_RECEIVER_TEMPORARY_UNAVAILABLE_EXCEPTION.getStatusCode());
+ Assert.assertFalse(PipeStopStrategy.accept(null, status));
+ Assert.assertEquals(
+ PipeResourceFailureType.RECEIVER_UNAVAILABLE,
+ PipeStopStrategy.getResourceFailureType(null, status));
+ }
+
+ @Test
+ public void testOtherFailuresKeepExistingStopPolicy() {
+ Assert.assertTrue(PipeStopStrategy.accept(new IOException("network
disconnected"), null));
+ Assert.assertTrue(
+ PipeStopStrategy.accept(
+ null, new
TSStatus(TSStatusCode.INTERNAL_SERVER_ERROR.getStatusCode())));
+ }
+
+ @Test
+ public void testRecordedMarkerIsFoundWhenResourceFailureIsWrapped() {
+ final Exception failure =
+ new IOException(
+ new PipeRuntimeSinkResourceException(
+ "retry queue exhausted",
PipeResourceFailureType.NETWORK_TIMEOUT, true));
+
+ Assert.assertTrue(PipeStopStrategy.isResourceFailureRecorded(failure));
+ }
+}
diff --git a/iotdb-protocol/thrift-commons/src/main/thrift/common.thrift
b/iotdb-protocol/thrift-commons/src/main/thrift/common.thrift
index 73aef5c4c59..5dd66be212c 100644
--- a/iotdb-protocol/thrift-commons/src/main/thrift/common.thrift
+++ b/iotdb-protocol/thrift-commons/src/main/thrift/common.thrift
@@ -201,6 +201,7 @@ struct TPipeHeartbeatResp {
2: optional list<bool> pipeCompletedList
3: optional list<i64> pipeRemainingEventCountList
4: optional list<double> pipeRemainingTimeList
+ 6: optional list<map<string, i64>> pipeRecentFailureList
}
struct TLicense {
diff --git a/iotdb-protocol/thrift-confignode/src/main/thrift/confignode.thrift
b/iotdb-protocol/thrift-confignode/src/main/thrift/confignode.thrift
index d6e0b186aad..b04274945da 100644
--- a/iotdb-protocol/thrift-confignode/src/main/thrift/confignode.thrift
+++ b/iotdb-protocol/thrift-confignode/src/main/thrift/confignode.thrift
@@ -729,6 +729,7 @@ struct TShowPipeInfo {
7: required string exceptionMessage
8: optional i64 remainingEventCount
9: optional double EstimatedRemainingTime
+ 11: optional map<string, i64> recentFailures
}
struct TGetAllPipeInfoResp {
@@ -1775,4 +1776,3 @@ service IConfigNodeRPCService {
/** Push heartbeat in shutdown */
common.TSStatus pushHeartbeat(i32 dataNodeId, common.TPipeHeartbeatResp resp)
}
-
diff --git a/iotdb-protocol/thrift-datanode/src/main/thrift/datanode.thrift
b/iotdb-protocol/thrift-datanode/src/main/thrift/datanode.thrift
index 989d624f337..0d1f60d61a8 100644
--- a/iotdb-protocol/thrift-datanode/src/main/thrift/datanode.thrift
+++ b/iotdb-protocol/thrift-datanode/src/main/thrift/datanode.thrift
@@ -307,6 +307,7 @@ struct TDataNodeHeartbeatResp {
14: optional list<bool> pipeCompletedList
15: optional list<i64> pipeRemainingEventCountList
16: optional list<double> pipeRemainingTimeList
+ 19: optional list<map<string, i64>> pipeRecentFailureList
}
struct TPipeHeartbeatReq {
@@ -1076,4 +1077,4 @@ service MPPDataExchangeService {
/** Empty rpc, only for connection test */
common.TSStatus testConnectionEmptyRPC()
-}
\ No newline at end of file
+}