From fbf2597ebbed36e57cf4cfbfd0987c842486a12b Mon Sep 17 00:00:00 2001 From: HwangRock Date: Sat, 11 Jul 2026 16:37:21 +0900 Subject: [PATCH] [ZEPPELIN-6481] Restore interrupt status after InterruptedException in JDBC completion loading The catch (InterruptedException e) block around awaitTermination(...) in createOrUpdateSqlCompleter(...) logged a warning and closed the connection but never restored the interrupt status. Catching InterruptedException clears the thread's interrupt flag, so the interrupt was swallowed and callers on the completion request path could no longer detect it, silently dropping cooperative cancellation and shutdown signals. Restore the interrupt status with Thread.currentThread().interrupt() as the last statement of the catch block, after the connection-close attempt. Add a unit test that pre-interrupts the calling thread, drives the completer through the catch path, and asserts the interrupt flag is restored on return. --- .../apache/zeppelin/jdbc/JDBCInterpreter.java | 1 + .../zeppelin/jdbc/JDBCInterpreterTest.java | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/jdbc/src/main/java/org/apache/zeppelin/jdbc/JDBCInterpreter.java b/jdbc/src/main/java/org/apache/zeppelin/jdbc/JDBCInterpreter.java index 196747395b2..90c34614b5f 100644 --- a/jdbc/src/main/java/org/apache/zeppelin/jdbc/JDBCInterpreter.java +++ b/jdbc/src/main/java/org/apache/zeppelin/jdbc/JDBCInterpreter.java @@ -328,6 +328,7 @@ public void run() { LOGGER.warn("Error close connection", e1); } } + Thread.currentThread().interrupt(); } return completer; } diff --git a/jdbc/src/test/java/org/apache/zeppelin/jdbc/JDBCInterpreterTest.java b/jdbc/src/test/java/org/apache/zeppelin/jdbc/JDBCInterpreterTest.java index 2fe6dc3aab8..cc5002b22a8 100644 --- a/jdbc/src/test/java/org/apache/zeppelin/jdbc/JDBCInterpreterTest.java +++ b/jdbc/src/test/java/org/apache/zeppelin/jdbc/JDBCInterpreterTest.java @@ -34,6 +34,7 @@ import org.junit.jupiter.api.Test; import java.io.IOException; +import java.lang.reflect.Method; import java.nio.file.Files; import java.nio.file.Path; import java.sql.Connection; @@ -52,6 +53,7 @@ import static java.lang.String.format; import static org.apache.zeppelin.jdbc.JDBCInterpreter.COMMON_MAX_LINE; import static org.apache.zeppelin.jdbc.JDBCInterpreter.DEFAULT_DRIVER; +import static org.apache.zeppelin.jdbc.JDBCInterpreter.DEFAULT_KEY; import static org.apache.zeppelin.jdbc.JDBCInterpreter.DEFAULT_PASSWORD; import static org.apache.zeppelin.jdbc.JDBCInterpreter.DEFAULT_PRECODE; import static org.apache.zeppelin.jdbc.JDBCInterpreter.DEFAULT_STATEMENT_PRECODE; @@ -510,6 +512,35 @@ void testAutoCompletion() throws IOException, InterpreterException { assertEquals(true, completionList.contains(correctCompletionKeyword)); } + @Test + void testCreateOrUpdateSqlCompleterRestoresInterruptStatusOnTimeout() throws Exception { + Properties properties = new Properties(); + properties.setProperty("common.max_count", "1000"); + properties.setProperty("common.max_retry", "3"); + properties.setProperty("default.driver", "org.h2.Driver"); + properties.setProperty("default.url", getJdbcConnection()); + properties.setProperty("default.user", ""); + properties.setProperty("default.password", ""); + JDBCInterpreter jdbcInterpreter = new JDBCInterpreter(properties); + + // createOrUpdateSqlCompleter is private, so invoke it via reflection. + Method createOrUpdateSqlCompleter = JDBCInterpreter.class.getDeclaredMethod( + "createOrUpdateSqlCompleter", SqlCompleter.class, Connection.class, String.class, + String.class, int.class); + createOrUpdateSqlCompleter.setAccessible(true); + + // Entering already interrupted makes awaitTermination throw InterruptedException + // immediately, deterministically hitting the catch block under test. + Thread.currentThread().interrupt(); + try { + createOrUpdateSqlCompleter.invoke(jdbcInterpreter, null, null, DEFAULT_KEY, "sel", 3); + assertTrue(Thread.currentThread().isInterrupted()); + } finally { + // Clear the interrupt flag so it doesn't leak into subsequent tests. + Thread.interrupted(); + } + } + private Properties getDBProperty(String dbPrefix, String dbUser, String dbPassowrd) throws IOException {