This is an automated email from the ASF dual-hosted git repository.
jt2594838 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 b626c602ead [Pipe] Unify TsFile parser allocation and resize quotas
(#18984)
b626c602ead is described below
commit b626c602ead0ba8d96744eac8324d948c19a4e30
Author: Caideyipi <[email protected]>
AuthorDate: Thu Oct 22 16:29:42 2026 +0800
[Pipe] Unify TsFile parser allocation and resize quotas (#18984)
---
.../apache/iotdb/db/i18n/DataNodePipeMessages.java | 4 +
.../apache/iotdb/db/i18n/DataNodePipeMessages.java | 4 +
.../TsFileInsertionEventParserMemoryManager.java | 23 +--
.../resource/memory/PipeMemoryBlockCategory.java | 1 +
.../pipe/resource/memory/PipeMemoryBlockType.java | 1 +
.../db/pipe/resource/memory/PipeMemoryManager.java | 82 +++++++++-
.../memory/PipeTsFileParserMemoryBlock.java | 33 ++++
.../memory/PipeMemoryManagerResizeTest.java | 180 +++++++++++++++++++++
8 files changed, 305 insertions(+), 23 deletions(-)
diff --git
a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodePipeMessages.java
b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodePipeMessages.java
index 8eb2b8a6116..8569f76908d 100644
---
a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodePipeMessages.java
+++
b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodePipeMessages.java
@@ -2403,6 +2403,10 @@ public final class DataNodePipeMessages {
public static final String
PIPE_EXCEPTION_FORCERESIZE_FAILED_TO_ALLOCATE_MEMORY_AFTER_D_RETRIES_TOTAL_8C6948BC
=
"forceResize: failed to allocate memory after %d retries, total memory
size %d bytes, used "
+ "memory size %d bytes, requested memory size %d bytes";
+ public static final String
EXCEPTION_FORCERESIZE_FAILED_AFTER_ARG_RETRIES_TOTAL_ARG_BYTES_USED_ARG_BYTES_USEDTABLETS_ARG_BYTES_USEDTSFILES_ARG_BYTES_TABLETLIMIT_ARG_BYTES_TSFILELIMIT_ARG_BYTES_TABLETANDTSFILELIMIT_ARG_BYTES_PROTECTEDPARSERLIMIT_ARG_BYTES_REQUESTED_ARG_BYTES_311C8C7E
=
+ "forceResize failed after %d retries: total=%d bytes, used=%d bytes, "
+ + "usedTablets=%d bytes, usedTsFiles=%d bytes, tabletLimit=%d bytes,
tsFileLimit=%d bytes, "
+ + "tabletAndTsFileLimit=%d bytes, protectedParserLimit=%d bytes,
requested=%d bytes";
public static final String
EXCEPTION_UNSUPPORTED_BATCH_TYPE_ARG_WHEN_TRANSFERRING_TABLET_INSERTION_EVENT_66153E12
=
"Unsupported batch type %s when transferring tablet insertion
event.";
diff --git
a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodePipeMessages.java
b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodePipeMessages.java
index ee86a424c29..57f98bad5f0 100644
---
a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodePipeMessages.java
+++
b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodePipeMessages.java
@@ -2233,6 +2233,10 @@ public final class DataNodePipeMessages {
"forceAllocate:重试 %d 次后仍无法分配内存,总内存大小 %d bytes,已用内存大小 %d bytes,请求内存大小 %d
bytes";
public static final String
PIPE_EXCEPTION_FORCERESIZE_FAILED_TO_ALLOCATE_MEMORY_AFTER_D_RETRIES_TOTAL_8C6948BC
=
"forceResize:重试 %d 次后仍无法分配内存,总内存大小 %d bytes,已用内存大小 %d bytes,请求内存大小 %d
bytes";
+ public static final String
EXCEPTION_FORCERESIZE_FAILED_AFTER_ARG_RETRIES_TOTAL_ARG_BYTES_USED_ARG_BYTES_USEDTABLETS_ARG_BYTES_USEDTSFILES_ARG_BYTES_TABLETLIMIT_ARG_BYTES_TSFILELIMIT_ARG_BYTES_TABLETANDTSFILELIMIT_ARG_BYTES_PROTECTEDPARSERLIMIT_ARG_BYTES_REQUESTED_ARG_BYTES_311C8C7E
=
+ "forceResize 重试 %d 次后仍无法分配内存:total=%d bytes,used=%d bytes,"
+ + "usedTablets=%d bytes,usedTsFiles=%d bytes,tabletLimit=%d
bytes,tsFileLimit=%d bytes,"
+ + "tabletAndTsFileLimit=%d bytes,protectedParserLimit=%d
bytes,requested=%d bytes";
public static final String
EXCEPTION_UNSUPPORTED_BATCH_TYPE_ARG_WHEN_TRANSFERRING_TABLET_INSERTION_EVENT_66153E12
=
"传输 tablet insertion event 时不支持 batch 类型 %s。";
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/event/common/tsfile/parser/TsFileInsertionEventParserMemoryManager.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/event/common/tsfile/parser/TsFileInsertionEventParserMemoryManager.java
index 36b522f1999..8e7048d16d7 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/event/common/tsfile/parser/TsFileInsertionEventParserMemoryManager.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/event/common/tsfile/parser/TsFileInsertionEventParserMemoryManager.java
@@ -20,10 +20,10 @@
package org.apache.iotdb.db.pipe.event.common.tsfile.parser;
import
org.apache.iotdb.commons.exception.pipe.PipeRuntimeOutOfMemoryCriticalException;
-import org.apache.iotdb.db.i18n.DataNodePipeMessages;
import org.apache.iotdb.db.pipe.resource.PipeDataNodeResourceManager;
import org.apache.iotdb.db.pipe.resource.memory.PipeMemoryBlock;
import org.apache.iotdb.db.pipe.resource.memory.PipeMemoryBlockCategory;
+import org.apache.iotdb.db.pipe.resource.memory.PipeMemoryManager;
/** Allocates parser working memory from the pool owned by the caller. */
public interface TsFileInsertionEventParserMemoryManager {
@@ -63,11 +63,8 @@ public interface TsFileInsertionEventParserMemoryManager {
final String name, final long sizeInBytes) {
return new PipeBlock(
PipeDataNodeResourceManager.memory()
- .forceAllocateForTabletWithRetry(
- name,
- sizeInBytes,
- PipeMemoryBlockCategory.TABLET,
- TsFileInsertionEventParser.class.getSimpleName()));
+ .forceAllocateForTsFileParserWithRetry(
+ name, sizeInBytes,
TsFileInsertionEventParser.class.getSimpleName()));
}
@Override
@@ -90,8 +87,7 @@ public interface TsFileInsertionEventParserMemoryManager {
final Object assigner) {
return new PipeBlock(
PipeDataNodeResourceManager.memory()
- .forceAllocateForTabletWithRetry(
- name, sizeInBytes, PipeMemoryBlockCategory.TABLET,
assigner, parent));
+ .forceAllocateForTsFileParserWithRetry(name, sizeInBytes,
assigner, parent));
}
@Override
@@ -123,15 +119,10 @@ public interface TsFileInsertionEventParserMemoryManager {
@Override
public void forceResize(final long newSizeInBytes) {
final long oldSize = delegate.getMemoryUsageInBytes();
- if (!PipeDataNodeResourceManager.memory().tryResize(delegate,
newSizeInBytes)) {
+ final PipeMemoryManager memoryManager =
PipeDataNodeResourceManager.memory();
+ if (!memoryManager.tryResize(delegate, newSizeInBytes)) {
throw new PipeRuntimeOutOfMemoryCriticalException(
- String.format(
- DataNodePipeMessages
-
.PIPE_EXCEPTION_FORCERESIZE_FAILED_TO_ALLOCATE_MEMORY_AFTER_D_RETRIES_TOTAL_8C6948BC,
- 0,
-
PipeDataNodeResourceManager.memory().getTotalNonFloatingMemorySizeInBytes(),
-
PipeDataNodeResourceManager.memory().getUsedMemorySizeInBytes(),
- newSizeInBytes - oldSize));
+ memoryManager.getResizeFailureMessage(0, newSizeInBytes -
oldSize));
}
}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/memory/PipeMemoryBlockCategory.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/memory/PipeMemoryBlockCategory.java
index 6b3fce264f3..0427e226d18 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/memory/PipeMemoryBlockCategory.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/memory/PipeMemoryBlockCategory.java
@@ -48,6 +48,7 @@ public enum PipeMemoryBlockCategory {
return switch (type) {
case NORMAL -> OTHER;
case TABLET -> TABLET;
+ case TS_FILE_PARSER -> PARSER;
case TS_FILE -> TS_FILE;
case BATCH -> BATCH;
case WAL -> WAL;
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/memory/PipeMemoryBlockType.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/memory/PipeMemoryBlockType.java
index 846fc7dd1ce..78fd9aff670 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/memory/PipeMemoryBlockType.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/memory/PipeMemoryBlockType.java
@@ -22,6 +22,7 @@ package org.apache.iotdb.db.pipe.resource.memory;
public enum PipeMemoryBlockType {
NORMAL,
TABLET,
+ TS_FILE_PARSER,
TS_FILE,
BATCH,
WAL
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/memory/PipeMemoryManager.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/memory/PipeMemoryManager.java
index b86c4ab5954..3d51448df10 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/memory/PipeMemoryManager.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/memory/PipeMemoryManager.java
@@ -152,6 +152,35 @@ public class PipeMemoryManager {
* totalNonFloatingMemorySizeInBytes;
}
+ private boolean isEnoughForTsFileParserMemory(
+ final long extraMemoryInBytes, final long
totalNonFloatingMemorySizeInBytes) {
+ return (double) usedMemorySizeInBytesOfTablets
+ + (double) usedMemorySizeInBytesOfTsFiles
+ + (double) extraMemoryInBytes
+ < EXCEED_PROTECT_THRESHOLD
+ *
allowedMaxMemorySizeInBytesOfTabletsAndTsFiles(totalNonFloatingMemorySizeInBytes);
+ }
+
+ /** Reports both global availability and the category limits that can reject
a resize. */
+ public synchronized String getResizeFailureMessage(final int retries, final
long requestedSize) {
+ final long totalNonFloatingMemorySizeInBytes =
getTotalNonFloatingMemorySizeInBytes();
+ final double combinedLimit =
+
allowedMaxMemorySizeInBytesOfTabletsAndTsFiles(totalNonFloatingMemorySizeInBytes);
+ return String.format(
+ DataNodePipeMessages
+
.EXCEPTION_FORCERESIZE_FAILED_AFTER_ARG_RETRIES_TOTAL_ARG_BYTES_USED_ARG_BYTES_USEDTABLETS_ARG_BYTES_USEDTSFILES_ARG_BYTES_TABLETLIMIT_ARG_BYTES_TSFILELIMIT_ARG_BYTES_TABLETANDTSFILELIMIT_ARG_BYTES_PROTECTEDPARSERLIMIT_ARG_BYTES_REQUESTED_ARG_BYTES_311C8C7E,
+ retries,
+ totalNonFloatingMemorySizeInBytes,
+ memoryBlock.getUsedMemoryInBytes(),
+ usedMemorySizeInBytesOfTablets,
+ usedMemorySizeInBytesOfTsFiles,
+
Math.round(allowedMaxMemorySizeInBytesOfTablets(totalNonFloatingMemorySizeInBytes)),
+
Math.round(allowedMaxMemorySizeInBytesOfTsTiles(totalNonFloatingMemorySizeInBytes)),
+ Math.round(combinedLimit),
+ Math.round(EXCEED_PROTECT_THRESHOLD * combinedLimit),
+ requestedSize);
+ }
+
private static long getTsFileParserMemorySizeInBytes() {
return Math.max(
PIPE_CONFIG.getTsFileParserMemory(),
PIPE_CONFIG.getPipeMemoryAllocateMinSizeInBytes());
@@ -556,6 +585,10 @@ public class PipeMemoryManager {
private boolean isHardEnoughForResizing(
final PipeMemoryBlock block, final long extraMemoryInBytes) {
+ if (block instanceof PipeTsFileParserMemoryBlock) {
+ return isEnoughForTsFileParserMemory(
+ extraMemoryInBytes, getTotalNonFloatingMemorySizeInBytes());
+ }
if (!(block instanceof PipeTabletMemoryBlock) && !(block instanceof
PipeTsFileMemoryBlock)) {
return true;
}
@@ -757,6 +790,39 @@ public class PipeMemoryManager {
name, tabletSizeInBytes, PipeMemoryBlockCategory.EVENT_CHILD, null,
parent);
}
+ /** Allocates TsFile parser memory against the protected tablet + TsFile
combined quota. */
+ public PipeTsFileParserMemoryBlock forceAllocateForTsFileParserWithRetry(
+ final String name, final long parserMemorySizeInBytes, final Object
assigner) {
+ return forceAllocateForTsFileParserWithRetry(name,
parserMemorySizeInBytes, assigner, null);
+ }
+
+ /** Allocates TsFile parser memory as an event child against the combined
quota. */
+ public synchronized PipeTsFileParserMemoryBlock
forceAllocateForTsFileParserWithRetry(
+ final String name,
+ final long parserMemorySizeInBytes,
+ final Object assigner,
+ final PipeMemoryBlock parent) {
+ final PipeMemoryBlockCategory category =
+ parent == null ? PipeMemoryBlockCategory.PARSER :
PipeMemoryBlockCategory.EVENT_CHILD;
+ final PipeTsFileParserMemoryBlock block =
+ (PipeTsFileParserMemoryBlock)
+ registerMemoryBlock(
+ name, 0, PipeMemoryBlockType.TS_FILE_PARSER, category,
assigner, parent);
+ if (!PIPE_MEMORY_MANAGEMENT_ENABLED || parserMemorySizeInBytes == 0) {
+ return block;
+ }
+
+ try {
+ // Initial allocation and subsequent growth must check the same
post-allocation usage
+ // under the manager lock, including after a wait releases that lock.
+ forceResize(block, parserMemorySizeInBytes);
+ return block;
+ } catch (final RuntimeException e) {
+ release(block);
+ throw e;
+ }
+ }
+
public PipeTsFileMemoryBlock forceAllocateForTsFileWithRetry(
final String name, final long tsFileSizeInBytes)
throws PipeRuntimeOutOfMemoryCriticalException {
@@ -1001,17 +1067,14 @@ public class PipeMemoryManager {
if (force) {
throw new PipeRuntimeOutOfMemoryCriticalException(
- String.format(
- DataNodePipeMessages
-
.PIPE_EXCEPTION_FORCERESIZE_FAILED_TO_ALLOCATE_MEMORY_AFTER_D_RETRIES_TOTAL_8C6948BC,
- memoryAllocateMaxRetries,
- getTotalNonFloatingMemorySizeInBytes(),
- memoryBlock.getUsedMemoryInBytes(),
- sizeInBytes));
+ getResizeFailureMessage(memoryAllocateMaxRetries, sizeInBytes));
}
}
private boolean tryResizeInternal(final PipeMemoryBlock block, final long
targetSize) {
+ if (block.isReleased()) {
+ return false;
+ }
// Parent blocks expose an aggregate usage. Do not let a direct resize
reduce that aggregate
// below the bytes still owned by live children.
final long normalizedTargetSize =
@@ -1249,6 +1312,11 @@ public class PipeMemoryManager {
returnedMemoryBlock =
new PipeTabletMemoryBlock(this, name, 0, normalizedCategory, null,
normalizedParent);
break;
+ case TS_FILE_PARSER:
+ returnedMemoryBlock =
+ new PipeTsFileParserMemoryBlock(
+ this, name, 0, normalizedCategory, null, normalizedParent);
+ break;
case TS_FILE:
returnedMemoryBlock =
new PipeTsFileMemoryBlock(this, name, 0, normalizedCategory, null,
normalizedParent);
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/memory/PipeTsFileParserMemoryBlock.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/memory/PipeTsFileParserMemoryBlock.java
new file mode 100644
index 00000000000..999610b7517
--- /dev/null
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/memory/PipeTsFileParserMemoryBlock.java
@@ -0,0 +1,33 @@
+/*
+ * 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.pipe.resource.memory;
+
+/** Parser working memory charged to TsFiles and admitted against the
protected combined quota. */
+public class PipeTsFileParserMemoryBlock extends PipeTsFileMemoryBlock {
+
+ PipeTsFileParserMemoryBlock(
+ final PipeMemoryManager pipeMemoryManager,
+ final String name,
+ final long memoryUsageInBytes,
+ final PipeMemoryBlockCategory category,
+ final String assigner,
+ final PipeMemoryBlock parent) {
+ super(pipeMemoryManager, name, memoryUsageInBytes, category, assigner,
parent);
+ }
+}
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/resource/memory/PipeMemoryManagerResizeTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/resource/memory/PipeMemoryManagerResizeTest.java
index 9fd6820fece..85cfb650d9c 100644
---
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/resource/memory/PipeMemoryManagerResizeTest.java
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/resource/memory/PipeMemoryManagerResizeTest.java
@@ -41,10 +41,16 @@ import org.junit.Test;
import org.mockito.Mockito;
import java.lang.reflect.Field;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+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 java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
@@ -117,6 +123,180 @@ public class PipeMemoryManagerResizeTest {
}
}
+ @Test
+ public void testTsFileParserAllocationAndResizeShareProtectedCombinedLimit()
{
+ final AtomicLong floatingMemoryUsageInBytes = new AtomicLong(0);
+ final PipeMemoryManager manager =
+ new PipeMemoryManager(
+ new AtomicLongMemoryBlock(
+ "PipeMemoryManagerResizeTest",
+ null,
+ TOTAL_MEMORY_SIZE_IN_BYTES,
+ MemoryBlockType.DYNAMIC),
+ floatingMemoryUsageInBytes::get);
+ final PipeTsFileParserMemoryBlock parser =
+ manager.forceAllocateForTsFileParserWithRetry("parser", 1000, "test");
+
+ try {
+ Assert.assertTrue(manager.tryResize(parser, 1100));
+ Assert.assertFalse(manager.tryResize(parser, 1140));
+ Assert.assertEquals(1100, parser.getMemoryUsageInBytes());
+ Assert.assertEquals(0, manager.getUsedMemorySizeInBytesOfTablets());
+ Assert.assertEquals(1100, manager.getUsedMemorySizeInBytesOfTsFiles());
+ Assert.assertEquals(1100, manager.getUsedMemorySizeInBytes());
+
+ final PipeRuntimeOutOfMemoryCriticalException allocationException =
+ Assert.assertThrows(
+ PipeRuntimeOutOfMemoryCriticalException.class,
+ () -> manager.forceAllocateForTsFileParserWithRetry("parser-2",
40, "test"));
+ Assert.assertTrue(
+ allocationException.getMessage(),
+ allocationException.getMessage().contains("usedTsFiles=1100 bytes"));
+ Assert.assertTrue(
+ allocationException.getMessage(),
+ allocationException.getMessage().contains("tabletLimit=900 bytes"));
+ Assert.assertTrue(
+ allocationException.getMessage().contains("tabletAndTsFileLimit=1200
bytes"));
+ Assert.assertTrue(
+ allocationException.getMessage().contains("protectedParserLimit=1140
bytes"));
+ Assert.assertEquals(2, manager.getPipeMemoryBlockInfoList().size());
+
+ // Parser working memory must not consume the tablet-only quota: the
generated tablet
+ // still needs to be retained and sent while the parser owns its decoded
pages/chunks.
+ final PipeTabletMemoryBlock output =
manager.forceAllocateForTabletWithRetry("output", 50);
+ try {
+ Assert.assertTrue(manager.tryResize(output, 90));
+ Assert.assertEquals(90, manager.getUsedMemorySizeInBytesOfTablets());
+ Assert.assertEquals(1100, manager.getUsedMemorySizeInBytesOfTsFiles());
+ } finally {
+ manager.release(output);
+ }
+ } finally {
+ manager.release(parser);
+ }
+
+ Assert.assertEquals(0, manager.getUsedMemorySizeInBytes());
+ }
+
+ @Test
+ public void testZeroSizedTsFileParserCanGrowBeyondTabletLimitAndRelease() {
+ final PipeMemoryManager manager =
+ new PipeMemoryManager(
+ new AtomicLongMemoryBlock(
+ "PipeMemoryManagerResizeTest",
+ null,
+ TOTAL_MEMORY_SIZE_IN_BYTES,
+ MemoryBlockType.DYNAMIC),
+ () -> 0);
+ final PipeMemoryBlock event = manager.forceAllocate("event", 0);
+ final PipeTsFileParserMemoryBlock parser =
+ manager.forceAllocateForTsFileParserWithRetry("parser", 0, "test",
event);
+ try {
+ Assert.assertTrue(manager.tryResize(parser, 1000));
+ Assert.assertEquals(1000, event.getMemoryUsageInBytes());
+ Assert.assertEquals(1000, manager.getUsedMemorySizeInBytesOfTsFiles());
+ Assert.assertEquals(1000, manager.getUsedMemorySizeInBytes());
+
+ Assert.assertTrue(manager.tryResize(parser, 100));
+ Assert.assertEquals(100, event.getMemoryUsageInBytes());
+ Assert.assertEquals(100, manager.getUsedMemorySizeInBytesOfTsFiles());
+ manager.release(event);
+ Assert.assertTrue(parser.isReleased());
+ Assert.assertEquals(0, manager.getUsedMemorySizeInBytesOfTsFiles());
+ Assert.assertEquals(0, manager.getUsedMemorySizeInBytesOfTablets());
+ Assert.assertEquals(0, manager.getUsedMemorySizeInBytes());
+ } finally {
+ manager.release(event);
+ }
+ }
+
+ @Test
+ public void
testTsFileParserSharesQuotaWithTabletsAndTsFilesAndHonorsGlobalLimit() {
+ final PipeMemoryManager manager =
+ new PipeMemoryManager(
+ new AtomicLongMemoryBlock(
+ "PipeMemoryManagerResizeTest",
+ null,
+ TOTAL_MEMORY_SIZE_IN_BYTES,
+ MemoryBlockType.DYNAMIC),
+ () -> 0);
+ final PipeTabletMemoryBlock tablet =
manager.forceAllocateForTabletWithRetry("tablet", 200);
+ final PipeTsFileMemoryBlock tsFile =
manager.forceAllocateForTsFileWithRetry("tsFile", 500);
+ final PipeTsFileParserMemoryBlock parser =
+ manager.forceAllocateForTsFileParserWithRetry("parser", 400, "test");
+ final PipeMemoryBlock sink = manager.forceAllocate("sink", 900);
+ try {
+ Assert.assertFalse(manager.tryResize(parser, 440));
+ Assert.assertFalse(manager.tryResize(parser, 401));
+ Assert.assertEquals(200, manager.getUsedMemorySizeInBytesOfTablets());
+ Assert.assertEquals(900, manager.getUsedMemorySizeInBytesOfTsFiles());
+ Assert.assertEquals(2000, manager.getUsedMemorySizeInBytes());
+
+ manager.release(sink);
+ Assert.assertTrue(manager.tryResize(parser, 439));
+ Assert.assertFalse(manager.tryResize(parser, 440));
+ Assert.assertEquals(200, manager.getUsedMemorySizeInBytesOfTablets());
+ Assert.assertEquals(939, manager.getUsedMemorySizeInBytesOfTsFiles());
+ Assert.assertEquals(1139, manager.getUsedMemorySizeInBytes());
+ } finally {
+ manager.release(tablet);
+ manager.release(tsFile);
+ manager.release(parser);
+ manager.release(sink);
+ }
+ Assert.assertEquals(0, manager.getUsedMemorySizeInBytesOfTablets());
+ Assert.assertEquals(0, manager.getUsedMemorySizeInBytesOfTsFiles());
+ Assert.assertEquals(0, manager.getUsedMemorySizeInBytes());
+ }
+
+ @Test
+ public void testConcurrentTsFileParserAllocationsCannotOvershootQuota()
throws Exception {
+ final PipeMemoryManager manager =
+ new PipeMemoryManager(
+ new AtomicLongMemoryBlock(
+ "PipeMemoryManagerResizeTest",
+ null,
+ TOTAL_MEMORY_SIZE_IN_BYTES,
+ MemoryBlockType.DYNAMIC),
+ () -> 0);
+ final ExecutorService executor = Executors.newFixedThreadPool(4);
+ final CountDownLatch start = new CountDownLatch(1);
+ final List<Future<PipeTsFileParserMemoryBlock>> futures = new
ArrayList<>();
+ final List<PipeTsFileParserMemoryBlock> allocated = new ArrayList<>();
+ try {
+ for (int i = 0; i < 20; i++) {
+ futures.add(
+ executor.submit(
+ () -> {
+ start.await();
+ try {
+ return
manager.forceAllocateForTsFileParserWithRetry("parser", 300, "test");
+ } catch (final PipeRuntimeOutOfMemoryCriticalException e) {
+ return null;
+ }
+ }));
+ }
+ start.countDown();
+ for (final Future<PipeTsFileParserMemoryBlock> future : futures) {
+ final PipeTsFileParserMemoryBlock block = future.get(10,
TimeUnit.SECONDS);
+ if (block != null) {
+ allocated.add(block);
+ }
+ }
+ Assert.assertEquals(3, allocated.size());
+ Assert.assertEquals(900, manager.getUsedMemorySizeInBytes());
+ Assert.assertEquals(0, manager.getUsedMemorySizeInBytesOfTablets());
+ Assert.assertEquals(900, manager.getUsedMemorySizeInBytesOfTsFiles());
+ Assert.assertEquals(4, manager.getPipeMemoryBlockInfoList().size());
+ } finally {
+ executor.shutdownNow();
+ allocated.forEach(manager::release);
+ }
+ Assert.assertEquals(0, manager.getUsedMemorySizeInBytes());
+ Assert.assertEquals(0, manager.getUsedMemorySizeInBytesOfTsFiles());
+ Assert.assertEquals(0, manager.getUsedMemorySizeInBytesOfTablets());
+ }
+
@Test
public void testTabletResizeLeavesMemoryForSinkForwardProgress() {
final PipeMemoryManager manager =