16
16
17
17
package com .google .cloud .spanner .jdbc ;
18
18
19
+ import com .google .cloud .spanner .ErrorCode ;
19
20
import com .google .cloud .spanner .Options ;
20
21
import com .google .cloud .spanner .Options .QueryOption ;
21
22
import com .google .cloud .spanner .ReadContext .QueryAnalyzeMode ;
34
35
import java .time .Duration ;
35
36
import java .util .Arrays ;
36
37
import java .util .concurrent .TimeUnit ;
38
+ import java .util .concurrent .atomic .AtomicBoolean ;
37
39
import java .util .concurrent .locks .Lock ;
38
40
import java .util .concurrent .locks .ReentrantLock ;
39
41
import java .util .function .Function ;
@@ -47,6 +49,7 @@ abstract class AbstractJdbcStatement extends AbstractJdbcWrapper implements Stat
47
49
final AbstractStatementParser parser ;
48
50
private final Lock executingLock ;
49
51
private volatile Thread executingThread ;
52
+ private final AtomicBoolean cancelled = new AtomicBoolean ();
50
53
private boolean closed ;
51
54
private boolean closeOnCompletion ;
52
55
private boolean poolable ;
@@ -259,10 +262,18 @@ private <T> T doWithStatementTimeout(
259
262
connection .recordClientLibLatencyMetric (executionDuration .toMillis ());
260
263
return result ;
261
264
} 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
+ }
262
272
throw JdbcSqlExceptionFactory .of (spannerException );
263
273
} finally {
264
274
if (this .executingLock != null ) {
265
275
this .executingThread = null ;
276
+ this .cancelled .set (false );
266
277
this .executingLock .unlock ();
267
278
}
268
279
if (shouldResetTimeout .apply (result )) {
@@ -375,7 +386,13 @@ public void cancel() throws SQLException {
375
386
// between the if-check and the actual execution. Just ignore if that happens.
376
387
try {
377
388
this .executingThread .interrupt ();
389
+ this .cancelled .set (true );
378
390
} 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 );
379
396
}
380
397
} else {
381
398
connection .getSpannerConnection ().cancel ();
0 commit comments