Skip to content

Commit

Permalink
chore: add property for keep-transaction-alive (#3149)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
olavloite and gcf-owl-bot[bot] authored Jul 3, 2024
1 parent b8e9174 commit 943d649
Show file tree
Hide file tree
Showing 16 changed files with 2,373 additions and 266 deletions.
12 changes: 12 additions & 0 deletions google-cloud-spanner/clirr-ignored-differences.xml
Original file line number Diff line number Diff line change
Expand Up @@ -732,4 +732,16 @@
<className>com/google/cloud/spanner/connection/Connection</className>
<method>void reset()</method>
</difference>

<!-- Added keepTransactionAlive property -->
<difference>
<differenceType>7012</differenceType>
<className>com/google/cloud/spanner/connection/Connection</className>
<method>void setKeepTransactionAlive(boolean)</method>
</difference>
<difference>
<differenceType>7012</differenceType>
<className>com/google/cloud/spanner/connection/Connection</className>
<method>boolean isKeepTransactionAlive()</method>
</difference>
</differences>
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,25 @@ default boolean isDelayTransactionStartUntilFirstWrite() {
throw new UnsupportedOperationException("Unimplemented");
}

/**
* Sets whether this connection should keep read/write transactions alive by executing a SELECT 1
* once every 10 seconds during inactive read/write transactions.
*
* <p>NOTE: This will keep read/write transactions alive and hold on to locks until it is
* explicitly committed or rolled back.
*/
default void setKeepTransactionAlive(boolean keepTransactionAlive) {
throw new UnsupportedOperationException("Unimplemented");
}

/**
* @return true if this connection keeps read/write transactions alive by executing a SELECT 1
* once every 10 seconds during inactive read/write transactions.
*/
default boolean isKeepTransactionAlive() {
throw new UnsupportedOperationException("Unimplemented");
}

/**
* Commits the current transaction of this connection. All mutations that have been buffered
* during the current transaction will be written to the database.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ static UnitOfWorkType of(TransactionMode transactionMode) {
private boolean readOnly;
private boolean returnCommitStats;
private boolean delayTransactionStartUntilFirstWrite;
private boolean keepTransactionAlive;

private UnitOfWork currentUnitOfWork = null;
/**
Expand Down Expand Up @@ -438,6 +439,7 @@ public void reset() {
this.ddlInTransactionMode = options.getDdlInTransactionMode();
this.returnCommitStats = options.isReturnCommitStats();
this.delayTransactionStartUntilFirstWrite = options.isDelayTransactionStartUntilFirstWrite();
this.keepTransactionAlive = options.isKeepTransactionAlive();
this.dataBoostEnabled = options.isDataBoostEnabled();
this.autoPartitionMode = options.isAutoPartitionMode();
this.maxPartitions = options.getMaxPartitions();
Expand Down Expand Up @@ -987,6 +989,20 @@ public boolean isDelayTransactionStartUntilFirstWrite() {
return this.delayTransactionStartUntilFirstWrite;
}

@Override
public void setKeepTransactionAlive(boolean keepTransactionAlive) {
ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
ConnectionPreconditions.checkState(
!isTransactionStarted(), "Cannot set KeepTransactionAlive while a transaction is active");
this.keepTransactionAlive = keepTransactionAlive;
}

@Override
public boolean isKeepTransactionAlive() {
ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
return this.keepTransactionAlive;
}

/** Resets this connection to its default transaction options. */
private void setDefaultTransactionOptions() {
if (transactionStack.isEmpty()) {
Expand Down Expand Up @@ -1908,6 +1924,7 @@ UnitOfWork createNewUnitOfWork(
.setUseAutoSavepointsForEmulator(options.useAutoSavepointsForEmulator())
.setDatabaseClient(dbClient)
.setDelayTransactionStartUntilFirstWrite(delayTransactionStartUntilFirstWrite)
.setKeepTransactionAlive(keepTransactionAlive)
.setRetryAbortsInternally(retryAbortsInternally)
.setSavepointSupport(savepointSupport)
.setReturnCommitStats(returnCommitStats)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ public String[] getValidValues() {
private static final boolean DEFAULT_LENIENT = false;
private static final boolean DEFAULT_ROUTE_TO_LEADER = true;
private static final boolean DEFAULT_DELAY_TRANSACTION_START_UNTIL_FIRST_WRITE = false;
private static final boolean DEFAULT_KEEP_TRANSACTION_ALIVE = false;
private static final boolean DEFAULT_TRACK_SESSION_LEAKS = true;
private static final boolean DEFAULT_TRACK_CONNECTION_LEAKS = true;
private static final boolean DEFAULT_DATA_BOOST_ENABLED = false;
Expand Down Expand Up @@ -269,6 +270,8 @@ public String[] getValidValues() {
/** Name of the 'delay transaction start until first write' property. */
public static final String DELAY_TRANSACTION_START_UNTIL_FIRST_WRITE_NAME =
"delayTransactionStartUntilFirstWrite";
/** Name of the 'keep transaction alive' property. */
public static final String KEEP_TRANSACTION_ALIVE_PROPERTY_NAME = "keepTransactionAlive";
/** Name of the 'trackStackTraceOfSessionCheckout' connection property. */
public static final String TRACK_SESSION_LEAKS_PROPERTY_NAME = "trackSessionLeaks";
/** Name of the 'trackStackTraceOfConnectionCreation' connection property. */
Expand Down Expand Up @@ -405,6 +408,12 @@ private static String generateGuardedConnectionPropertyError(
+ "the first write operation in a read/write transaction will be executed using the read/write transaction. Enabling this mode can reduce locking "
+ "and improve performance for applications that can handle the lower transaction isolation semantics.",
DEFAULT_DELAY_TRANSACTION_START_UNTIL_FIRST_WRITE),
ConnectionProperty.createBooleanProperty(
KEEP_TRANSACTION_ALIVE_PROPERTY_NAME,
"Enabling this option will trigger the connection to keep read/write transactions alive by executing a SELECT 1 query once every 10 seconds "
+ "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 "
+ "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.",
DEFAULT_KEEP_TRANSACTION_ALIVE),
ConnectionProperty.createBooleanProperty(
TRACK_SESSION_LEAKS_PROPERTY_NAME,
"Capture the call stack of the thread that checked out a session of the session pool. This will "
Expand Down Expand Up @@ -735,6 +744,7 @@ public static Builder newBuilder() {
private final RpcPriority rpcPriority;
private final DdlInTransactionMode ddlInTransactionMode;
private final boolean delayTransactionStartUntilFirstWrite;
private final boolean keepTransactionAlive;
private final boolean trackSessionLeaks;
private final boolean trackConnectionLeaks;

Expand Down Expand Up @@ -799,6 +809,7 @@ private ConnectionOptions(Builder builder) {
this.rpcPriority = parseRPCPriority(this.uri);
this.ddlInTransactionMode = parseDdlInTransactionMode(this.uri);
this.delayTransactionStartUntilFirstWrite = parseDelayTransactionStartUntilFirstWrite(this.uri);
this.keepTransactionAlive = parseKeepTransactionAlive(this.uri);
this.trackSessionLeaks = parseTrackSessionLeaks(this.uri);
this.trackConnectionLeaks = parseTrackConnectionLeaks(this.uri);

Expand Down Expand Up @@ -1179,6 +1190,12 @@ static boolean parseDelayTransactionStartUntilFirstWrite(String uri) {
: DEFAULT_DELAY_TRANSACTION_START_UNTIL_FIRST_WRITE;
}

@VisibleForTesting
static boolean parseKeepTransactionAlive(String uri) {
String value = parseUriProperty(uri, KEEP_TRANSACTION_ALIVE_PROPERTY_NAME);
return value != null ? Boolean.parseBoolean(value) : DEFAULT_KEEP_TRANSACTION_ALIVE;
}

@VisibleForTesting
static boolean parseTrackSessionLeaks(String uri) {
String value = parseUriProperty(uri, TRACK_SESSION_LEAKS_PROPERTY_NAME);
Expand Down Expand Up @@ -1315,6 +1332,14 @@ static List<String> parseProperties(String uri) {
return res;
}

static long tryParseLong(String value, long defaultValue) {
try {
return Long.parseLong(value);
} catch (NumberFormatException ignore) {
return defaultValue;
}
}

/**
* Create a new {@link Connection} from this {@link ConnectionOptions}. Calling this method
* multiple times for the same {@link ConnectionOptions} will return multiple instances of {@link
Expand Down Expand Up @@ -1551,6 +1576,18 @@ boolean isDelayTransactionStartUntilFirstWrite() {
return delayTransactionStartUntilFirstWrite;
}

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

boolean isTrackConnectionLeaks() {
return this.trackConnectionLeaks;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ StatementResult statementSetDelayTransactionStartUntilFirstWrite(

StatementResult statementShowDelayTransactionStartUntilFirstWrite();

StatementResult statementSetKeepTransactionAlive(Boolean keepTransactionAlive);

StatementResult statementShowKeepTransactionAlive();

StatementResult statementSetStatementTag(String tag);

StatementResult statementShowStatementTag();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_DELAY_TRANSACTION_START_UNTIL_FIRST_WRITE;
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_DIRECTED_READ;
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_EXCLUDE_TXN_FROM_CHANGE_STREAMS;
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_KEEP_TRANSACTION_ALIVE;
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_MAX_COMMIT_DELAY;
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_MAX_PARTITIONED_PARALLELISM;
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_MAX_PARTITIONS;
Expand All @@ -57,6 +58,7 @@
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_DELAY_TRANSACTION_START_UNTIL_FIRST_WRITE;
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_DIRECTED_READ;
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_EXCLUDE_TXN_FROM_CHANGE_STREAMS;
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_KEEP_TRANSACTION_ALIVE;
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_MAX_COMMIT_DELAY;
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_MAX_PARTITIONED_PARALLELISM;
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_MAX_PARTITIONS;
Expand Down Expand Up @@ -388,6 +390,20 @@ public StatementResult statementShowDelayTransactionStartUntilFirstWrite() {
SHOW_DELAY_TRANSACTION_START_UNTIL_FIRST_WRITE);
}

@Override
public StatementResult statementSetKeepTransactionAlive(Boolean keepTransactionAlive) {
getConnection().setKeepTransactionAlive(keepTransactionAlive);
return noResult(SET_KEEP_TRANSACTION_ALIVE);
}

@Override
public StatementResult statementShowKeepTransactionAlive() {
return resultSet(
String.format("%sKEEP_TRANSACTION_ALIVE", getNamespace(connection.getDialect())),
getConnection().isKeepTransactionAlive(),
SHOW_KEEP_TRANSACTION_ALIVE);
}

@Override
public StatementResult statementSetStatementTag(String tag) {
getConnection().setStatementTag("".equals(tag) ? null : tag);
Expand Down
Loading

0 comments on commit 943d649

Please sign in to comment.