Liquibase and JDBC driver cancellation: should interrupted changeSets clear the interrupt flag or propagate it?
0 reputation · 11 Jun 2020, 01:01 UTC
0 reputation · 11 Jun 2020, 01:01 UTC
I am integrating Liquibase update execution into a service that manages its own worker threads and cancellation lifecycle. The integration boundary in question is between Liquibase's changeSet executor and the underlying JDBC driver: when a long-running changeSet is cancelled via Thread.interrupt, Liquibase attempts Statement.cancel() if the driver supports it, and otherwise lets the thread exit with InterruptedException.
The uncertainty is driver-dependent behavior and interrupt-flag ownership. PostgreSQL implements cancel() via a CancelRequest message, while MySQL Connector/J may ignore it or throw UnsupportedOperationException, so the same cancellation request produces different latency and exception types per driver. Separately, query timeouts set via Statement.setQueryTimeout surface as driver-specific SQLExceptions that Liquibase treats as changeSet failures, but there is no unified Liquibase configuration to choose between interrupt-based cancellation and timeout-based termination. Assume Liquibase 4.x with recent PostgreSQL and MySQL drivers.
My questions:
InterruptedException during a cancelled changeSet, is the thread's interrupted status preserved for the calling framework, or cleared? Is this documented or driver-dependent?35275 reputation · 11 Jun 2020, 05:30 UTC
For Liquibase 4.x, the interrupt flag is not safely preserved across a cancelled changeSet. When Liquibase catches InterruptedException and wraps it as a changeSet failure, the JVM has already cleared the interrupt flag. Unless the specific Liquibase minor version explicitly calls Thread.currentThread().interrupt() before rethrowing—behavior that is undocumented and version-dependent—your calling framework will observe Thread.isInterrupted() == false. Treat flag preservation as your integration boundary’s responsibility, not a driver or Liquibase guarantee.
Confirmed: Throwing InterruptedException clears the flag. Statement.cancel() is driver-specific: PostgreSQL sends a cancel request and aborts the transaction; MySQL Connector/J may ignore it or throw UnsupportedOperationException. Statement.setQueryTimeout is the only standard JDBC timeout hook, but its enforcement and resulting SQLException types vary by driver.
Likely but unconfirmed for your exact artifacts: Liquibase may swallow the interrupt cause when wrapping the failure, and may not re-interrupt. Because this is version-sensitive, verify against your pinned Liquibase 4.x minor and driver majors rather than assuming “recent” means uniform.
There is no supported Liquibase switch that chooses interrupt-based cancellation over timeout-based termination. Statement.setQueryTimeout is the only portable JDBC call, but PostgreSQL maps it to server-side cancellation while MySQL Connector/J may ignore or reject it. A custom Connection wrapper that enforces a client-side timeout is a workaround, not a configuration option. Do not parse driver-specific SQLState or vendor codes as portable control flow.
An interrupt-driven abort does not guarantee a reusable connection. PostgreSQL cancellation leaves the socket open but the transaction in a failed state; MySQL may leave the connection in an unknown state when cancel is unsupported. Discard the connection unless you can confirm the driver honored cancel(), you issued an explicit rollback(), and a trivial validation query succeeds.
liquibase.update() on a thread you own.Statement.cancel() if the driver supports it.try {
liquibase.update("");
} catch (Exception e) {
// Liquibase may have wrapped InterruptedException and cleared the flag
if (isCancellation(e)) {
// rollback or discard the connection here
Thread.currentThread().interrupt();
}
throw e;
}
InterruptedException, wraps it, and whether Thread.isInterrupted() is true after return.Statement.setQueryTimeout in the same test and capture exception class, message, SQLState, and vendor code for PostgreSQL and MySQL.rollback() and a trivial SELECT on the same connection; if either fails or the driver reports closed, configure the pool to discard rather than validate.Statement.cancel attempts and any swallowed InterruptedException; compare with the pinned version source if ambiguous.One missing detail would change the connection-reuse recommendation: whether your pool validates connections on return. If it does, a PostgreSQL connection may be salvaged after rollback; otherwise discard is the safer default.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.