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 a29a48831df970f911b7b1dc71247dad97673983 Author: wangxianghu <[email protected]> AuthorDate: Mon Jun 29 22:29:37 2026 +0800 fix(partition-ttl): Fix the integer overflow issue when TTL exceeded 24 days (#19075) (cherry picked from commit d4d7e20771b1bf3ec20c11346452a5d2640523a5) --- .../action/ttl/strategy/KeepByTimeStrategy.java | 3 +- .../ttl/strategy/TestPartitionTTLStrategy.java | 82 ++++++++++++++++++++++ 2 files changed, 84 insertions(+), 1 deletion(-) 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 7f07c4b735d2..329fee22cf60 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 @@ -31,6 +31,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import static org.apache.hudi.common.table.timeline.HoodieInstantTimeGenerator.fixInstantTimeCompatibility; @@ -46,7 +47,7 @@ public class KeepByTimeStrategy extends PartitionTTLStrategy { public KeepByTimeStrategy(HoodieTable hoodieTable, String instantTime) { super(hoodieTable, instantTime); - this.ttlInMilis = writeConfig.getPartitionTTLStrategyDaysRetain() * 1000 * 3600 * 24; + this.ttlInMilis = TimeUnit.DAYS.toMillis(writeConfig.getPartitionTTLStrategyDaysRetain()); } @Override diff --git a/hudi-client/hudi-client-common/src/test/java/org/apache/hudi/table/action/ttl/strategy/TestPartitionTTLStrategy.java b/hudi-client/hudi-client-common/src/test/java/org/apache/hudi/table/action/ttl/strategy/TestPartitionTTLStrategy.java new file mode 100644 index 000000000000..4cad31289ec4 --- /dev/null +++ b/hudi-client/hudi-client-common/src/test/java/org/apache/hudi/table/action/ttl/strategy/TestPartitionTTLStrategy.java @@ -0,0 +1,82 @@ +/* + * 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 static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class TestPartitionTTLStrategy { + + private static final long MILLIS_PER_DAY = 24L * 3600L * 1000L; + + private KeepByTimeStrategy newStrategy(int daysRetain) { + HoodieTable hoodieTable = mock(HoodieTable.class); + HoodieWriteConfig writeConfig = mock(HoodieWriteConfig.class); + when(hoodieTable.getConfig()).thenReturn(writeConfig); + when(writeConfig.getPartitionTTLStrategyDaysRetain()).thenReturn(daysRetain); + return new KeepByTimeStrategy(hoodieTable, ""); + } + + /** + * Verify that ttlInMilis is computed in long arithmetic, so values that would + * overflow a 32-bit int multiplication (daysRetain > 24) still yield the + * correct positive millisecond value. + * + * <p>Without the {@code (long)} cast on {@code getPartitionTTLStrategyDaysRetain()}, + * the expression {@code daysRetain * 1000 * 3600 * 24} is evaluated as int and + * overflows once daysRetain * 86_400_000 exceeds Integer.MAX_VALUE + * (i.e., for any daysRetain >= 25). + */ + @Test + public void testKeepByTimeStrategyTTLInMilis() { + // Small value: no overflow either way, sanity check. + assertEquals(1L * MILLIS_PER_DAY, newStrategy(1).ttlInMilis); + + // Largest value that does NOT overflow int: 24 * 86_400_000 = 2_073_600_000. + assertEquals(24L * MILLIS_PER_DAY, newStrategy(24).ttlInMilis); + + // 25 days: 25 * 86_400_000 = 2_160_000_000, exceeds Integer.MAX_VALUE. + // With the (long) cast the result is correct; without it the int expression + // would overflow to a negative value. + int days = 25; + long expected = (long) days * MILLIS_PER_DAY; + assertEquals(expected, newStrategy(days).ttlInMilis); + assertTrue(newStrategy(days).ttlInMilis > 0, + "ttlInMilis must stay positive for daysRetain beyond the int-overflow boundary"); + // Guard against regression: the unfixed int expression would produce this + // (overflowed) value. The fixed code must NOT match it. + int overflowed = days * 1000 * 3600 * 24; + assertEquals(-2_134_967_296, overflowed, "sanity: confirm the int expression overflows for 25 days"); + assertTrue(newStrategy(days).ttlInMilis != overflowed, + "ttlInMilis must not equal the int-overflowed value"); + + // A clearly large value (e.g., 365 days) to ensure long arithmetic holds. + assertEquals(365L * MILLIS_PER_DAY, newStrategy(365).ttlInMilis); + + // Zero / disabled. + assertEquals(0L, newStrategy(0).ttlInMilis); + } +}
