morrySnow commented on code in PR #66473:
URL: https://github.com/apache/doris/pull/66473#discussion_r3800531493


##########
fe/fe-core/src/main/java/org/apache/doris/datasource/paimon/PaimonUtil.java:
##########
@@ -633,14 +635,73 @@ public static List<Column> parseSchema(RowType rowType, 
List<String> primaryKeys
     }
 
     public static <T> String encodeObjectToString(T t) {
+        byte[] bytes = serializeObject(t);
+        return new String(BASE64_ENCODER.encode(bytes), 
java.nio.charset.StandardCharsets.UTF_8);
+    }
+
+    public static <T> byte[] serializeObject(T object) {
+        try {
+            return InstantiationUtil.serializeObject(object);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static <T> Optional<byte[]> serializeObjectWithinLimit(T object, 
long maxBytes) {
+        LimitedByteArrayOutputStream output = new 
LimitedByteArrayOutputStream(maxBytes);
+        try (ObjectOutputStream objectOutput = new ObjectOutputStream(output)) 
{
+            objectOutput.writeObject(object);
+            objectOutput.flush();
+            return Optional.of(output.toByteArray());
+        } catch (SerializationSizeLimitException e) {
+            return Optional.empty();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static <T> T deserializeObject(byte[] bytes) {
         try {
-            byte[] bytes = InstantiationUtil.serializeObject(t);
-            return new String(BASE64_ENCODER.encode(bytes), 
java.nio.charset.StandardCharsets.UTF_8);
+            return InstantiationUtil.deserializeObject(bytes, 
PaimonUtil.class.getClassLoader());
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
     }
 
+    private static final class LimitedByteArrayOutputStream extends 
OutputStream {

Review Comment:
   这种可以放到fe-foundation或者 fe-common中



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/StatementContext.java:
##########
@@ -910,6 +917,7 @@ protected void finalize() throws Throwable {
 
     @Override
     public void close() {
+        clearExternalScanTasks();

Review Comment:
   这个为什么不放在 `releasePlannerResources` 里?



##########
fe/fe-core/src/main/java/org/apache/doris/job/executor/TaskProcessor.java:
##########
@@ -82,6 +84,22 @@ private void runTask(AbstractTask task) {
             task.runTask();
         } catch (Exception e) {
             log.warn("Execute task error, task id: {}", task.getTaskId(), e);
+        } finally {

Review Comment:
   内存泄露的,用单独的PR修复



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/FileQueryScanNode.java:
##########
@@ -136,6 +142,46 @@ public FileQueryScanNode(PlanNodeId id, TupleDescriptor 
desc, String planNodeNam
             StatisticalType statisticalType, ScanContext scanContext, boolean 
needCheckColumnPriv, SessionVariable sv) {
         super(id, desc, planNodeName, statisticalType, scanContext, 
needCheckColumnPriv);
         this.sessionVariable = sv;
+        ConnectContext context = ConnectContext.get();
+        StatementContext statementContext = context == null ? null : 
context.getStatementContext();
+        this.externalScanTaskCache = statementContext == null
+                ? null : statementContext.getExternalScanTaskCache();
+    }
+
+    protected <T> List<T> getOrLoadExternalScanTasks(
+            ExternalScanTaskCacheKey<T> key, Callable<List<T>> loader) throws 
Exception {
+        if (!sessionVariable.enableExternalScanTaskReuse || 
externalScanTaskCache == null) {
+            return loader.call();
+        }
+        return externalScanTaskCache.getOrLoad(key, loader);
+    }
+
+    protected <T> List<T> getOrLoadExternalScanTasks(
+            ExternalScanTaskCacheKey<T> key, Callable<List<T>> loader,
+            ToLongFunction<List<T>> weigher, long maxRetainedWeight) throws 
Exception {
+        if (!sessionVariable.enableExternalScanTaskReuse || 
externalScanTaskCache == null) {
+            return loader.call();
+        }
+        return externalScanTaskCache.getOrLoad(key, loader, weigher, 
maxRetainedWeight);
+    }

Review Comment:
   没有用到的函数



##########
fe/fe-core/src/main/java/org/apache/doris/dictionary/DictionaryManager.java:
##########
@@ -453,129 +454,143 @@ public void dataLoad(ConnectContext ctx, Dictionary 
dictionary, boolean adaptive
             Thread.sleep(10);
         }
 
-        if (ctx == null) { // for run with scheduler, not by command.
+        boolean ownsContext = ctx == null;

Review Comment:
   这个内存泄漏,单独提一个PR比较好呀



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/hive/HiveExternalMetaCache.java:
##########
@@ -980,6 +1017,7 @@ public String toString() {
     @Data
     public static class FileCacheValue {
         private final List<HiveFileStatus> files = Lists.newArrayList();
+        private long cacheGeneration;

Review Comment:
   增加注释,解释这个变量的意义



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/StatementContext.java:
##########
@@ -1366,6 +1377,261 @@ public List<org.apache.iceberg.FileScanTask> 
getAndClearIcebergRewriteFileScanTa
         return tasks;
     }
 
+    public ExternalScanTaskCache getExternalScanTaskCache() {
+        return externalScanTaskCache;
+    }
+
+    /**
+     * Release scan tasks at the end of one execution without closing reusable 
prepared-statement
+     * state. Delayed scan work retains only the invalidated generation and 
cannot repopulate this
+     * StatementContext.
+     */
+    public void clearExternalScanTasks() {
+        externalScanTaskCache.invalidate();
+    }
+
+    /**
+     * One execution generation of statement-scoped external scan tasks.
+     *
+     * <p>Scan nodes capture this object when they are constructed. Resetting 
a prepared statement
+     * swaps the generation before invalidating the old one, so a delayed 
asynchronous scan from
+     * the previous execution cannot insert tasks into the next execution's 
cache.
+     */
+    public static final class ExternalScanTaskCache {
+        /** Maximum number of connector tasks retained by one statement cache 
generation. */
+        public static final long MAX_RETAINED_TASK_COUNT = 10_000;
+
+        /** Loads a value using the weight atomically reserved for this cache 
entry. */
+        @FunctionalInterface
+        public interface WeightedLoader<T> {
+            List<T> load(long reservedWeight) throws Exception;
+        }
+
+        /** Independent cumulative budgets used by task-count and 
serialized-byte retention. */
+        public enum WeightBudget {
+            TASK_COUNT,
+            ICEBERG_SERIALIZED_BYTES,
+            PAIMON_SERIALIZED_BYTES
+        }
+
+        private final Map<ExternalScanTaskCacheKey<?>, 
CompletableFuture<List<?>>> tasks =
+                new ConcurrentHashMap<>();
+        private long retainedTaskCount;
+        private long retainedIcebergBytes;
+        private long retainedPaimonBytes;
+        private long reservedIcebergBytes;
+        private long reservedPaimonBytes;
+        private boolean invalidated;
+
+        /**
+         * Return the tasks for {@code key}, loading and publishing an 
immutable result once per
+         * cache generation.
+         */
+        @SuppressWarnings("unchecked")
+        public <T> List<T> getOrLoad(
+                ExternalScanTaskCacheKey<T> key, Callable<List<T>> loader) 
throws Exception {
+            return getOrLoad(key, ignored -> loader.call(), tasks -> 
Math.max(1, tasks.size()),
+                    WeightBudget.TASK_COUNT, MAX_RETAINED_TASK_COUNT,
+                    MAX_RETAINED_TASK_COUNT, false);
+        }
+
+        /**
+         * Return the tasks for {@code key}, retaining the loaded result only 
when its weight fits
+         * within the cache generation's cumulative limit.
+         */
+        @SuppressWarnings("unchecked")
+        public <T> List<T> getOrLoad(
+                ExternalScanTaskCacheKey<T> key, Callable<List<T>> loader,
+                ToLongFunction<List<T>> weigher, long maxRetainedWeight) 
throws Exception {
+            return getOrLoad(key, ignored -> loader.call(), weigher,
+                    WeightBudget.TASK_COUNT, maxRetainedWeight, 
maxRetainedWeight, false);
+        }
+
+        /**
+         * Return the tasks for {@code key}, optionally reserving an entry 
allowance before running
+         * the loader. A reserving loader receives the remaining allowance so 
it can stop producing
+         * a cache value early; other loaders are admitted atomically by their 
actual weight.
+         */
+        @SuppressWarnings("unchecked")
+        public <T> List<T> getOrLoad(
+                ExternalScanTaskCacheKey<T> key, WeightedLoader<T> loader,
+                ToLongFunction<List<T>> weigher, WeightBudget weightBudget,
+                long maxEntryWeight, long maxRetainedWeight,
+                boolean reserveBeforeLoad) throws Exception {
+            CompletableFuture<List<?>> newLoad = new CompletableFuture<>();
+            CompletableFuture<List<?>> load;
+            boolean cacheable;
+            long reservedWeight = 0;
+            synchronized (this) {

Review Comment:
   这个锁真的有必要吗?StatementContext什么时候会面对多线程?



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/StatementContext.java:
##########
@@ -1366,6 +1377,261 @@ public List<org.apache.iceberg.FileScanTask> 
getAndClearIcebergRewriteFileScanTa
         return tasks;
     }
 
+    public ExternalScanTaskCache getExternalScanTaskCache() {
+        return externalScanTaskCache;
+    }
+
+    /**
+     * Release scan tasks at the end of one execution without closing reusable 
prepared-statement
+     * state. Delayed scan work retains only the invalidated generation and 
cannot repopulate this
+     * StatementContext.
+     */
+    public void clearExternalScanTasks() {
+        externalScanTaskCache.invalidate();
+    }
+
+    /**
+     * One execution generation of statement-scoped external scan tasks.
+     *
+     * <p>Scan nodes capture this object when they are constructed. Resetting 
a prepared statement
+     * swaps the generation before invalidating the old one, so a delayed 
asynchronous scan from
+     * the previous execution cannot insert tasks into the next execution's 
cache.
+     */
+    public static final class ExternalScanTaskCache {
+        /** Maximum number of connector tasks retained by one statement cache 
generation. */
+        public static final long MAX_RETAINED_TASK_COUNT = 10_000;

Review Comment:
   这个10000是怎么来的?需要改成可配置的吗?



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/hive/HiveExternalMetaCache.java:
##########
@@ -119,6 +121,8 @@ public class HiveExternalMetaCache extends 
AbstractExternalMetaCache {
     public static final String ERR_CACHE_INCONSISTENCY = 
"ERR_CACHE_INCONSISTENCY: ";
 
     private final ExecutorService fileListingExecutor;
+    private final Map<Long, AtomicLong> fileCacheInvalidationGenerations = new 
ConcurrentHashMap<>();
+    private final Map<Long, AtomicLong> fileCacheValueGenerations = new 
ConcurrentHashMap<>();

Review Comment:
   增加注释,解释这俩是干什么用的,在什么情况下需要增加generation,在什么情况下不能增加generation



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/StatementContext.java:
##########
@@ -1366,6 +1377,261 @@ public List<org.apache.iceberg.FileScanTask> 
getAndClearIcebergRewriteFileScanTa
         return tasks;
     }
 
+    public ExternalScanTaskCache getExternalScanTaskCache() {
+        return externalScanTaskCache;
+    }
+
+    /**
+     * Release scan tasks at the end of one execution without closing reusable 
prepared-statement
+     * state. Delayed scan work retains only the invalidated generation and 
cannot repopulate this
+     * StatementContext.
+     */
+    public void clearExternalScanTasks() {
+        externalScanTaskCache.invalidate();
+    }
+
+    /**
+     * One execution generation of statement-scoped external scan tasks.
+     *
+     * <p>Scan nodes capture this object when they are constructed. Resetting 
a prepared statement
+     * swaps the generation before invalidating the old one, so a delayed 
asynchronous scan from
+     * the previous execution cannot insert tasks into the next execution's 
cache.
+     */
+    public static final class ExternalScanTaskCache {
+        /** Maximum number of connector tasks retained by one statement cache 
generation. */
+        public static final long MAX_RETAINED_TASK_COUNT = 10_000;
+
+        /** Loads a value using the weight atomically reserved for this cache 
entry. */
+        @FunctionalInterface
+        public interface WeightedLoader<T> {
+            List<T> load(long reservedWeight) throws Exception;
+        }
+
+        /** Independent cumulative budgets used by task-count and 
serialized-byte retention. */
+        public enum WeightBudget {
+            TASK_COUNT,
+            ICEBERG_SERIALIZED_BYTES,
+            PAIMON_SERIALIZED_BYTES
+        }
+
+        private final Map<ExternalScanTaskCacheKey<?>, 
CompletableFuture<List<?>>> tasks =
+                new ConcurrentHashMap<>();
+        private long retainedTaskCount;
+        private long retainedIcebergBytes;
+        private long retainedPaimonBytes;
+        private long reservedIcebergBytes;
+        private long reservedPaimonBytes;

Review Comment:
   感觉不用具体到每一种类型,抽象成 retrain 和 reserved 就可以了



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/hive/source/HiveScanNode.java:
##########
@@ -320,8 +324,31 @@ private void 
getFileSplitByPartitions(HiveExternalMetaCache cache, List<HivePart
             }
         } else {
             boolean withCache = Config.max_external_file_cache_num > 0;
-            fileCaches = cache.getFilesByPartitions(partitions, withCache, 
partitions.size() > 1,
-                    directoryLister, hmsTable);
+            if (isBatchMode || !withCache) {
+                // Batch mode bounds FE memory by retaining only the 
partitions currently in flight.
+                // Keeping every completed partition in the statement cache 
would materialize the
+                // full scan again and defeat that bound. When the global file 
cache is disabled,
+                // statement retention must not become an uncapped replacement 
for that memory fence.
+                fileCaches = cache.getFilesByPartitions(partitions, withCache, 
partitions.size() > 1,
+                        directoryLister, hmsTable);
+            } else {
+                List<FileCacheValue> currentFileCaches = 
cache.getFilesByPartitions(partitions, true,
+                        partitions.size() > 1, directoryLister, hmsTable);
+                HiveFileScanTaskCacheKey cacheKey = new 
HiveFileScanTaskCacheKey(
+                        hmsTable.getCatalog().getId(), hmsTable.getId(), 
partitions,
+                        
cache.getFileCacheInvalidationGeneration(hmsTable.getCatalog().getId()), 
currentFileCaches);
+                try {

Review Comment:
   看起来hive这段代码是没必要的,会导致重复cache?



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/StatementContext.java:
##########
@@ -1366,6 +1377,261 @@ public List<org.apache.iceberg.FileScanTask> 
getAndClearIcebergRewriteFileScanTa
         return tasks;
     }
 
+    public ExternalScanTaskCache getExternalScanTaskCache() {
+        return externalScanTaskCache;
+    }
+
+    /**
+     * Release scan tasks at the end of one execution without closing reusable 
prepared-statement
+     * state. Delayed scan work retains only the invalidated generation and 
cannot repopulate this
+     * StatementContext.
+     */
+    public void clearExternalScanTasks() {
+        externalScanTaskCache.invalidate();
+    }
+
+    /**
+     * One execution generation of statement-scoped external scan tasks.
+     *
+     * <p>Scan nodes capture this object when they are constructed. Resetting 
a prepared statement
+     * swaps the generation before invalidating the old one, so a delayed 
asynchronous scan from
+     * the previous execution cannot insert tasks into the next execution's 
cache.
+     */
+    public static final class ExternalScanTaskCache {
+        /** Maximum number of connector tasks retained by one statement cache 
generation. */
+        public static final long MAX_RETAINED_TASK_COUNT = 10_000;
+
+        /** Loads a value using the weight atomically reserved for this cache 
entry. */
+        @FunctionalInterface
+        public interface WeightedLoader<T> {
+            List<T> load(long reservedWeight) throws Exception;
+        }
+
+        /** Independent cumulative budgets used by task-count and 
serialized-byte retention. */
+        public enum WeightBudget {
+            TASK_COUNT,
+            ICEBERG_SERIALIZED_BYTES,
+            PAIMON_SERIALIZED_BYTES
+        }
+
+        private final Map<ExternalScanTaskCacheKey<?>, 
CompletableFuture<List<?>>> tasks =
+                new ConcurrentHashMap<>();
+        private long retainedTaskCount;
+        private long retainedIcebergBytes;
+        private long retainedPaimonBytes;
+        private long reservedIcebergBytes;
+        private long reservedPaimonBytes;
+        private boolean invalidated;
+
+        /**
+         * Return the tasks for {@code key}, loading and publishing an 
immutable result once per
+         * cache generation.
+         */
+        @SuppressWarnings("unchecked")
+        public <T> List<T> getOrLoad(
+                ExternalScanTaskCacheKey<T> key, Callable<List<T>> loader) 
throws Exception {
+            return getOrLoad(key, ignored -> loader.call(), tasks -> 
Math.max(1, tasks.size()),
+                    WeightBudget.TASK_COUNT, MAX_RETAINED_TASK_COUNT,
+                    MAX_RETAINED_TASK_COUNT, false);
+        }
+
+        /**
+         * Return the tasks for {@code key}, retaining the loaded result only 
when its weight fits
+         * within the cache generation's cumulative limit.
+         */
+        @SuppressWarnings("unchecked")
+        public <T> List<T> getOrLoad(
+                ExternalScanTaskCacheKey<T> key, Callable<List<T>> loader,
+                ToLongFunction<List<T>> weigher, long maxRetainedWeight) 
throws Exception {
+            return getOrLoad(key, ignored -> loader.call(), weigher,
+                    WeightBudget.TASK_COUNT, maxRetainedWeight, 
maxRetainedWeight, false);
+        }
+
+        /**
+         * Return the tasks for {@code key}, optionally reserving an entry 
allowance before running
+         * the loader. A reserving loader receives the remaining allowance so 
it can stop producing
+         * a cache value early; other loaders are admitted atomically by their 
actual weight.
+         */
+        @SuppressWarnings("unchecked")
+        public <T> List<T> getOrLoad(
+                ExternalScanTaskCacheKey<T> key, WeightedLoader<T> loader,
+                ToLongFunction<List<T>> weigher, WeightBudget weightBudget,
+                long maxEntryWeight, long maxRetainedWeight,
+                boolean reserveBeforeLoad) throws Exception {
+            CompletableFuture<List<?>> newLoad = new CompletableFuture<>();
+            CompletableFuture<List<?>> load;
+            boolean cacheable;

Review Comment:
   为啥要引入这个变量?



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/StatementContext.java:
##########
@@ -1366,6 +1377,261 @@ public List<org.apache.iceberg.FileScanTask> 
getAndClearIcebergRewriteFileScanTa
         return tasks;
     }
 
+    public ExternalScanTaskCache getExternalScanTaskCache() {
+        return externalScanTaskCache;
+    }
+
+    /**
+     * Release scan tasks at the end of one execution without closing reusable 
prepared-statement
+     * state. Delayed scan work retains only the invalidated generation and 
cannot repopulate this
+     * StatementContext.
+     */
+    public void clearExternalScanTasks() {
+        externalScanTaskCache.invalidate();
+    }
+
+    /**
+     * One execution generation of statement-scoped external scan tasks.
+     *
+     * <p>Scan nodes capture this object when they are constructed. Resetting 
a prepared statement
+     * swaps the generation before invalidating the old one, so a delayed 
asynchronous scan from
+     * the previous execution cannot insert tasks into the next execution's 
cache.
+     */
+    public static final class ExternalScanTaskCache {
+        /** Maximum number of connector tasks retained by one statement cache 
generation. */
+        public static final long MAX_RETAINED_TASK_COUNT = 10_000;
+
+        /** Loads a value using the weight atomically reserved for this cache 
entry. */
+        @FunctionalInterface
+        public interface WeightedLoader<T> {
+            List<T> load(long reservedWeight) throws Exception;
+        }
+
+        /** Independent cumulative budgets used by task-count and 
serialized-byte retention. */
+        public enum WeightBudget {
+            TASK_COUNT,
+            ICEBERG_SERIALIZED_BYTES,
+            PAIMON_SERIALIZED_BYTES
+        }
+
+        private final Map<ExternalScanTaskCacheKey<?>, 
CompletableFuture<List<?>>> tasks =
+                new ConcurrentHashMap<>();
+        private long retainedTaskCount;
+        private long retainedIcebergBytes;
+        private long retainedPaimonBytes;
+        private long reservedIcebergBytes;
+        private long reservedPaimonBytes;
+        private boolean invalidated;
+
+        /**
+         * Return the tasks for {@code key}, loading and publishing an 
immutable result once per
+         * cache generation.
+         */
+        @SuppressWarnings("unchecked")
+        public <T> List<T> getOrLoad(
+                ExternalScanTaskCacheKey<T> key, Callable<List<T>> loader) 
throws Exception {
+            return getOrLoad(key, ignored -> loader.call(), tasks -> 
Math.max(1, tasks.size()),
+                    WeightBudget.TASK_COUNT, MAX_RETAINED_TASK_COUNT,
+                    MAX_RETAINED_TASK_COUNT, false);
+        }
+
+        /**
+         * Return the tasks for {@code key}, retaining the loaded result only 
when its weight fits
+         * within the cache generation's cumulative limit.
+         */
+        @SuppressWarnings("unchecked")
+        public <T> List<T> getOrLoad(
+                ExternalScanTaskCacheKey<T> key, Callable<List<T>> loader,
+                ToLongFunction<List<T>> weigher, long maxRetainedWeight) 
throws Exception {
+            return getOrLoad(key, ignored -> loader.call(), weigher,
+                    WeightBudget.TASK_COUNT, maxRetainedWeight, 
maxRetainedWeight, false);
+        }
+
+        /**
+         * Return the tasks for {@code key}, optionally reserving an entry 
allowance before running
+         * the loader. A reserving loader receives the remaining allowance so 
it can stop producing
+         * a cache value early; other loaders are admitted atomically by their 
actual weight.
+         */
+        @SuppressWarnings("unchecked")
+        public <T> List<T> getOrLoad(
+                ExternalScanTaskCacheKey<T> key, WeightedLoader<T> loader,
+                ToLongFunction<List<T>> weigher, WeightBudget weightBudget,
+                long maxEntryWeight, long maxRetainedWeight,
+                boolean reserveBeforeLoad) throws Exception {
+            CompletableFuture<List<?>> newLoad = new CompletableFuture<>();
+            CompletableFuture<List<?>> load;
+            boolean cacheable;
+            long reservedWeight = 0;
+            synchronized (this) {
+                cacheable = !invalidated;
+                if (cacheable) {
+                    load = tasks.putIfAbsent(key, newLoad);
+                    if (load == null && reserveBeforeLoad) {
+                        long retainedWeight = retainedWeight(weightBudget);
+                        long alreadyReservedWeight = 
reservedWeight(weightBudget);
+                        long availableWeight = Math.max(
+                                0, maxRetainedWeight - retainedWeight - 
alreadyReservedWeight);
+                        reservedWeight = Math.min(maxEntryWeight, 
availableWeight);
+                        addReservedWeight(weightBudget, reservedWeight);
+                    }
+                } else {
+                    load = null;
+                }
+            }
+            if (!cacheable) {
+                return immutableCopy(loader.load(0));
+            }
+            if (load == null) {
+                List<T> loadedTasks;
+                long weight;
+                try {
+                    loadedTasks = loader.load(reserveBeforeLoad ? 
reservedWeight : maxEntryWeight);
+                    weight = weigher.applyAsLong(loadedTasks);
+                } catch (Exception | Error throwable) {
+                    synchronized (this) {
+                        if (reserveBeforeLoad) {
+                            releaseReservation(weightBudget, reservedWeight);
+                        }
+                        tasks.remove(key, newLoad);
+                    }
+                    newLoad.completeExceptionally(throwable);
+                    throw throwable;
+                }
+                boolean mayRetain;
+                synchronized (this) {
+                    long availableWeight = Math.max(
+                            0, maxRetainedWeight - 
retainedWeight(weightBudget));
+                    mayRetain = !invalidated && weight <= availableWeight
+                            && (!reserveBeforeLoad || weight <= 
reservedWeight);
+                }
+                List<T> retainedCopy = null;
+                boolean reservationReleased = false;
+                boolean retainedCommitted = false;
+                try {
+                    if (mayRetain) {
+                        retainedCopy = immutableCopy(loadedTasks);
+                    }
+                    synchronized (this) {
+                        if (reserveBeforeLoad) {
+                            releaseReservation(weightBudget, reservedWeight);
+                            reservationReleased = true;
+                        }
+                        long availableWeight = Math.max(
+                                0, maxRetainedWeight - 
retainedWeight(weightBudget));
+                        retainedCommitted = mayRetain && !invalidated && 
weight <= availableWeight;
+                        if (retainedCommitted) {
+                            addRetainedWeight(weightBudget, weight);
+                        } else {
+                            tasks.remove(key, newLoad);
+                        }
+                    }
+                    List<T> result = retainedCommitted ? retainedCopy : 
loadedTasks;
+                    newLoad.complete(result);
+                    return result;
+                } catch (Exception | Error throwable) {
+                    synchronized (this) {
+                        if (retainedCommitted) {
+                            addRetainedWeight(weightBudget, -weight);
+                        }
+                        if (reserveBeforeLoad && !reservationReleased) {
+                            releaseReservation(weightBudget, reservedWeight);
+                        }
+                        tasks.remove(key, newLoad);
+                    }
+                    newLoad.completeExceptionally(throwable);
+                    throw throwable;
+                }
+            }
+            try {
+                return (List<T>) load.get();
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                throw e;
+            } catch (ExecutionException e) {
+                Throwable cause = e.getCause();
+                if (cause instanceof Exception) {
+                    throw (Exception) cause;
+                }
+                throw (Error) cause;
+            }
+        }
+
+        private synchronized void invalidate() {
+            invalidated = true;
+            tasks.clear();
+            retainedTaskCount = 0;
+            retainedIcebergBytes = 0;
+            retainedPaimonBytes = 0;
+            reservedIcebergBytes = 0;
+            reservedPaimonBytes = 0;
+        }
+
+        private void releaseReservation(WeightBudget weightBudget, long 
reservedWeight) {
+            addReservedWeight(weightBudget, -reservedWeight);
+        }
+
+        private long retainedWeight(WeightBudget weightBudget) {
+            switch (weightBudget) {
+                case TASK_COUNT:
+                    return retainedTaskCount;
+                case ICEBERG_SERIALIZED_BYTES:
+                    return retainedIcebergBytes;
+                case PAIMON_SERIALIZED_BYTES:
+                    return retainedPaimonBytes;
+                default:
+                    throw new IllegalStateException("Unknown external scan 
task weight budget: " + weightBudget);
+            }
+        }
+
+        private long reservedWeight(WeightBudget weightBudget) {
+            switch (weightBudget) {
+                case TASK_COUNT:
+                    return 0;
+                case ICEBERG_SERIALIZED_BYTES:
+                    return reservedIcebergBytes;
+                case PAIMON_SERIALIZED_BYTES:
+                    return reservedPaimonBytes;
+                default:
+                    throw new IllegalStateException("Unknown external scan 
task weight budget: " + weightBudget);
+            }
+        }
+
+        private void addRetainedWeight(WeightBudget weightBudget, long weight) 
{
+            switch (weightBudget) {
+                case TASK_COUNT:
+                    retainedTaskCount += weight;
+                    break;
+                case ICEBERG_SERIALIZED_BYTES:
+                    retainedIcebergBytes += weight;
+                    break;
+                case PAIMON_SERIALIZED_BYTES:
+                    retainedPaimonBytes += weight;
+                    break;
+                default:
+                    throw new IllegalStateException("Unknown external scan 
task weight budget: " + weightBudget);
+            }
+        }
+
+        private void addReservedWeight(WeightBudget weightBudget, long weight) 
{
+            switch (weightBudget) {
+                case TASK_COUNT:
+                    break;
+                case ICEBERG_SERIALIZED_BYTES:
+                    reservedIcebergBytes += weight;
+                    break;
+                case PAIMON_SERIALIZED_BYTES:
+                    reservedPaimonBytes += weight;
+                    break;
+                default:
+                    throw new IllegalStateException("Unknown external scan 
task weight budget: " + weightBudget);
+            }
+        }
+
+        private static <T> List<T> immutableCopy(List<T> loadedTasks) {
+            return Collections.unmodifiableList(new ArrayList<>(loadedTasks));
+        }

Review Comment:
   为什么需要copy一下?



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/source/IcebergScanNode.java:
##########
@@ -887,9 +907,103 @@ private CloseableIterable<FileScanTask> 
splitFiles(TableScan scan) {
         } catch (Exception e) {
             throw new RuntimeException("Failed to materialize file scan 
tasks", e);
         }
+        return fileScanTaskList;
+    }
 
-        targetSplitSize = determineTargetFileSplitSize(fileScanTaskList);
-        return 
TableScanUtil.splitFiles(CloseableIterable.withNoopClose(fileScanTaskList), 
targetSplitSize);
+    @VisibleForTesting
+    List<FileScanTask> getOrPlanFileScanTasks(TableScan scan, 
Supplier<List<FileScanTask>> planner) {
+        try {
+            return 
getOrPlanSerializedIcebergTasks(createFileScanTaskCacheKey(scan), planner::get);
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to plan Iceberg file scan 
tasks", e);
+        }
+    }
+
+    private IcebergScanTaskCacheKey<IcebergSerializedScanTask<FileScanTask>> 
createFileScanTaskCacheKey(
+            TableScan scan) {
+        Snapshot snapshot = scan.snapshot();
+        return new IcebergScanTaskCacheKey<>(
+                source.getCatalog().getId(),
+                source.getTargetTable().getId(),
+                snapshot == null ? null : snapshot.snapshotId(),
+                scan.schema().schemaId(),
+                scan.filter(),
+                scan.isCaseSensitive(),
+                FileScanTask.class.getName());
+    }
+
+    @VisibleForTesting
+    List<PositionDeletesScanTask> getOrPlanPositionDeleteTasks(
+            BatchScan scan, Callable<List<PositionDeletesScanTask>> planner) 
throws Exception {
+        Snapshot snapshot = scan.snapshot();
+        
IcebergScanTaskCacheKey<IcebergSerializedScanTask<PositionDeletesScanTask>> 
cacheKey
+                = new IcebergScanTaskCacheKey<>(
+                source.getCatalog().getId(),
+                source.getTargetTable().getId(),
+                snapshot == null ? null : snapshot.snapshotId(),
+                scan.schema().schemaId(),
+                scan.filter(),
+                scan.isCaseSensitive(),
+                PositionDeletesScanTask.class.getName());
+        return getOrPlanSerializedIcebergTasks(cacheKey, planner);
+    }
+
+    private <T> List<T> getOrPlanSerializedIcebergTasks(
+            IcebergScanTaskCacheKey<IcebergSerializedScanTask<T>> cacheKey,
+            Callable<List<T>> planner) throws Exception {
+        try {
+            List<IcebergSerializedScanTask<T>> serializedTasks = 
getOrLoadExternalScanTasks(
+                    cacheKey,
+                    remainingBytes -> 
serializeIcebergTasksWithinLimit(planner.call(), remainingBytes),
+                    IcebergScanNode::serializedIcebergTaskBytes,
+                    
StatementContext.ExternalScanTaskCache.WeightBudget.ICEBERG_SERIALIZED_BYTES,
+                    maxRetainedSerializedTaskBytes, 
maxRetainedSerializedTaskBytes, true);
+            return serializedTasks.stream()
+                    .map(IcebergSerializedScanTask::deserialize)
+                    .collect(Collectors.toList());
+        } catch (IcebergTaskCacheLimitException e) {
+            List<T> plannedTasks = e.takePlannedTasks();
+            return plannedTasks == null ? planner.call() : plannedTasks;
+        }
+    }
+
+    private <T> List<IcebergSerializedScanTask<T>> 
serializeIcebergTasksWithinLimit(

Review Comment:
   这个和paimonUtils中的代码其实是重复的



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to