Skip to content

Fix: try cancelling statement in all cases before throing an error#1202

Open
zashroof wants to merge 1 commit intodatabricks:mainfrom
zashroof:trycancel-on-statement-failures
Open

Fix: try cancelling statement in all cases before throing an error#1202
zashroof wants to merge 1 commit intodatabricks:mainfrom
zashroof:trycancel-on-statement-failures

Conversation

@zashroof
Copy link

@zashroof zashroof commented Feb 6, 2026

Description

If thread is inturrpted or a statment timesout, an exception is thrown while sometimes the statement continues to execute on the server. Also we see lots of exceptions that cancelling a statement failed (404).

Adding a tryCancelStatement to always try to cancel the statement on the server even if the chances the cancelStatement succeeds are slim. And not throwing an exception if canceling a statement fails.

Testing

Added tests

Additional Notes to the Reviewer

Cursor helped me create this PR. This is my first PR in this repo as well so probably I got a few things wrong.

Copilot AI review requested due to automatic review settings February 6, 2026 22:33
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Ensure server-side operations/statements are cancelled when polling or thread interruption errors occur, without surfacing cancellation failures as additional exceptions.

Changes:

  • Add “best-effort” cancellation helpers (tryCancelOperation / tryCancelStatement) that swallow cancellation failures
  • Invoke cancellation on polling errors and thread interruptions before rethrowing
  • Add tests validating cancellation is attempted when polling fails

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
src/main/java/com/databricks/jdbc/dbclient/impl/thrift/DatabricksThriftAccessor.java Adds tryCancelOperation and uses it on polling errors/interruption during async status polling
src/main/java/com/databricks/jdbc/dbclient/impl/sqlexec/DatabricksSdkClient.java Adds tryCancelStatement and uses it on polling errors/interruption during statement execution polling
src/test/java/com/databricks/jdbc/dbclient/impl/thrift/DatabricksThriftAccessorTest.java Adds test ensuring CancelOperation is invoked when polling throws
src/test/java/com/databricks/jdbc/dbclient/impl/sqlexec/DatabricksSdkClientTest.java Adds test ensuring statement cancel endpoint is called when polling throws

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 254 to 261
try {
Thread.sleep(connectionContext.getAsyncExecPollInterval());
} catch (InterruptedException e) {
// Cancel the statement on the server before throwing - it may still be running
tryCancelStatement(typedStatementId, "thread interruption");
String timeoutErrorMessage =
String.format(
"Thread interrupted due to statement timeout. StatementID %s", statementId);
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The InterruptedException catch block does not restore the thread interrupt flag. Please call Thread.currentThread().interrupt() before proceeding (even if you cancel and throw), so upstream code can observe the interruption consistently.

Copilot uses AI. Check for mistakes.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please fix

"Failed to cancel statement {} after {}: {}",
statementId.toSQLExecStatementId(),
reason,
e.getMessage());
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The warning log drops the exception stack trace by logging only e.getMessage(). Please include the throwable in the log call (e.g., as the last argument) so cancellation failures can be debugged when needed.

Suggested change
e.getMessage());
e.getMessage(),
e);

Copilot uses AI. Check for mistakes.
Comment on lines +195 to +198
"Failed to cancel operation for statement {} after {}: {}",
statementId.toSQLExecStatementId(),
reason,
e.getMessage());
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same logging issue here: only e.getMessage() is logged, which loses the stack trace/context. Please pass the exception into the logger so the full failure cause is captured.

Suggested change
"Failed to cancel operation for statement {} after {}: {}",
statementId.toSQLExecStatementId(),
reason,
e.getMessage());
"Failed to cancel operation for statement {} after {}",
statementId.toSQLExecStatementId(),
reason,
e);

Copilot uses AI. Check for mistakes.

// Mock CancelOperation to succeed
when(thriftClient.CancelOperation(any(TCancelOperationReq.class)))
.thenReturn(new TCancelOperationResp());
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This stub returns an empty TCancelOperationResp. If production code inspects the response status (common in Thrift patterns), an empty response can cause unexpected exceptions and make the test pass for the wrong reason (because tryCancelOperation swallows exceptions). Consider returning a response populated with a SUCCESS status to better reflect a real successful cancellation.

Suggested change
.thenReturn(new TCancelOperationResp());
.thenReturn(
new TCancelOperationResp()
.setStatus(new TStatus().setStatusCode(TStatusCode.SUCCESS_STATUS)));

Copilot uses AI. Check for mistakes.
try {
Thread.sleep(connectionContext.getAsyncExecPollInterval());
} catch (InterruptedException e) {
// Cancel the statement on the server before throwing - it may still be running
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have cancel being fired from checkTimeout above also. We can keep a check that if cancel was already attempted, don't do cancel again.

@@ -320,8 +357,7 @@ private TGetOperationStatusResp pollTillOperationFinished(
TimeUnit.MILLISECONDS.sleep(asyncPollIntervalMillis);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // Restore interrupt flag
Copy link
Collaborator

@gopalldb gopalldb Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

restore interrupt flag -> can we add this to sdkClient handling as well?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants