Skip to content

Commit 943d649

Browse files
chore: add property for keep-transaction-alive (#3149)
* chore: add property for keep-transaction-alive Adds a property to the Connection API for keeping read/write transactions alive. This can be used in CLI-like applications that might wait a longer period of time for user input. The property is disabled by default, as enabling it can cause read/write transactions to hold on to locks for a longer period of time than intended. Updates GoogleCloudPlatform/pgadapter#1826 * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
1 parent b8e9174 commit 943d649

File tree

16 files changed

+2373
-266
lines changed

16 files changed

+2373
-266
lines changed

google-cloud-spanner/clirr-ignored-differences.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -732,4 +732,16 @@
732732
<className>com/google/cloud/spanner/connection/Connection</className>
733733
<method>void reset()</method>
734734
</difference>
735+
736+
<!-- Added keepTransactionAlive property -->
737+
<difference>
738+
<differenceType>7012</differenceType>
739+
<className>com/google/cloud/spanner/connection/Connection</className>
740+
<method>void setKeepTransactionAlive(boolean)</method>
741+
</difference>
742+
<difference>
743+
<differenceType>7012</differenceType>
744+
<className>com/google/cloud/spanner/connection/Connection</className>
745+
<method>boolean isKeepTransactionAlive()</method>
746+
</difference>
735747
</differences>

google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/Connection.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,25 @@ default boolean isDelayTransactionStartUntilFirstWrite() {
666666
throw new UnsupportedOperationException("Unimplemented");
667667
}
668668

669+
/**
670+
* Sets whether this connection should keep read/write transactions alive by executing a SELECT 1
671+
* once every 10 seconds during inactive read/write transactions.
672+
*
673+
* <p>NOTE: This will keep read/write transactions alive and hold on to locks until it is
674+
* explicitly committed or rolled back.
675+
*/
676+
default void setKeepTransactionAlive(boolean keepTransactionAlive) {
677+
throw new UnsupportedOperationException("Unimplemented");
678+
}
679+
680+
/**
681+
* @return true if this connection keeps read/write transactions alive by executing a SELECT 1
682+
* once every 10 seconds during inactive read/write transactions.
683+
*/
684+
default boolean isKeepTransactionAlive() {
685+
throw new UnsupportedOperationException("Unimplemented");
686+
}
687+
669688
/**
670689
* Commits the current transaction of this connection. All mutations that have been buffered
671690
* during the current transaction will be written to the database.

google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionImpl.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ static UnitOfWorkType of(TransactionMode transactionMode) {
219219
private boolean readOnly;
220220
private boolean returnCommitStats;
221221
private boolean delayTransactionStartUntilFirstWrite;
222+
private boolean keepTransactionAlive;
222223

223224
private UnitOfWork currentUnitOfWork = null;
224225
/**
@@ -438,6 +439,7 @@ public void reset() {
438439
this.ddlInTransactionMode = options.getDdlInTransactionMode();
439440
this.returnCommitStats = options.isReturnCommitStats();
440441
this.delayTransactionStartUntilFirstWrite = options.isDelayTransactionStartUntilFirstWrite();
442+
this.keepTransactionAlive = options.isKeepTransactionAlive();
441443
this.dataBoostEnabled = options.isDataBoostEnabled();
442444
this.autoPartitionMode = options.isAutoPartitionMode();
443445
this.maxPartitions = options.getMaxPartitions();
@@ -987,6 +989,20 @@ public boolean isDelayTransactionStartUntilFirstWrite() {
987989
return this.delayTransactionStartUntilFirstWrite;
988990
}
989991

992+
@Override
993+
public void setKeepTransactionAlive(boolean keepTransactionAlive) {
994+
ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
995+
ConnectionPreconditions.checkState(
996+
!isTransactionStarted(), "Cannot set KeepTransactionAlive while a transaction is active");
997+
this.keepTransactionAlive = keepTransactionAlive;
998+
}
999+
1000+
@Override
1001+
public boolean isKeepTransactionAlive() {
1002+
ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
1003+
return this.keepTransactionAlive;
1004+
}
1005+
9901006
/** Resets this connection to its default transaction options. */
9911007
private void setDefaultTransactionOptions() {
9921008
if (transactionStack.isEmpty()) {
@@ -1908,6 +1924,7 @@ UnitOfWork createNewUnitOfWork(
19081924
.setUseAutoSavepointsForEmulator(options.useAutoSavepointsForEmulator())
19091925
.setDatabaseClient(dbClient)
19101926
.setDelayTransactionStartUntilFirstWrite(delayTransactionStartUntilFirstWrite)
1927+
.setKeepTransactionAlive(keepTransactionAlive)
19111928
.setRetryAbortsInternally(retryAbortsInternally)
19121929
.setSavepointSupport(savepointSupport)
19131930
.setReturnCommitStats(returnCommitStats)

google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionOptions.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ public String[] getValidValues() {
192192
private static final boolean DEFAULT_LENIENT = false;
193193
private static final boolean DEFAULT_ROUTE_TO_LEADER = true;
194194
private static final boolean DEFAULT_DELAY_TRANSACTION_START_UNTIL_FIRST_WRITE = false;
195+
private static final boolean DEFAULT_KEEP_TRANSACTION_ALIVE = false;
195196
private static final boolean DEFAULT_TRACK_SESSION_LEAKS = true;
196197
private static final boolean DEFAULT_TRACK_CONNECTION_LEAKS = true;
197198
private static final boolean DEFAULT_DATA_BOOST_ENABLED = false;
@@ -269,6 +270,8 @@ public String[] getValidValues() {
269270
/** Name of the 'delay transaction start until first write' property. */
270271
public static final String DELAY_TRANSACTION_START_UNTIL_FIRST_WRITE_NAME =
271272
"delayTransactionStartUntilFirstWrite";
273+
/** Name of the 'keep transaction alive' property. */
274+
public static final String KEEP_TRANSACTION_ALIVE_PROPERTY_NAME = "keepTransactionAlive";
272275
/** Name of the 'trackStackTraceOfSessionCheckout' connection property. */
273276
public static final String TRACK_SESSION_LEAKS_PROPERTY_NAME = "trackSessionLeaks";
274277
/** Name of the 'trackStackTraceOfConnectionCreation' connection property. */
@@ -405,6 +408,12 @@ private static String generateGuardedConnectionPropertyError(
405408
+ "the first write operation in a read/write transaction will be executed using the read/write transaction. Enabling this mode can reduce locking "
406409
+ "and improve performance for applications that can handle the lower transaction isolation semantics.",
407410
DEFAULT_DELAY_TRANSACTION_START_UNTIL_FIRST_WRITE),
411+
ConnectionProperty.createBooleanProperty(
412+
KEEP_TRANSACTION_ALIVE_PROPERTY_NAME,
413+
"Enabling this option will trigger the connection to keep read/write transactions alive by executing a SELECT 1 query once every 10 seconds "
414+
+ "if no other statements are being executed. This option should be used with caution, as it can keep transactions alive and hold on to locks "
415+
+ "longer than intended. This option should typically be used for CLI-type application that might wait for user input for a longer period of time.",
416+
DEFAULT_KEEP_TRANSACTION_ALIVE),
408417
ConnectionProperty.createBooleanProperty(
409418
TRACK_SESSION_LEAKS_PROPERTY_NAME,
410419
"Capture the call stack of the thread that checked out a session of the session pool. This will "
@@ -735,6 +744,7 @@ public static Builder newBuilder() {
735744
private final RpcPriority rpcPriority;
736745
private final DdlInTransactionMode ddlInTransactionMode;
737746
private final boolean delayTransactionStartUntilFirstWrite;
747+
private final boolean keepTransactionAlive;
738748
private final boolean trackSessionLeaks;
739749
private final boolean trackConnectionLeaks;
740750

@@ -799,6 +809,7 @@ private ConnectionOptions(Builder builder) {
799809
this.rpcPriority = parseRPCPriority(this.uri);
800810
this.ddlInTransactionMode = parseDdlInTransactionMode(this.uri);
801811
this.delayTransactionStartUntilFirstWrite = parseDelayTransactionStartUntilFirstWrite(this.uri);
812+
this.keepTransactionAlive = parseKeepTransactionAlive(this.uri);
802813
this.trackSessionLeaks = parseTrackSessionLeaks(this.uri);
803814
this.trackConnectionLeaks = parseTrackConnectionLeaks(this.uri);
804815

@@ -1179,6 +1190,12 @@ static boolean parseDelayTransactionStartUntilFirstWrite(String uri) {
11791190
: DEFAULT_DELAY_TRANSACTION_START_UNTIL_FIRST_WRITE;
11801191
}
11811192

1193+
@VisibleForTesting
1194+
static boolean parseKeepTransactionAlive(String uri) {
1195+
String value = parseUriProperty(uri, KEEP_TRANSACTION_ALIVE_PROPERTY_NAME);
1196+
return value != null ? Boolean.parseBoolean(value) : DEFAULT_KEEP_TRANSACTION_ALIVE;
1197+
}
1198+
11821199
@VisibleForTesting
11831200
static boolean parseTrackSessionLeaks(String uri) {
11841201
String value = parseUriProperty(uri, TRACK_SESSION_LEAKS_PROPERTY_NAME);
@@ -1315,6 +1332,14 @@ static List<String> parseProperties(String uri) {
13151332
return res;
13161333
}
13171334

1335+
static long tryParseLong(String value, long defaultValue) {
1336+
try {
1337+
return Long.parseLong(value);
1338+
} catch (NumberFormatException ignore) {
1339+
return defaultValue;
1340+
}
1341+
}
1342+
13181343
/**
13191344
* Create a new {@link Connection} from this {@link ConnectionOptions}. Calling this method
13201345
* multiple times for the same {@link ConnectionOptions} will return multiple instances of {@link
@@ -1551,6 +1576,18 @@ boolean isDelayTransactionStartUntilFirstWrite() {
15511576
return delayTransactionStartUntilFirstWrite;
15521577
}
15531578

1579+
/**
1580+
* Whether connections created by this {@link ConnectionOptions} should keep read/write
1581+
* transactions alive by executing a SELECT 1 once every 10 seconds if no other statements are
1582+
* executed. This option should be used with caution, as enabling it can keep transactions alive
1583+
* for a very long time, which will hold on to any locks that have been taken by the transaction.
1584+
* This option should typically only be enabled for CLI-type applications or other user-input
1585+
* applications that might wait for a longer period of time on user input.
1586+
*/
1587+
boolean isKeepTransactionAlive() {
1588+
return keepTransactionAlive;
1589+
}
1590+
15541591
boolean isTrackConnectionLeaks() {
15551592
return this.trackConnectionLeaks;
15561593
}

google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionStatementExecutor.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ StatementResult statementSetDelayTransactionStartUntilFirstWrite(
9191

9292
StatementResult statementShowDelayTransactionStartUntilFirstWrite();
9393

94+
StatementResult statementSetKeepTransactionAlive(Boolean keepTransactionAlive);
95+
96+
StatementResult statementShowKeepTransactionAlive();
97+
9498
StatementResult statementSetStatementTag(String tag);
9599

96100
StatementResult statementShowStatementTag();

google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionStatementExecutorImpl.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_DELAY_TRANSACTION_START_UNTIL_FIRST_WRITE;
3232
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_DIRECTED_READ;
3333
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_EXCLUDE_TXN_FROM_CHANGE_STREAMS;
34+
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_KEEP_TRANSACTION_ALIVE;
3435
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_MAX_COMMIT_DELAY;
3536
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_MAX_PARTITIONED_PARALLELISM;
3637
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_MAX_PARTITIONS;
@@ -57,6 +58,7 @@
5758
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_DELAY_TRANSACTION_START_UNTIL_FIRST_WRITE;
5859
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_DIRECTED_READ;
5960
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_EXCLUDE_TXN_FROM_CHANGE_STREAMS;
61+
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_KEEP_TRANSACTION_ALIVE;
6062
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_MAX_COMMIT_DELAY;
6163
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_MAX_PARTITIONED_PARALLELISM;
6264
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_MAX_PARTITIONS;
@@ -388,6 +390,20 @@ public StatementResult statementShowDelayTransactionStartUntilFirstWrite() {
388390
SHOW_DELAY_TRANSACTION_START_UNTIL_FIRST_WRITE);
389391
}
390392

393+
@Override
394+
public StatementResult statementSetKeepTransactionAlive(Boolean keepTransactionAlive) {
395+
getConnection().setKeepTransactionAlive(keepTransactionAlive);
396+
return noResult(SET_KEEP_TRANSACTION_ALIVE);
397+
}
398+
399+
@Override
400+
public StatementResult statementShowKeepTransactionAlive() {
401+
return resultSet(
402+
String.format("%sKEEP_TRANSACTION_ALIVE", getNamespace(connection.getDialect())),
403+
getConnection().isKeepTransactionAlive(),
404+
SHOW_KEEP_TRANSACTION_ALIVE);
405+
}
406+
391407
@Override
392408
public StatementResult statementSetStatementTag(String tag) {
393409
getConnection().setStatementTag("".equals(tag) ? null : tag);

0 commit comments

Comments
 (0)