This is an automated email from the ASF dual-hosted git repository. voonhous pushed a commit to branch release-1.2.1 in repository https://gitbox.apache.org/repos/asf/hudi.git
commit d7c5a22bfe8614216f8dd65df52d6c6712276736 Author: wangxianghu <[email protected]> AuthorDate: Tue Jun 30 20:17:03 2026 +0800 fix(partition-ttl): Fix IllegalArgumentException in KeepByTimeStrategy when no candidate partitions exist (#19092) * fix(partition-ttl): Fix IllegalArgumentException in KeepByTimeStrategy when no candidate partitions exist (cherry picked from commit 3da0a9807dd11d038e73b6747de53ba57f979000) --- .../action/ttl/strategy/KeepByTimeStrategy.java | 4 ++ .../ttl/strategy/TestKeepByTimeStrategy.java | 62 ++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/hudi-client/hudi-client-common/src/main/java/org/apache/hudi/table/action/ttl/strategy/KeepByTimeStrategy.java b/hudi-client/hudi-client-common/src/main/java/org/apache/hudi/table/action/ttl/strategy/KeepByTimeStrategy.java index 329fee22cf60..8968d69f70bd 100644 --- a/hudi-client/hudi-client-common/src/main/java/org/apache/hudi/table/action/ttl/strategy/KeepByTimeStrategy.java +++ b/hudi-client/hudi-client-common/src/main/java/org/apache/hudi/table/action/ttl/strategy/KeepByTimeStrategy.java @@ -81,6 +81,10 @@ public class KeepByTimeStrategy extends PartitionTTLStrategy { * @param partitionPaths Partitions to collect stats. */ private Map<String, Option<String>> getLastCommitTimeForPartitions(List<String> partitionPaths) { + if (partitionPaths.isEmpty()) { + log.info("Candidate partition paths list is empty, skip TTL stats collection"); + return Collections.emptyMap(); + } int statsParallelism = Math.min(partitionPaths.size(), 200); return hoodieTable.getContext().map(partitionPaths, partitionPath -> { Option<String> partitionLastModifiedTime = hoodieTable.getHoodieView() diff --git a/hudi-client/hudi-client-common/src/test/java/org/apache/hudi/table/action/ttl/strategy/TestKeepByTimeStrategy.java b/hudi-client/hudi-client-common/src/test/java/org/apache/hudi/table/action/ttl/strategy/TestKeepByTimeStrategy.java new file mode 100644 index 000000000000..309eab2d6013 --- /dev/null +++ b/hudi-client/hudi-client-common/src/test/java/org/apache/hudi/table/action/ttl/strategy/TestKeepByTimeStrategy.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hudi.table.action.ttl.strategy; + +import org.apache.hudi.config.HoodieWriteConfig; +import org.apache.hudi.table.HoodieTable; + +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Tests for {@link KeepByTimeStrategy}. + */ +public class TestKeepByTimeStrategy { + + /** + * Regression test: when there are no candidate partitions to evaluate, + * the strategy must short-circuit and return an empty result instead of + * handing a parallelism of 0 to the engine, which would surface as: + * java.lang.IllegalArgumentException: Positive number of partitions required + * from ParallelCollectionRDD.slice on the Spark path. + */ + @Test + public void testGetExpiredPartitionsForTimeStrategy_emptyInput_returnsEmptyWithoutTouchingEngine() { + HoodieTable hoodieTable = mock(HoodieTable.class); + HoodieWriteConfig writeConfig = mock(HoodieWriteConfig.class); + when(hoodieTable.getConfig()).thenReturn(writeConfig); + when(writeConfig.getPartitionTTLStrategyDaysRetain()).thenReturn(10); + + KeepByTimeStrategy strategy = new KeepByTimeStrategy(hoodieTable, "20240101000000000"); + + List<String> expired = strategy.getExpiredPartitionsForTimeStrategy(Collections.emptyList()); + + assertTrue(expired.isEmpty(), "Empty candidate list should yield no expired partitions"); + // Crucial: we must never reach the engine map call with parallelism=0. + verify(hoodieTable, never()).getContext(); + } +}
