HIVE-28265: Fix JDBC timeout message for hive.query.timeout.seconds#6412
HIVE-28265: Fix JDBC timeout message for hive.query.timeout.seconds#6412ashniku wants to merge 21 commits into
Conversation
| fail("Expecting SQLTimeoutException"); | ||
| } catch (SQLTimeoutException e) { | ||
| assertNotNull(e); | ||
| assertTrue("Message should reflect JDBC query timeout (1s): " + e.getMessage(), |
There was a problem hiding this comment.
Can you show us an example about the whole output that demonstrates the change?
I wonder if it is possible to get any number other than the timeout in the message. Like a timestamp or maybe a query id, host name, etc. Asserting to a single number in a string looks a little bit fragile to me.
There was a problem hiding this comment.
Introduced constant QUERY_TIMED_OUT_AFTER_1_SECONDS = "Query timed out after 1 seconds" with Javadoc that this is the full message from HS2 / client (no query id, host, timestamp in that string for these paths).
testQueryTimeout now uses assertEquals, expected value = that constant, with a failure message that repeats the example text.
| * {@code N == 1} with flexible whitespace so we do not treat {@code 10} or unrelated digits as {@code 1}. | ||
| */ | ||
| private static boolean isQueryTimedOutAfterOneSecondMessage(String msg) { | ||
| return msg != null && msg.matches("(?is).*timed out after\\s+1\\s+seconds.*"); |
There was a problem hiding this comment.
We know it is 1 sec. And we don't accept any other output in that case.
In my opinion, regex here can be a little bit overkill.
What about something like:
final String expectedMessage = "Query timed out after 1 seconds";
assertEquals("Message should reflect JDBC query timeout", expectedMesage, message);
There was a problem hiding this comment.
Removed isQueryTimedOutAfterOneSecondMessage (regex helper).
Positive assertions use assertEquals("…", QUERY_TIMED_OUT_AFTER_1_SECONDS, e.getMessage()) in both timeout-related tests.
| assertNotNull(e); | ||
| assertTrue("Message should reflect JDBC query timeout (1s): " + e.getMessage(), | ||
| isQueryTimedOutAfterOneSecondMessage(e.getMessage())); | ||
| assertFalse("Message should not claim 0 seconds: " + e.getMessage(), |
There was a problem hiding this comment.
Considering the previous assertion, is that assertion possible at all?
There was a problem hiding this comment.
(assertFalse(… "after 0 seconds") after the positive check in testQueryTimeout — “is that even possible?”)
Changes
Removed assertFalse(..., e.getMessage().contains("after 0 seconds")) from testQueryTimeout.
| + " t2 on t1.under_col = t2.under_col"); | ||
| fail("Expecting SQLTimeoutException"); | ||
| } catch (SQLTimeoutException e) { | ||
| assertNotNull(e); |
There was a problem hiding this comment.
Is it possible having an exception with null value in a catch block?
There was a problem hiding this comment.
(assertNotNull(e) in catch (SQLTimeoutException e) — can e be null?)
Changes
Removed assertNotNull(e) from both SQLTimeoutException catch blocks in these tests.
| assertNotNull(e); | ||
| assertTrue("Message should include session timeout (1s): " + e.getMessage(), | ||
| isQueryTimedOutAfterOneSecondMessage(e.getMessage())); | ||
| assertFalse("Message should not claim 0 seconds (HIVE-28265): " + e.getMessage(), |
There was a problem hiding this comment.
What is the benefits of putting the ticket number into assertions or comments?
There was a problem hiding this comment.
Dropped ticket id from assertion messages.
Kept HIVE-28265 and expected message behavior in testQueryTimeoutMessageUsesHiveConf Javadoc (and the constant / assertEquals text describes behavior without the ticket).
| assertNotNull(e); | ||
| assertTrue("Message should include session timeout (1s): " + e.getMessage(), | ||
| isQueryTimedOutAfterOneSecondMessage(e.getMessage())); | ||
| assertFalse("Message should not claim 0 seconds (HIVE-28265): " + e.getMessage(), |
There was a problem hiding this comment.
Considering the previous assertion, is that assertion possible at all?
There was a problem hiding this comment.
Removed assertFalse(..., "after 0 seconds") from testQueryTimeoutMessageUsesHiveConf.
| * Sentinel: no {@code SET hive.query.timeout.seconds} has been observed on this connection yet. | ||
| */ | ||
| static final long SESSION_QUERY_TIMEOUT_NOT_TRACKED = -1L; | ||
| private final AtomicLong sessionQueryTimeoutSeconds = new AtomicLong(SESSION_QUERY_TIMEOUT_NOT_TRACKED); |
There was a problem hiding this comment.
Thinking out loud: I wonder if a connection can have concurrency issue: I mean, you can have multiple individual connections to Hive, but inside a connection itself, can we have multiple hive statements in parallel?
I have no such use case in my mind, but let me ping Ayush about this question.
@ayushtkn , what do you think?
There was a problem hiding this comment.
a single JDBC Connection can be shared across multiple threads, and it is entirely possible to have multiple HiveStatement objects executing concurrently on the same connection (which maps to a single session on the HS2 side).
via Beeline or so maybe not but In Hive Server 2 (HS2), a single JDBC Connection corresponds to a single HS2 Session. You can absolutely execute multiple queries concurrently within the same session by spawning multiple threads on the client side, each using a different HiveStatement created from that single HiveConnection.
| * Records the effective {@code hive.query.timeout.seconds} (in seconds) after a successful | ||
| * {@code SET hive.query.timeout.seconds=...} on this connection. Used for JDBC timeout messages. | ||
| */ | ||
| void recordSessionQueryTimeoutFromSet(long seconds) { |
There was a problem hiding this comment.
Can we keep the getter..setter naming pattern?
There was a problem hiding this comment.
recordSessionQueryTimeoutFromSet(long) → setSessionQueryTimeoutSeconds(long)
getSessionQueryTimeoutSecondsTracked() → getSessionQueryTimeoutSeconds()
HiveStatement updated to call the new names.
ashniku
left a comment
There was a problem hiding this comment.
@InvisibleProgrammer can you please check
| context + ": should start with " + QUERY_TIMED_OUT_AFTER_1_SECONDS | ||
| + " (HS2 may append ; Query ID: ...); actual=" + msg, | ||
| msg.startsWith(QUERY_TIMED_OUT_AFTER_1_SECONDS)); | ||
| assertFalse( |
There was a problem hiding this comment.
You are absolutely right. The assertFalse(...contains("after 0 seconds")) is logically redundant: if msg.startsWith("Query timed out after 1 seconds") is true, it is impossible for it to also contain "after 0 seconds". The assertFalse can never add new information. I'll remove it — the assertTrue(startsWith(...)) alone is sufficient.
| public void testURLWithFetchSize() throws SQLException { | ||
| Connection con = getConnection(testDbName + ";fetchSize=1234", ""); | ||
| Statement stmt = con.createStatement(); | ||
| Connection connectionWithFetchSize = getConnection(testDbName + ";fetchSize=1234", ""); |
There was a problem hiding this comment.
The only change to testURLWithFetchSize was a rename: the local variable con was renamed to connectionWithFetchSize to avoid a Sonar HiddenField warning, because the method-level con shadowed the class-level static Connection con. No logic was changed.
| * in the URL query ({@code ?hive_conf_list}) per the driver format | ||
| * {@code jdbc:hive2://.../db;sess?hive_conf#hive_var}. | ||
| * <p> | ||
| * HIVE-28265: {@link SQLTimeoutException#getMessage()} must reflect the configured limit (1s), |
| * Sentinel: no {@code hive.query.timeout.seconds} has been applied from the JDBC URL or a client | ||
| * {@code SET} on this connection yet. | ||
| */ | ||
| static final long SESSION_QUERY_TIMEOUT_NOT_TRACKED = -1L; |
There was a problem hiding this comment.
Fair point. The constant is only used as the initial value of sessionQueryTimeoutSeconds and in getSessionQueryTimeoutSeconds() Javadoc. I can inline -1L and drop the constant entirely to simplify.
| } | ||
|
|
||
| /** | ||
| * If the JDBC URL supplied {@code hive.query.timeout.seconds} (query string / {@code hiveconf:} map), |
There was a problem hiding this comment.
That phrasing leaks internal PR history into source code and would be confusing to future readers. I'll rewrite the Javadoc to explain only the what and why, without referencing the historical alternative that was rejected.
| } | ||
|
|
||
| /** | ||
| * HIVE-28265: Prefer server error text (from {@code TGetOperationStatusResp.errorMessage}) unless |
There was a problem hiding this comment.
Agreed, I'll remove the HIVE-28265: prefix from that Javadoc as well.
|
|
||
| /** | ||
| * One GetOperationStatus response: progress update, Thrift status check, then terminal states. | ||
| * Extracted to keep {@link #waitForOperationToComplete()} smaller for static analysis (Sonar). |
There was a problem hiding this comment.
Correct. The comment should describe the purpose of the method, not the tooling reason it was extracted. I'll rewrite it to simply describe what the method does.
| stmt1.close(); | ||
|
|
||
| Statement stmt = con.createStatement(); | ||
| stmt.execute("set hive.query.timeout.seconds=1s"); |
There was a problem hiding this comment.
You are right. The name says "UsesHiveConf" — which implies the timeout is configured through a HiveConf object — but the implementation uses stmt.execute("set hive.query.timeout.seconds=1s"), a SQL SET statement, which is the same mechanism as testQueryTimeoutMessagePersistedAcrossStatements. The name is misleading.
What the test was actually trying to capture: The intended distinction from testQueryTimeout (which uses the JDBC standard stmt.setQueryTimeout(1)) is: timeout set via a session-level Hive config SET, with no setQueryTimeout() call on the statement. "HiveConf" was used loosely to mean "Hive session configuration" rather than "the JDBC timeout API".
The real distinction between the two SET-based tests is the statement lifecycle:
testQueryTimeoutMessageUsesHiveConf: SET and executeQuery run on the same open statement.
testQueryTimeoutMessagePersistedAcrossStatements: SET on stmt2 which is then closed; executeQuery on a brand-new stmt.
Suggestion: Rename testQueryTimeoutMessageUsesHiveConf to something like testQueryTimeoutMessageFromSessionSet (or testQueryTimeoutFromSetStatement) and update its Javadoc to describe the actual scenario — session timeout set via a SET command on the same statement, no setQueryTimeout() call — without the misleading "HiveConf" label. I'm happy to do that rename if you agree.
|
|
||
| // set progress bar to be completed when hive query execution has completed | ||
| if (inPlaceUpdateStream.isPresent()) { | ||
| if (progressUpdates) { |
There was a problem hiding this comment.
@InvisibleProgrammer I will be waiting for your reply on this.
| * without regex-parsing {@code SET} statements. Does not change HS2 behavior (already applied in | ||
| * {@link #openSession()}). | ||
| */ | ||
| private void applySessionQueryTimeoutFromJdbcUrl() { |
There was a problem hiding this comment.
@InvisibleProgrammer I will be waiting for your reply on this.
| case CANCELED_STATE: | ||
| throw sqlExceptionForCanceledState(statusResp); | ||
| case TIMEDOUT_STATE: | ||
| throw new SQLTimeoutException(sqlTimeoutMessageForTimedOutState(statusResp.getErrorMessage())); |
There was a problem hiding this comment.
If I interpreted it correctly, it consumes the response text, checks if we got the same text as it set in SQLOperation, and if yes, it tries to extract the timeout value from the string itself. It looks a little bit overkill. Does the code execution receives the SQL State set at SQLOperation? If yes, deciding if we get the timeout from that is way more easier.
I wonder, what is the current error message and error code on master?
| } | ||
|
|
||
| private SQLException sqlExceptionForCanceledState(TGetOperationStatusResp statusResp) { | ||
| final String errMsg = statusResp.getErrorMessage(); |
There was a problem hiding this comment.
I think that comment was actually useful:
// 01000 -> warning
InvisibleProgrammer
left a comment
There was a problem hiding this comment.
On overall, it looks good. I have only some really minor suggestions left.
Approved, non-binding. Let's hope if it gets the attention of a committer to do a binding review.
Thank you for picking up this issue and providing a fix.
|
ashniku
left a comment
There was a problem hiding this comment.
@InvisibleProgrammer @ayushtkn
I have made the changes.
Use HYT00 SQL state to select timeout message; restore 01000 comment
- sqlTimeoutMessageForTimedOutState: check statusResp.getSqlState() == "HYT00"
instead of inspecting message text; remove needsLocalTimeoutMessageForTimedOut - sqlExceptionForCanceledState: restore // SQLSTATE 01000 = warning comment
could you please review?
|
can someone please view the pull request |
There was a problem hiding this comment.
Pull request overview
This PR fixes misleading JDBC/Beeline timeout messages (“Query timed out after 0 seconds”) by making HiveServer2 populate a proper timeout exception message and updating the JDBC driver to prefer server-provided timeout text with local fallbacks, plus adding/adjusting integration tests to assert the message content.
Changes:
- HiveServer2: sets a
HiveSQLExceptionwith SQLStateHYT00before cancelling an operation asTIMEDOUT, and avoids overwriting the timeout exception from async background threads. - JDBC: refactors operation-status polling and constructs
SQLTimeoutExceptionmessages using server error text when available, otherwise deriving a message from statement/query timeout information. - Tests: adds new
TestJdbcDriver2cases and cleanup to validate timeout messages for URL/session/JDBC-timeout paths.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| service/src/java/org/apache/hive/service/cli/operation/SQLOperation.java | Sets a server-side timeout exception message and avoids overwriting it from async execution paths. |
| jdbc/src/java/org/apache/hive/jdbc/HiveStatement.java | Refactors status handling and improves JDBC-side timeout exception message selection/fallback. |
| jdbc/src/java/org/apache/hive/jdbc/HiveConnection.java | Tracks a session timeout value seeded from the JDBC URL and refactors retry-interval parsing. |
| itests/hive-unit/src/test/java/org/apache/hive/jdbc/TestJdbcDriver2.java | Adds/updates tests asserting that timeout messages reflect the configured timeout (not 0). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (queryTimeout > 0L) { | ||
| timeoutExecutor = Executors.newSingleThreadScheduledExecutor(); | ||
| timeoutExecutor.schedule(() -> { | ||
| try { | ||
| final String queryId = queryState.getQueryId(); |
| private String sqlTimeoutMessageForTimedOutState(String serverMessage, String sqlState) { | ||
| if ("HYT00".equals(sqlState) && StringUtils.isNotBlank(serverMessage)) { | ||
| return serverMessage; | ||
| } | ||
| long effectiveSec = resolveEffectiveTimeoutSecondsForMessage(); | ||
| if (effectiveSec > 0) { | ||
| return "Query timed out after " + effectiveSec + " seconds"; | ||
| } | ||
| return "Query timed out"; | ||
| } |
| private long resolveEffectiveTimeoutSecondsForMessage() { | ||
| if (queryTimeout > 0) { | ||
| return queryTimeout; | ||
| } | ||
| long tracked = connection.getSessionQueryTimeoutSeconds(); | ||
| if (tracked > 0) { | ||
| return tracked; | ||
| } | ||
| return 0L; |
| try { | ||
| HiveConf conf = new HiveConf(); | ||
| conf.set(ConfVars.HIVE_QUERY_TIMEOUT_SECONDS.varname, raw.trim()); | ||
| long sec = HiveConf.getTimeVar(conf, ConfVars.HIVE_QUERY_TIMEOUT_SECONDS, TimeUnit.SECONDS); | ||
| if (sec > 0) { | ||
| setSessionQueryTimeoutSeconds(sec); | ||
| } | ||
| } catch (Exception e) { | ||
| LOG.debug("Could not parse {} from JDBC URL: {}", ConfVars.HIVE_QUERY_TIMEOUT_SECONDS.varname, raw, e); | ||
| } |
Use server-side HiveSQLException with effective timeout seconds before cancel(TIMEDOUT) so GetOperationStatus exposes correct errorMessage. JDBC: prefer server message; ignore bogus 'after 0 seconds'; fall back to Statement queryTimeout or last SET hive.query.timeout.seconds tracked on HiveConnection. Parse SET assignments anywhere in SQL (last wins). SQLOperation async: do not overwrite operationException when already TIMEDOUT. Tests: TestJdbcDriver2 (session SET + sleep UDF; tighten testQueryTimeout). Made-with: Cursor
…meout in tests - Extract TIMEDOUT/CANCELED handling into helpers to satisfy Sonar (complexity, nested blocks, nested ternary). - Add @after in TestJdbcDriver2 to run SET hive.query.timeout.seconds=0s on the shared connection so testQueryTimeoutMessageUsesHiveConf does not leave a 1s server-side limit for other tests (fixes Jenkins SQLTimeoutException cascades). Made-with: Cursor
Use a regex for 'timed out after 1 seconds' instead of contains("1") to
avoid false positives (e.g. 10-second timeouts).
Made-with: Cursor
- TestJdbcDriver2: assert exact 'Query timed out after 1 seconds'; drop redundant assertNotNull/assertFalse; keep HIVE-28265 context in Javadoc not assert text - HiveConnection: AtomicLong Javadoc for concurrent statements; rename to setSessionQueryTimeoutSeconds / getSessionQueryTimeoutSeconds - HiveStatement: update call sites Made-with: Cursor
HiveSQLException appends '; Query ID: ...' to getMessage(). When the client passes through the server timeout text (non-broken path), SQLTimeoutException included that suffix and TestJdbcDriver2 exact assertions failed on CI. Strip the same trailer the server uses before returning the message. Made-with: Cursor
… ID suffix - HiveStatement: stop stripping '; Query ID;' from server timeout text; pass through when server message is usable (reverts strip-only follow-up). - TestJdbcDriver2: assert message starts with expected timeout text and does not contain 'after 0 seconds' (HIVE-28265), allowing HS2/HiveSQLException suffix. Made-with: Cursor
- sqlExceptionForCanceledState: replace ternary with if/else (S3358). - Extract processOperationStatusResponse from waitForOperationToComplete (S6541). - Use local progressUpdates flag to simplify nested flow (AvoidNestedBlocks). Does not address hundreds of repo-wide Sonar findings outside this file. Made-with: Cursor
…ests - Rename AtomicInteger counter to SKIPPED_ATTEMPTS (ConstantNameCheck). - Drop unused partitionDiscoveryEnabled again (revert commit restores history). - testQueryProgress: accept ELAPSED TIME, Beeline row timing, or Driver Time taken. - llap_io_cache: use 8MiB RPAD payload to avoid Parquet logging OOM on CI. Made-with: Cursor
Restore Beeline/llap_io_cache Q test, PartitionManagementTask, and TestPartitionManagement to upstream master. Core fix remains: SQLOperation, HiveStatement, HiveConnection, TestJdbcDriver2. Made-with: Cursor
testURLWithHiveQueryTimeoutSeconds sets hive.query.timeout.seconds via the URL query string (getConnection postfix), matching the driver doc for db;sess?hive_conf. Asserts timeout message shows 1s (HIVE-28265). Made-with: Cursor
…er2.con Sonar: local name 'con' shadowed static field 'con' (HiddenField). Made-with: Cursor
Parse hive.query.timeout.seconds from connParams.getHiveConfs() at connect time using HiveConf.getTimeVar (same semantics as HiveStatement SET path) so JDBC timeout messages work when the timeout is set via URL only. Made-with: Cursor
…ements testQueryTimeoutMessagePersistedAcrossStatements verifies that when SET hive.query.timeout.seconds is issued on a separate closed statement, the tracked value on HiveConnection still drives the SQLTimeoutException message on a subsequent new statement (no setQueryTimeout call). Made-with: Cursor
…ctor Extract retry-config parsing from HiveConnection(uri,info,...,initSession) into readRetryIntervalMillis() so the constructor fits within Sonar's 150-line limit (was 151, now 142). Made-with: Cursor
Made-with: Cursor
The reviewer (InvisibleProgrammer) correctly pointed out that scanning every executed SQL string with a regex to detect SET hive.query.timeout.seconds is the wrong approach. The right source is TGetOperationStatusResp.errorMessage: SQLOperation already sets "Query timed out after N seconds" with the real value before cancel(TIMEDOUT), so the client-side regex fallback is unnecessary. Remove: - Pattern SET_HIVE_QUERY_TIMEOUT_SECONDS field - trackSessionQueryTimeoutIfSet(sql) method and its call site - Unused imports (HiveConf, TimeUnit, Matcher, Pattern) The timeout message resolution is now: 1. Server errorMessage from TGetOperationStatusResp (primary - correct approach) 2. Statement-level setQueryTimeout() (JDBC standard) 3. URL-seeded hive.query.timeout.seconds from applySessionQueryTimeoutFromJdbcUrl() All three paths avoid per-statement SQL parsing. Made-with: Cursor
Made-with: Cursor
- Remove SESSION_QUERY_TIMEOUT_NOT_TRACKED constant; inline -1L - Rewrite Javadocs on setSessionQueryTimeoutSeconds, applySessionQueryTimeoutFromJdbcUrl, sqlTimeoutMessageForTimedOutState, and processOperationStatusResponse to drop ticket numbers and implementation-history notes - Rename testQueryTimeoutMessageUsesHiveConf to testQueryTimeoutFromSetStatement; update its Javadoc and cross-refs - Remove redundant assertFalse from assertTimeoutMessageShowsOneSecond
…000 comment - sqlTimeoutMessageForTimedOutState: check statusResp.getSqlState() == "HYT00" instead of inspecting message text; remove needsLocalTimeoutMessageForTimedOut - sqlExceptionForCanceledState: restore // SQLSTATE 01000 = warning comment
…veConf) Replace new HiveConf() + getTimeVar with HiveConf.toTime(raw, SECONDS, SECONDS) in applySessionQueryTimeoutFromJdbcUrl. This parses the duration string directly without loading Hadoop/Hive configuration resources at connect time.
|



What changes were proposed in this pull request?
hive.query.timeout.seconds is enforced correctly, but Beeline/JDBC reported Query timed out after 0 seconds when Statement.setQueryTimeout was not used.
This PR:
SQLOperation (HiveServer2)
Before cancel(OperationState.TIMEDOUT), set a HiveSQLException whose message is Query timed out after seconds, using the effective operation timeout ( is the same value used to schedule the cancel). GetOperationStatus then exposes the right text via operationException.
For async execution, do not call setOperationException from the background thread if the operation is already TIMEDOUT, so the timeout message is not overwritten.
HiveStatement (JDBC)
On TIMEDOUT_STATE, prefer the server errorMessage. If it is missing or clearly wrong (contains after 0 seconds), build the client message from Statement query timeout or from the last SET hive.query.timeout.seconds=... value tracked on HiveConnection. SET is detected with a regex find() so assignments can appear inside a longer script (last match wins).
HiveConnection
Stores the last parsed hive.query.timeout.seconds from a successful SET for use in the timeout message when needed.
Tests
==> testQueryTimeoutMessageUsesHiveConf: session SET hive.query.timeout.seconds=1s, no setQueryTimeout, slow query via existing SleepMsUDF — expects SQLTimeoutException and message not claiming after 0 seconds, and containing 1.
==> testQueryTimeout: same checks for the existing setQueryTimeout(1) path.
-->
Why are the changes needed?
Does this PR introduce any user-facing change?
How was this patch tested?