Skip to content

Commit 0205bfd

Browse files
committed
fix: clear interrupted flag after cancel
Clear the interrupted flag after cancelling a statement when using a direct executor. Fixes #1879
1 parent 91f575e commit 0205bfd

File tree

2 files changed

+17
-1
lines changed

2 files changed

+17
-1
lines changed

src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcStatement.java

+17
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.google.cloud.spanner.jdbc;
1818

19+
import com.google.cloud.spanner.ErrorCode;
1920
import com.google.cloud.spanner.Options;
2021
import com.google.cloud.spanner.Options.QueryOption;
2122
import com.google.cloud.spanner.ReadContext.QueryAnalyzeMode;
@@ -34,6 +35,7 @@
3435
import java.time.Duration;
3536
import java.util.Arrays;
3637
import java.util.concurrent.TimeUnit;
38+
import java.util.concurrent.atomic.AtomicBoolean;
3739
import java.util.concurrent.locks.Lock;
3840
import java.util.concurrent.locks.ReentrantLock;
3941
import 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();

src/test/java/com/google/cloud/spanner/jdbc/JdbcStatementTimeoutTest.java

-1
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,6 @@ public void testCancel() throws Exception {
161161
message instanceof ExecuteSqlRequest
162162
&& ((ExecuteSqlRequest) message).getSql().equals(sql),
163163
5000L);
164-
System.out.println("Cancelling statement");
165164
statement.cancel();
166165
return null;
167166
});

0 commit comments

Comments
 (0)