This is an automated email from the ASF dual-hosted git repository.

yuqi1129 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/main by this push:
     new 9a72e5fe1b [#14694] fix(jdbc): prevent PostgreSQL timeout overflow 
(#14695)
9a72e5fe1b is described below

commit 9a72e5fe1bfd57f81a299081e19c37f3e7561591
Author: Qi Yu <[email protected]>
AuthorDate: Fri Nov 6 15:57:01 2026 +0800

    [#14694] fix(jdbc): prevent PostgreSQL timeout overflow (#14695)
    
    ### What changes were proposed in this pull request?
    
    Cap derived PostgreSQL timeouts at 2147483 whole seconds so the JDBC
    driver can convert them back to integer milliseconds safely. Add
    boundary regressions for pooled connections and create-time probes, and
    document the upper-bound rounding.
    
    ### Why are the changes needed?
    
    The valid jdbc.pool.connect-timeout-ms=2147483647 setting rounds up to
    2147484 seconds. PostgreSQL JDBC multiplies timeout seconds by 1000
    using integer arithmetic, which overflows and can cause connection
    verification to fail with "timeout can't be negative".
    
    Fixes #14694
    
    ### Does this PR introduce _any_ user-facing change?
    
    Values near the accepted millisecond upper bound now generate a safe
    driver timeout. The cap shortens those values by at most 647
    milliseconds. The accepted configuration range, normal rounding,
    operator-supplied driver properties and other driver families are
    unchanged.
    
    ### How was this patch tested?
    
    - On current main, the new boundary regressions fail in three cases
    before the fix. After the fix, 61 relevant tests across six JDBC common
    classes pass with no failures, errors or skips.
    - The prepared branch-1.3 backport applies without conflicts; all 57
    relevant tests pass with no failures, errors or skips.
    - A local TCP-listener probe using the actual translator and PostgreSQL
    JDBC 42.7.11 reproduces the negative-timeout exception before the fix
    and reaches protocol handling after the fix for 2147483001 and
    2147483647 milliseconds. This is not a live-database or deployed REST
    test.
    - Scoped Spotless and git diff --check pass.
    
    A branch-1.3 backport is prepared for submission after the main fix
    merges.
---
 .../gravitino/catalog/jdbc/config/JdbcConfig.java  |  8 ++---
 .../catalog/jdbc/utils/JdbcConnectionTimeouts.java | 12 ++++----
 .../jdbc/utils/TestJdbcConnectionTimeouts.java     | 35 +++++++++++++++-------
 docs/jdbc-catalog-connection-validation.md         |  4 ++-
 4 files changed, 39 insertions(+), 20 deletions(-)

diff --git 
a/catalogs/catalog-jdbc-common/src/main/java/org/apache/gravitino/catalog/jdbc/config/JdbcConfig.java
 
b/catalogs/catalog-jdbc-common/src/main/java/org/apache/gravitino/catalog/jdbc/config/JdbcConfig.java
index 5ef5348ab1..161152ec42 100644
--- 
a/catalogs/catalog-jdbc-common/src/main/java/org/apache/gravitino/catalog/jdbc/config/JdbcConfig.java
+++ 
b/catalogs/catalog-jdbc-common/src/main/java/org/apache/gravitino/catalog/jdbc/config/JdbcConfig.java
@@ -129,10 +129,10 @@ public class JdbcConfig extends Config {
    * otherwise block the connect-only bound forever. An operator value set 
directly on the
    * connection or in the {@code jdbc-url} is never overridden.
    *
-   * <p>The value is bounded by {@link Integer#MAX_VALUE} milliseconds because 
the MySQL and
-   * PostgreSQL driver timeouts are {@code int}s (and the PostgreSQL 
millisecond-to-seconds
-   * conversion stays within {@code int} range); a larger value would be 
accepted here and only fail
-   * later when the driver parses it.
+   * <p>The value is bounded by {@link Integer#MAX_VALUE} milliseconds to fit 
the drivers' integer
+   * millisecond socket timeouts. PostgreSQL's whole-second timeout is capped 
before conversion back
+   * to milliseconds, shortening values near the upper bound by less than one 
second to avoid
+   * overflow. Larger millisecond values are rejected here before reaching the 
driver.
    */
   public static final ConfigEntry<Long> CONNECT_TIMEOUT_MS =
       new ConfigBuilder("jdbc.pool.connect-timeout-ms")
diff --git 
a/catalogs/catalog-jdbc-common/src/main/java/org/apache/gravitino/catalog/jdbc/utils/JdbcConnectionTimeouts.java
 
b/catalogs/catalog-jdbc-common/src/main/java/org/apache/gravitino/catalog/jdbc/utils/JdbcConnectionTimeouts.java
index 88188551f0..a20d98164d 100644
--- 
a/catalogs/catalog-jdbc-common/src/main/java/org/apache/gravitino/catalog/jdbc/utils/JdbcConnectionTimeouts.java
+++ 
b/catalogs/catalog-jdbc-common/src/main/java/org/apache/gravitino/catalog/jdbc/utils/JdbcConnectionTimeouts.java
@@ -202,17 +202,19 @@ public final class JdbcConnectionTimeouts {
 
   /**
    * Converts milliseconds to seconds, rounded up with a minimum of 1 so a 
sub-second bound never
-   * collapses to 0 ("no timeout"). The arithmetic is overflow-safe for the 
whole positive {@code
-   * long} range (the input is validated positive and {@code <= 
Integer.MAX_VALUE} by {@link
-   * JdbcConfig}).
+   * collapses to 0 ("no timeout"). Cap the result at the largest whole-second 
timeout that the
+   * PostgreSQL driver can multiply by 1000 without overflowing its integer 
millisecond timeout. The
+   * input is validated positive and {@code <= Integer.MAX_VALUE} by {@link 
JdbcConfig}.
    *
    * @param millis the timeout in milliseconds (positive)
-   * @return the timeout in whole seconds, at least 1
+   * @return the timeout in whole seconds, from 1 through {@code 
Integer.MAX_VALUE / 1000}
    */
   private static long millisToSeconds(long millis) {
     // (millis - 1) / 1000 + 1 is ceil(millis/1000) for positive millis 
without the overflow that
     // (millis + 999) would hit near Long.MAX_VALUE.
-    return (millis - 1L) / 1000L + 1L;
+    // PostgreSQL converts seconds back to int milliseconds. At the upper 
bound, rounding down
+    // by less than one second keeps the timeout positive instead of 
overflowing or disabling it.
+    return Math.min((millis - 1L) / 1000L + 1L, Integer.MAX_VALUE / 1000L);
   }
 
   /**
diff --git 
a/catalogs/catalog-jdbc-common/src/test/java/org/apache/gravitino/catalog/jdbc/utils/TestJdbcConnectionTimeouts.java
 
b/catalogs/catalog-jdbc-common/src/test/java/org/apache/gravitino/catalog/jdbc/utils/TestJdbcConnectionTimeouts.java
index da3426ff26..ad80de7104 100644
--- 
a/catalogs/catalog-jdbc-common/src/test/java/org/apache/gravitino/catalog/jdbc/utils/TestJdbcConnectionTimeouts.java
+++ 
b/catalogs/catalog-jdbc-common/src/test/java/org/apache/gravitino/catalog/jdbc/utils/TestJdbcConnectionTimeouts.java
@@ -24,6 +24,8 @@ import java.util.Map;
 import org.apache.gravitino.catalog.jdbc.config.JdbcConfig;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
 
 public class TestJdbcConnectionTimeouts {
 
@@ -128,16 +130,29 @@ public class TestJdbcConnectionTimeouts {
     assertPostgresConnectSeconds("1999", "2");
   }
 
-  @Test
-  void testPostgresSecondsConversionAtMaxAllowedValue() {
-    // The config caps the value at Integer.MAX_VALUE ms; the ceiling 
conversion stays correct and
-    // within int range at that bound (and is overflow-safe for the whole 
positive long range).
-    long millis = Integer.MAX_VALUE;
-    long expectedSeconds = (millis - 1L) / 1000L + 1L;
-    assertPostgresConnectSeconds(Long.toString(millis), 
Long.toString(expectedSeconds));
-    Assertions.assertTrue(expectedSeconds > 1L, "conversion must not collapse 
to 1 second");
-    Assertions.assertTrue(
-        expectedSeconds <= Integer.MAX_VALUE, "converted seconds must fit the 
driver int range");
+  @ParameterizedTest
+  @ValueSource(longs = {2147482999L, 2147483000L, 2147483001L, 2147483646L, 
2147483647L})
+  void testPostgresTimeoutsFitDriverMilliseconds(long millis) {
+    Map<String, String> props =
+        Map.of(
+            DRIVER_KEY, "org.postgresql.Driver",
+            URL_KEY, "jdbc:postgresql://host:5432/db",
+            CONNECT_TIMEOUT_MS_KEY, Long.toString(millis));
+    Map<String, String> pooled =
+        JdbcConnectionTimeouts.connectionProperties(config(props), 
props.get(URL_KEY));
+    Map<String, String> probe =
+        JdbcConnectionTimeouts.probeConnectionProperties(config(props), 
props.get(URL_KEY));
+
+    // PostgreSQL multiplies its integer timeout seconds by 1000 before 
setting socket timeouts.
+    // A value that fits an int in seconds can still overflow in that 
conversion.
+    for (String seconds : probe.values()) {
+      int driverMillis = Math.multiplyExact(Integer.parseInt(seconds), 1000);
+      Assertions.assertTrue(driverMillis > 0, "driver timeout must remain 
positive");
+    }
+    Assertions.assertEquals(Map.of(CONNECT_TIMEOUT, "2147483"), pooled);
+    Assertions.assertEquals(
+        Map.of(CONNECT_TIMEOUT, "2147483", SOCKET_TIMEOUT, "2147483", 
"loginTimeout", "2147483"),
+        probe);
   }
 
   private static void assertPostgresConnectSeconds(String millis, String 
expectedSeconds) {
diff --git a/docs/jdbc-catalog-connection-validation.md 
b/docs/jdbc-catalog-connection-validation.md
index f4aa15c3f3..e8e7a7d9d1 100644
--- a/docs/jdbc-catalog-connection-validation.md
+++ b/docs/jdbc-catalog-connection-validation.md
@@ -92,7 +92,9 @@ unit:
 - MySQL Connector/J — also used by the Doris, StarRocks, and OceanBase 
catalogs: `connectTimeout`
   and `socketTimeout` in milliseconds.
 - PostgreSQL — also used by the Hologres catalog: `connectTimeout`, 
`socketTimeout` and
-  `loginTimeout` in seconds (rounded up, minimum 1).
+  `loginTimeout` in seconds (rounded up, minimum 1, maximum `2147483`). The 
upper cap avoids
+  overflow when the driver converts seconds back to integer milliseconds; near 
the millisecond
+  upper bound, it shortens the configured timeout by less than one second.
 - ClickHouse: `connect_timeout` and `socket_timeout` in milliseconds (note the 
underscore names).
 
 It is applied differently to the two kinds of connection:

Reply via email to