1616
1717package com .google .cloud .spanner .jdbc ;
1818
19+ import com .google .cloud .spanner .ErrorCode ;
1920import com .google .cloud .spanner .Options ;
2021import com .google .cloud .spanner .Options .QueryOption ;
2122import com .google .cloud .spanner .ReadContext .QueryAnalyzeMode ;
3435import java .time .Duration ;
3536import java .util .Arrays ;
3637import java .util .concurrent .TimeUnit ;
38+ import java .util .concurrent .atomic .AtomicBoolean ;
3739import java .util .concurrent .locks .Lock ;
3840import java .util .concurrent .locks .ReentrantLock ;
3941import java .util .function .Function ;
@@ -47,6 +49,7 @@ abstract class AbstractJdbcStatement extends AbstractJdbcWrapper implements Stat
4749 final AbstractStatementParser parser ;
4850 private final Lock executingLock ;
4951 private volatile Thread executingThread ;
52+ private final AtomicBoolean cancelled = new AtomicBoolean ();
5053 private boolean closed ;
5154 private boolean closeOnCompletion ;
5255 private boolean poolable ;
@@ -259,10 +262,18 @@ private <T> T doWithStatementTimeout(
259262 connection .recordClientLibLatencyMetric (executionDuration .toMillis ());
260263 return result ;
261264 } catch (SpannerException spannerException ) {
265+ if (this .cancelled .get ()
266+ && spannerException .getErrorCode () == ErrorCode .CANCELLED
267+ && this .executingLock != null ) {
268+ // Clear the interrupted flag of the thread.
269+ //noinspection ResultOfMethodCallIgnored
270+ Thread .interrupted ();
271+ }
262272 throw JdbcSqlExceptionFactory .of (spannerException );
263273 } finally {
264274 if (this .executingLock != null ) {
265275 this .executingThread = null ;
276+ this .cancelled .set (false );
266277 this .executingLock .unlock ();
267278 }
268279 if (shouldResetTimeout .apply (result )) {
@@ -374,8 +385,14 @@ public void cancel() throws SQLException {
374385 // This is a best-effort operation. It could be that the executing thread is set to null
375386 // between the if-check and the actual execution. Just ignore if that happens.
376387 try {
388+ this .cancelled .set (true );
377389 this .executingThread .interrupt ();
378390 } catch (NullPointerException ignore ) {
391+ // ignore, this just means that the execution finished before we got to the point where we
392+ // could interrupt the thread.
393+ } catch (SecurityException securityException ) {
394+ throw JdbcSqlExceptionFactory .of (
395+ securityException .getMessage (), Code .PERMISSION_DENIED , securityException );
379396 }
380397 } else {
381398 connection .getSpannerConnection ().cancel ();
0 commit comments