diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ee28d7f8a66..b2b187f4ac5 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -54,6 +54,7 @@ jobs: JOB_TYPE: test GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS: true GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS_PARTITIONED_OPS: true + GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS_FOR_RW: true units-java8: # Building using Java 17 and run the tests with Java 8 runtime name: "units (8)" @@ -94,6 +95,7 @@ jobs: JOB_TYPE: test GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS: true GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS_PARTITIONED_OPS: true + GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS_FOR_RW: true windows: runs-on: windows-latest steps: diff --git a/.kokoro/presubmit/integration-multiplexed-sessions-enabled.cfg b/.kokoro/presubmit/integration-multiplexed-sessions-enabled.cfg index 49edd2e8df6..800e2a21558 100644 --- a/.kokoro/presubmit/integration-multiplexed-sessions-enabled.cfg +++ b/.kokoro/presubmit/integration-multiplexed-sessions-enabled.cfg @@ -41,3 +41,8 @@ env_vars: { key: "GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS_PARTITIONED_OPS" value: "true" } + +env_vars: { + key: "GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS_FOR_RW" + value: "true" +} diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncRunnerTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncRunnerTest.java index d659e149282..72e19e0291f 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncRunnerTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncRunnerTest.java @@ -201,7 +201,17 @@ public void asyncRunnerUpdateAbortedWithoutGettingResult() throws Exception { executor); assertThat(result.get()).isNull(); assertThat(attempt.get()).isEqualTo(2); - if (isMultiplexedSessionsEnabled()) { + if (isMultiplexedSessionsEnabledForRW()) { + assertThat(mockSpanner.getRequestTypes()) + .containsExactly( + CreateSessionRequest.class, + ExecuteSqlRequest.class, + // The retry will use an explicit BeginTransaction RPC because the first statement of + // the transaction did not return a transaction id during the initial attempt. + BeginTransactionRequest.class, + ExecuteSqlRequest.class, + CommitRequest.class); + } else if (isMultiplexedSessionsEnabled()) { assertThat(mockSpanner.getRequestTypes()) .containsExactly( CreateSessionRequest.class, @@ -260,7 +270,11 @@ public void asyncRunnerWaitsUntilAsyncUpdateHasFinished() throws Exception { }, executor); res.get(); - if (isMultiplexedSessionsEnabled()) { + if (isMultiplexedSessionsEnabledForRW()) { + assertThat(mockSpanner.getRequestTypes()) + .containsAtLeast( + CreateSessionRequest.class, ExecuteSqlRequest.class, CommitRequest.class); + } else if (isMultiplexedSessionsEnabled()) { // The mock server could have received a CreateSession request for a multiplexed session, but // it could also be that that request has not yet reached the server. assertThat(mockSpanner.getRequestTypes()) @@ -404,7 +418,17 @@ public void asyncRunnerBatchUpdateAbortedWithoutGettingResult() throws Exception executor); assertThat(result.get()).isNull(); assertThat(attempt.get()).isEqualTo(2); - if (isMultiplexedSessionsEnabled()) { + if (isMultiplexedSessionsEnabledForRW()) { + assertThat(mockSpanner.getRequestTypes()) + .containsExactly( + CreateSessionRequest.class, + ExecuteSqlRequest.class, + ExecuteBatchDmlRequest.class, + CommitRequest.class, + ExecuteSqlRequest.class, + ExecuteBatchDmlRequest.class, + CommitRequest.class); + } else if (isMultiplexedSessionsEnabled()) { assertThat(mockSpanner.getRequestTypes()) .containsExactly( CreateSessionRequest.class, @@ -463,7 +487,11 @@ public void asyncRunnerWaitsUntilAsyncBatchUpdateHasFinished() throws Exception }, executor); res.get(); - if (isMultiplexedSessionsEnabled()) { + if (isMultiplexedSessionsEnabledForRW()) { + assertThat(mockSpanner.getRequestTypes()) + .containsExactly( + CreateSessionRequest.class, ExecuteBatchDmlRequest.class, CommitRequest.class); + } else if (isMultiplexedSessionsEnabled()) { assertThat(mockSpanner.getRequestTypes()) .containsExactly( CreateSessionRequest.class, @@ -576,4 +604,11 @@ private boolean isMultiplexedSessionsEnabled() { } return spanner.getOptions().getSessionPoolOptions().getUseMultiplexedSession(); } + + private boolean isMultiplexedSessionsEnabledForRW() { + if (spanner.getOptions() == null || spanner.getOptions().getSessionPoolOptions() == null) { + return false; + } + return spanner.getOptions().getSessionPoolOptions().getUseMultiplexedSessionForRW(); + } } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncTransactionManagerTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncTransactionManagerTest.java index 371d3688c94..81a94edd980 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncTransactionManagerTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncTransactionManagerTest.java @@ -179,9 +179,11 @@ public void asyncTransactionManager_shouldRollbackOnCloseAsync() throws Exceptio AsyncTransactionManager manager = client().transactionManagerAsync(); TransactionContext txn = manager.beginAsync().get(); txn.executeUpdateAsync(UPDATE_STATEMENT).get(); - final TransactionSelector selector = - ((TransactionContextImpl) ((SessionPoolTransactionContext) txn).delegate) - .getTransactionSelector(); + if (txn instanceof SessionPoolTransactionContext) { + txn = ((SessionPoolTransactionContext) txn).delegate; + } + TransactionContextImpl impl = (TransactionContextImpl) txn; + final TransactionSelector selector = impl.getTransactionSelector(); SpannerApiFutures.get(manager.closeAsync()); // The mock server should already have the Rollback request, as we are waiting for the returned @@ -359,7 +361,22 @@ public void asyncTransactionManagerFireAndForgetInvalidUpdate() throws Exception ExecuteSqlRequest.class, ExecuteSqlRequest.class, CommitRequest.class); - if (isMultiplexedSessionsEnabled()) { + ImmutableList> expectedRequestsWithMultiplexedSessionForRW = + ImmutableList.of( + CreateSessionRequest.class, + // The first update that fails. This will cause a transaction retry. + ExecuteSqlRequest.class, + // The retry will use an explicit BeginTransaction call. + BeginTransactionRequest.class, + // The first update will again fail, but now there is a transaction id, so the + // transaction can continue. + ExecuteSqlRequest.class, + ExecuteSqlRequest.class, + CommitRequest.class); + if (isMultiplexedSessionsEnabledForRW()) { + assertThat(mockSpanner.getRequestTypes()) + .containsExactlyElementsIn(expectedRequestsWithMultiplexedSessionForRW); + } else if (isMultiplexedSessionsEnabled()) { assertThat(mockSpanner.getRequestTypes()).containsAtLeastElementsIn(expectedRequests); } else { assertThat(mockSpanner.getRequestTypes()).containsExactlyElementsIn(expectedRequests); @@ -502,14 +519,25 @@ public void asyncTransactionManagerUpdateAbortedWithoutGettingResult() throws Ex // The server may receive 1 or 2 commit requests depending on whether the call to // commitAsync() already knows that the transaction has aborted. If it does, it will not // attempt to call the Commit RPC and instead directly propagate the Aborted error. - assertThat(mockSpanner.getRequestTypes()) - .containsAtLeast( - BatchCreateSessionsRequest.class, - ExecuteSqlRequest.class, - // The retry will use a BeginTransaction RPC. - BeginTransactionRequest.class, - ExecuteSqlRequest.class, - CommitRequest.class); + if (isMultiplexedSessionsEnabledForRW()) { + assertThat(mockSpanner.getRequestTypes()) + .containsAtLeast( + CreateSessionRequest.class, + ExecuteSqlRequest.class, + // The retry will use a BeginTransaction RPC. + BeginTransactionRequest.class, + ExecuteSqlRequest.class, + CommitRequest.class); + } else { + assertThat(mockSpanner.getRequestTypes()) + .containsAtLeast( + BatchCreateSessionsRequest.class, + ExecuteSqlRequest.class, + // The retry will use a BeginTransaction RPC. + BeginTransactionRequest.class, + ExecuteSqlRequest.class, + CommitRequest.class); + } break; } catch (AbortedException e) { transactionContextFuture = manager.resetForRetryAsync(); @@ -557,7 +585,11 @@ public void asyncTransactionManagerWaitsUntilAsyncUpdateHasFinished() throws Exc executor) .commitAsync() .get(); - if (isMultiplexedSessionsEnabled()) { + if (isMultiplexedSessionsEnabledForRW()) { + assertThat(mockSpanner.getRequestTypes()) + .containsExactly( + CreateSessionRequest.class, ExecuteSqlRequest.class, CommitRequest.class); + } else if (isMultiplexedSessionsEnabled()) { assertThat(mockSpanner.getRequestTypes()) .containsExactly( CreateSessionRequest.class, @@ -678,7 +710,16 @@ public void asyncTransactionManagerFireAndForgetInvalidBatchUpdate() throws Exce ExecuteBatchDmlRequest.class, ExecuteBatchDmlRequest.class, CommitRequest.class); - if (isMultiplexedSessionsEnabled()) { + ImmutableList> expectedRequestsWithMultiplexedSessionsRW = + ImmutableList.of( + CreateSessionRequest.class, + ExecuteBatchDmlRequest.class, + ExecuteBatchDmlRequest.class, + CommitRequest.class); + if (isMultiplexedSessionsEnabledForRW()) { + assertThat(mockSpanner.getRequestTypes()) + .containsExactlyElementsIn(expectedRequestsWithMultiplexedSessionsRW); + } else if (isMultiplexedSessionsEnabled()) { assertThat(mockSpanner.getRequestTypes()).containsAtLeastElementsIn(expectedRequests); } else { assertThat(mockSpanner.getRequestTypes()).containsExactlyElementsIn(expectedRequests); @@ -722,7 +763,17 @@ public void asyncTransactionManagerBatchUpdateAborted() throws Exception { BeginTransactionRequest.class, ExecuteBatchDmlRequest.class, CommitRequest.class); - if (isMultiplexedSessionsEnabled()) { + ImmutableList> expectedRequestsWithMultiplexedSessionsRW = + ImmutableList.of( + CreateSessionRequest.class, + ExecuteBatchDmlRequest.class, + BeginTransactionRequest.class, + ExecuteBatchDmlRequest.class, + CommitRequest.class); + if (isMultiplexedSessionsEnabledForRW()) { + assertThat(mockSpanner.getRequestTypes()) + .containsExactlyElementsIn(expectedRequestsWithMultiplexedSessionsRW); + } else if (isMultiplexedSessionsEnabled()) { assertThat(mockSpanner.getRequestTypes()).containsAtLeastElementsIn(expectedRequests); } else { assertThat(mockSpanner.getRequestTypes()).containsExactlyElementsIn(expectedRequests); @@ -764,7 +815,20 @@ public void asyncTransactionManagerBatchUpdateAbortedBeforeFirstStatement() thro BeginTransactionRequest.class, ExecuteBatchDmlRequest.class, CommitRequest.class); - if (isMultiplexedSessionsEnabled()) { + // When requests run using multiplexed session with read-write enabled, the + // BatchCreateSessionsRequest will not be + // triggered because we are creating an empty pool during initialization. + ImmutableList> expectedRequestsWithMultiplexedSessionsRW = + ImmutableList.of( + CreateSessionRequest.class, + ExecuteBatchDmlRequest.class, + BeginTransactionRequest.class, + ExecuteBatchDmlRequest.class, + CommitRequest.class); + if (isMultiplexedSessionsEnabledForRW()) { + assertThat(mockSpanner.getRequestTypes()) + .containsExactlyElementsIn(expectedRequestsWithMultiplexedSessionsRW); + } else if (isMultiplexedSessionsEnabled()) { assertThat(mockSpanner.getRequestTypes()).containsAtLeastElementsIn(expectedRequests); } else { assertThat(mockSpanner.getRequestTypes()).containsExactlyElementsIn(expectedRequests); @@ -825,7 +889,18 @@ public void asyncTransactionManagerWithBatchUpdateCommitAborted() throws Excepti BeginTransactionRequest.class, ExecuteBatchDmlRequest.class, CommitRequest.class); - if (isMultiplexedSessionsEnabled()) { + ImmutableList> expectedRequestsWithMultiplexedSessionsRW = + ImmutableList.of( + CreateSessionRequest.class, + ExecuteBatchDmlRequest.class, + CommitRequest.class, + BeginTransactionRequest.class, + ExecuteBatchDmlRequest.class, + CommitRequest.class); + if (isMultiplexedSessionsEnabledForRW()) { + assertThat(mockSpanner.getRequestTypes()) + .containsExactlyElementsIn(expectedRequestsWithMultiplexedSessionsRW); + } else if (isMultiplexedSessionsEnabled()) { assertThat(mockSpanner.getRequestTypes()).containsAtLeastElementsIn(expectedRequests); } else { assertThat(mockSpanner.getRequestTypes()).containsExactlyElementsIn(expectedRequests); @@ -867,27 +942,50 @@ public void asyncTransactionManagerBatchUpdateAbortedWithoutGettingResult() thro assertThat(attempt.get()).isEqualTo(2); List> requests = mockSpanner.getRequestTypes(); // Remove the CreateSession requests for multiplexed sessions, as those are not relevant for - // this test. - requests.removeIf(request -> request == CreateSessionRequest.class); + // this test if multiplexed session for read-write is not enabled. + if (!isMultiplexedSessionsEnabledForRW()) { + requests.removeIf(request -> request == CreateSessionRequest.class); + } int size = Iterables.size(requests); assertThat(size).isIn(Range.closed(5, 6)); if (size == 5) { - assertThat(requests) - .containsExactly( - BatchCreateSessionsRequest.class, - ExecuteBatchDmlRequest.class, - BeginTransactionRequest.class, - ExecuteBatchDmlRequest.class, - CommitRequest.class); + if (isMultiplexedSessionsEnabledForRW()) { + assertThat(requests) + .containsExactly( + CreateSessionRequest.class, + ExecuteBatchDmlRequest.class, + BeginTransactionRequest.class, + ExecuteBatchDmlRequest.class, + CommitRequest.class); + } else { + assertThat(requests) + .containsExactly( + BatchCreateSessionsRequest.class, + ExecuteBatchDmlRequest.class, + BeginTransactionRequest.class, + ExecuteBatchDmlRequest.class, + CommitRequest.class); + } } else { - assertThat(requests) - .containsExactly( - BatchCreateSessionsRequest.class, - ExecuteBatchDmlRequest.class, - CommitRequest.class, - BeginTransactionRequest.class, - ExecuteBatchDmlRequest.class, - CommitRequest.class); + if (isMultiplexedSessionsEnabledForRW()) { + assertThat(requests) + .containsExactly( + CreateSessionRequest.class, + ExecuteBatchDmlRequest.class, + CommitRequest.class, + BeginTransactionRequest.class, + ExecuteBatchDmlRequest.class, + CommitRequest.class); + } else { + assertThat(requests) + .containsExactly( + BatchCreateSessionsRequest.class, + ExecuteBatchDmlRequest.class, + CommitRequest.class, + BeginTransactionRequest.class, + ExecuteBatchDmlRequest.class, + CommitRequest.class); + } } } @@ -918,7 +1016,13 @@ public void asyncTransactionManagerWithBatchUpdateCommitFails() { ImmutableList> expectedRequests = ImmutableList.of( BatchCreateSessionsRequest.class, ExecuteBatchDmlRequest.class, CommitRequest.class); - if (isMultiplexedSessionsEnabled()) { + ImmutableList> expectedRequestsWithMultiplexedSessionsRW = + ImmutableList.of( + CreateSessionRequest.class, ExecuteBatchDmlRequest.class, CommitRequest.class); + if (isMultiplexedSessionsEnabledForRW()) { + assertThat(mockSpanner.getRequestTypes()) + .containsExactlyElementsIn(expectedRequestsWithMultiplexedSessionsRW); + } else if (isMultiplexedSessionsEnabled()) { assertThat(mockSpanner.getRequestTypes()).containsAtLeastElementsIn(expectedRequests); } else { assertThat(mockSpanner.getRequestTypes()).containsExactlyElementsIn(expectedRequests); @@ -949,7 +1053,13 @@ public void asyncTransactionManagerWaitsUntilAsyncBatchUpdateHasFinished() throw ImmutableList> expectedRequests = ImmutableList.of( BatchCreateSessionsRequest.class, ExecuteBatchDmlRequest.class, CommitRequest.class); - if (isMultiplexedSessionsEnabled()) { + ImmutableList> expectedRequestsWithMultiplexedSessionsRW = + ImmutableList.of( + CreateSessionRequest.class, ExecuteBatchDmlRequest.class, CommitRequest.class); + if (isMultiplexedSessionsEnabledForRW()) { + assertThat(mockSpanner.getRequestTypes()) + .containsExactlyElementsIn(expectedRequestsWithMultiplexedSessionsRW); + } else if (isMultiplexedSessionsEnabled()) { assertThat(mockSpanner.getRequestTypes()).containsAtLeastElementsIn(expectedRequests); } else { assertThat(mockSpanner.getRequestTypes()).containsExactlyElementsIn(expectedRequests); @@ -1122,4 +1232,11 @@ private boolean isMultiplexedSessionsEnabled() { } return spanner.getOptions().getSessionPoolOptions().getUseMultiplexedSession(); } + + private boolean isMultiplexedSessionsEnabledForRW() { + if (spanner.getOptions() == null || spanner.getOptions().getSessionPoolOptions() == null) { + return false; + } + return spanner.getOptions().getSessionPoolOptions().getUseMultiplexedSessionForRW(); + } } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseClientImplTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseClientImplTest.java index 1523a62fd7e..87ea5c19ce9 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseClientImplTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseClientImplTest.java @@ -3142,15 +3142,20 @@ public void testDatabaseOrInstanceDoesNotExistOnCreate() { .readWriteTransaction() .run(transaction -> transaction.executeUpdate(UPDATE_STATEMENT))); // No additional requests should have been sent by the client. - // Note that in case of the use of multiplexed sessions, then we have 2 requests: - // Note that in case of the use of regular sessions, then we have 1 request: - // 1. BatchCreateSessions for the session pool. - // 2. CreateSession for the multiplexed session. - assertThat(mockSpanner.getRequests()) - .hasSize( - spanner.getOptions().getSessionPoolOptions().getUseMultiplexedSession() - ? 2 - : 1); + if (spanner.getOptions().getSessionPoolOptions().getUseMultiplexedSession() + && !spanner.getOptions().getSessionPoolOptions().getUseMultiplexedSessionForRW()) { + // Note that in case of the use of multiplexed sessions for read-only alone, then we + // have 2 requests: + // 1. BatchCreateSessions for the session pool. + // 2. CreateSession for the multiplexed session. + assertThat(mockSpanner.getRequests()).hasSize(2); + } else { + // Note that in case of the use of regular sessions, then we have 1 request: + // BatchCreateSessions for the session pool. + // Note that in case of the use of multiplexed sessions for read-write, then we have 1 + // request: CreateSession for the multiplexed session. + assertThat(mockSpanner.getRequests()).hasSize(1); + } } } mockSpanner.reset(); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/OpenTelemetrySpanTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/OpenTelemetrySpanTest.java index cbc449815e7..90e66526f14 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/OpenTelemetrySpanTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/OpenTelemetrySpanTest.java @@ -150,7 +150,7 @@ public class OpenTelemetrySpanTest { "Commit Done", "Transaction Attempt Succeeded"); - private int expectedReadWriteTransactionCount = 7; + private int expectedReadWriteTransactionEventsCount = 7; private List expectedReadWriteTransactionErrorWithBeginTransactionEvents = ImmutableList.of( "Acquiring session", @@ -244,6 +244,7 @@ public void setUp() throws Exception { SessionPoolOptions.newBuilder() .setMinSessions(2) .setWaitForMinSessionsDuration(Duration.ofSeconds(10)) + .setSkipVerifyingBeginTransactionForMuxRW(true) .build()); spanner = builder.build().getService(); @@ -458,6 +459,16 @@ public void transactionRunner() { "CloudSpannerOperation.Commit", "CloudSpannerOperation.BatchCreateSessions", "CloudSpanner.ReadWriteTransaction"); + + if (isMultiplexedSessionsEnabledForRW()) { + expectedReadWriteTransactionEvents = + ImmutableList.of( + "Starting Transaction Attempt", + "Starting Commit", + "Commit Done", + "Transaction Attempt Succeeded"); + expectedReadWriteTransactionEventsCount = 4; + } DatabaseClient client = getClient(); TransactionRunner runner = client.readWriteTransaction(); runner.run(transaction -> transaction.executeUpdate(UPDATE_STATEMENT)); @@ -502,7 +513,7 @@ public void transactionRunner() { verifyRequestEvents( spanItem, expectedReadWriteTransactionEvents, - expectedReadWriteTransactionCount); + expectedReadWriteTransactionEventsCount); verifyCommonAttributes(spanItem); break; default: @@ -528,6 +539,14 @@ public void transactionRunnerWithError() { "CloudSpannerOperation.BatchCreateSessions", "CloudSpannerOperation.ExecuteUpdate", "CloudSpanner.ReadWriteTransaction"); + if (isMultiplexedSessionsEnabledForRW()) { + expectedReadWriteTransactionErrorEvents = + ImmutableList.of( + "Starting Transaction Attempt", + "Transaction Attempt Failed in user operation", + "exception"); + expectedReadWriteTransactionErrorEventsCount = 3; + } DatabaseClient client = getClient(); TransactionRunner runner = client.readWriteTransaction(); SpannerException e = @@ -589,6 +608,18 @@ public void transactionRunnerWithFailedAndBeginTransaction() { "CloudSpannerOperation.Commit", "CloudSpannerOperation.BatchCreateSessions", "CloudSpanner.ReadWriteTransaction"); + if (isMultiplexedSessionsEnabledForRW()) { + expectedReadWriteTransactionErrorWithBeginTransactionEvents = + ImmutableList.of( + "Starting Transaction Attempt", + "Transaction Attempt Aborted in user operation. Retrying", + "Creating Transaction", + "Transaction Creation Done", + "Starting Commit", + "Commit Done", + "Transaction Attempt Succeeded"); + expectedReadWriteTransactionErrorWithBeginTransactionEventsCount = 8; + } DatabaseClient client = getClient(); assertEquals( Long.valueOf(1L), @@ -892,4 +923,11 @@ private boolean isMultiplexedSessionsEnabled() { } return spanner.getOptions().getSessionPoolOptions().getUseMultiplexedSession(); } + + private boolean isMultiplexedSessionsEnabledForRW() { + if (spanner.getOptions() == null || spanner.getOptions().getSessionPoolOptions() == null) { + return false; + } + return spanner.getOptions().getSessionPoolOptions().getUseMultiplexedSessionForRW(); + } } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/RetryOnDifferentGrpcChannelMockServerTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/RetryOnDifferentGrpcChannelMockServerTest.java index 267c6077add..b078ac9e1c0 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/RetryOnDifferentGrpcChannelMockServerTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/RetryOnDifferentGrpcChannelMockServerTest.java @@ -22,6 +22,7 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeFalse; import com.google.cloud.NoCredentials; import com.google.cloud.spanner.MockSpannerServiceImpl.SimulatedExecutionTime; @@ -133,6 +134,9 @@ public void testReadWriteTransaction_retriesOnNewChannel() { AtomicInteger attempts = new AtomicInteger(); try (Spanner spanner = builder.build().getService()) { + assumeFalse( + "RetryOnDifferentGrpcChannel handler is not implemented for read-write with multiplexed sessions", + isMultiplexedSessionsEnabledForRW(spanner)); DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); client .readWriteTransaction() @@ -168,6 +172,9 @@ public void testReadWriteTransaction_stopsRetrying() { SimulatedExecutionTime.ofStickyException(Status.DEADLINE_EXCEEDED.asRuntimeException())); try (Spanner spanner = builder.build().getService()) { + assumeFalse( + "RetryOnDifferentGrpcChannel handler is not implemented for read-write with multiplexed sessions", + isMultiplexedSessionsEnabledForRW(spanner)); DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); SpannerException exception = assertThrows( @@ -211,6 +218,9 @@ public void testDenyListedChannelIsCleared() { SimulatedExecutionTime.ofStickyException(Status.DEADLINE_EXCEEDED.asRuntimeException())); try (Spanner spanner = builder.build().getService()) { + assumeFalse( + "RetryOnDifferentGrpcChannel handler is not implemented for read-write with multiplexed sessions", + isMultiplexedSessionsEnabledForRW(spanner)); DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); // Retry until all channels have been deny-listed. @@ -339,6 +349,9 @@ public void testReadWriteTransaction_withGrpcContextDeadline_doesNotRetry() { SimulatedExecutionTime.ofMinimumAndRandomTime(500, 500)); try (Spanner spanner = builder.build().getService()) { + assumeFalse( + "RetryOnDifferentGrpcChannel handler is not implemented for read-write with multiplexed sessions", + isMultiplexedSessionsEnabledForRW(spanner)); DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); ScheduledExecutorService service = Executors.newScheduledThreadPool(1); Context context = @@ -365,4 +378,11 @@ public void testReadWriteTransaction_withGrpcContextDeadline_doesNotRetry() { // up. assertEquals(1, mockSpanner.countRequestsOfType(BeginTransactionRequest.class)); } + + private boolean isMultiplexedSessionsEnabledForRW(Spanner spanner) { + if (spanner.getOptions() == null || spanner.getOptions().getSessionPoolOptions() == null) { + return false; + } + return spanner.getOptions().getSessionPoolOptions().getUseMultiplexedSessionForRW(); + } } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpanTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpanTest.java index b87b7ba9752..5e9825c9659 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpanTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpanTest.java @@ -480,7 +480,23 @@ public void transactionRunner() { "Request for 2 sessions returned 2 sessions", "Request for 1 multiplexed session returned 1 session", "Creating 2 sessions"); - if (isMultiplexedSessionsEnabled()) { + List expectedAnnotationsForMultiplexedSessionsRW = + ImmutableList.of( + "Starting Transaction Attempt", + "Starting Commit", + "Commit Done", + "Transaction Attempt Succeeded", + "Requesting 2 sessions", + "Request for 2 sessions returned 2 sessions", + "Request for 1 multiplexed session returned 1 session", + "Creating 2 sessions"); + if (isMultiplexedSessionsEnabledForRW()) { + verifyAnnotations( + failOnOverkillTraceComponent.getAnnotations().stream() + .distinct() + .collect(Collectors.toList()), + expectedAnnotationsForMultiplexedSessionsRW); + } else if (isMultiplexedSessionsEnabled()) { verifyAnnotations( failOnOverkillTraceComponent.getAnnotations().stream() .distinct() @@ -538,7 +554,21 @@ public void transactionRunnerWithError() { "Request for 1 multiplexed session returned 1 session", "Request for 2 sessions returned 2 sessions", "Creating 2 sessions"); - if (isMultiplexedSessionsEnabled()) { + List expectedAnnotationsForMultiplexedSessionsRW = + ImmutableList.of( + "Starting Transaction Attempt", + "Transaction Attempt Failed in user operation", + "Requesting 2 sessions", + "Request for 1 multiplexed session returned 1 session", + "Request for 2 sessions returned 2 sessions", + "Creating 2 sessions"); + if (isMultiplexedSessionsEnabledForRW()) { + verifyAnnotations( + failOnOverkillTraceComponent.getAnnotations().stream() + .distinct() + .collect(Collectors.toList()), + expectedAnnotationsForMultiplexedSessionsRW); + } else if (isMultiplexedSessionsEnabled()) { verifyAnnotations( failOnOverkillTraceComponent.getAnnotations().stream() .distinct() @@ -565,4 +595,11 @@ private boolean isMultiplexedSessionsEnabled() { } return spanner.getOptions().getSessionPoolOptions().getUseMultiplexedSession(); } + + private boolean isMultiplexedSessionsEnabledForRW() { + if (spanner.getOptions() == null || spanner.getOptions().getSessionPoolOptions() == null) { + return false; + } + return spanner.getOptions().getSessionPoolOptions().getUseMultiplexedSessionForRW(); + } } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/AbortedTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/AbortedTest.java index 00e396c498c..151ac89e3a0 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/AbortedTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/AbortedTest.java @@ -72,6 +72,8 @@ public void testCommitAborted() { AbortInterceptor interceptor = new AbortInterceptor(0); try (ITConnection connection = createConnection(interceptor, new CountTransactionRetryListener())) { + interceptor.setUsingMultiplexedSession( + isMultiplexedSessionsEnabledForRW(connection.getSpanner())); // verify that the there is no test record try (ResultSet rs = connection.executeQuery(Statement.of("SELECT COUNT(*) AS C FROM TEST WHERE ID=1"))) { @@ -112,6 +114,8 @@ public void testCommitAbortedDuringUpdateWithReturning() { AbortInterceptor interceptor = new AbortInterceptor(0); try (ITConnection connection = createConnection(interceptor, new CountTransactionRetryListener())) { + interceptor.setUsingMultiplexedSession( + isMultiplexedSessionsEnabledForRW(connection.getSpanner())); // verify that the there is no test record try (ResultSet rs = connection.executeQuery(Statement.of("SELECT COUNT(*) AS C FROM TEST WHERE ID=1"))) { diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/AbstractMockServerTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/AbstractMockServerTest.java index fa3ab00b138..ac6a6ecbc77 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/AbstractMockServerTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/AbstractMockServerTest.java @@ -327,4 +327,11 @@ boolean isMultiplexedSessionsEnabled(Spanner spanner) { } return spanner.getOptions().getSessionPoolOptions().getUseMultiplexedSession(); } + + boolean isMultiplexedSessionsEnabledForRW(Spanner spanner) { + if (spanner.getOptions() == null || spanner.getOptions().getSessionPoolOptions() == null) { + return false; + } + return spanner.getOptions().getSessionPoolOptions().getUseMultiplexedSessionForRW(); + } } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITAbstractSpannerTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITAbstractSpannerTest.java index 5f2d88ac930..7bf6a670d9c 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITAbstractSpannerTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITAbstractSpannerTest.java @@ -95,6 +95,8 @@ static ExecutionStep of(StatementExecutionStep step) { private boolean onlyInjectOnce = false; private final Random random = new Random(); + private boolean usingMultiplexedsession = false; + public AbortInterceptor(double probability) { Preconditions.checkArgument(probability >= 0.0D && probability <= 1.0D); this.probability = probability; @@ -110,6 +112,14 @@ public void setOnlyInjectOnce(boolean value) { this.onlyInjectOnce = value; } + /** + * Set this value to true if a multiplexed session is being used. Determining this directly from + * TransactionManagerImpl is challenging as it is a private class. + */ + public void setUsingMultiplexedSession(boolean value) { + this.usingMultiplexedsession = value; + } + protected boolean shouldAbort(String statement, ExecutionStep step) { return probability > random.nextDouble(); } @@ -133,27 +143,35 @@ public void intercept( return; } Class cls = Class.forName("com.google.cloud.spanner.TransactionManagerImpl"); - Class cls2 = - Class.forName("com.google.cloud.spanner.SessionPool$AutoClosingTransactionManager"); - Field delegateField = cls2.getDeclaredField("delegate"); - delegateField.setAccessible(true); - watch = watch.reset().start(); - while (delegateField.get(tx) == null && watch.elapsed(TimeUnit.MILLISECONDS) < 100) { - Thread.sleep(1L); - } - TransactionManager delegate = (TransactionManager) delegateField.get(tx); - if (delegate == null) { - return; + if (usingMultiplexedsession) { + Field stateField = cls.getDeclaredField("txnState"); + stateField.setAccessible(true); + tx.rollback(); + stateField.set(tx, TransactionState.ABORTED); + } else { + Class cls2 = + Class.forName( + "com.google.cloud.spanner.SessionPool$AutoClosingTransactionManager"); + Field delegateField = cls2.getDeclaredField("delegate"); + delegateField.setAccessible(true); + watch = watch.reset().start(); + while (delegateField.get(tx) == null && watch.elapsed(TimeUnit.MILLISECONDS) < 100) { + Thread.sleep(1L); + } + TransactionManager delegate = (TransactionManager) delegateField.get(tx); + if (delegate == null) { + return; + } + Field stateField = cls.getDeclaredField("txnState"); + stateField.setAccessible(true); + + // First rollback the delegate, and then pretend it aborted. + // We should call rollback on the delegate and not the wrapping + // AutoClosingTransactionManager, as the latter would cause the session to be returned + // to the session pool. + delegate.rollback(); + stateField.set(delegate, TransactionState.ABORTED); } - Field stateField = cls.getDeclaredField("txnState"); - stateField.setAccessible(true); - - // First rollback the delegate, and then pretend it aborted. - // We should call rollback on the delegate and not the wrapping - // AutoClosingTransactionManager, as the latter would cause the session to be returned - // to the session pool. - delegate.rollback(); - stateField.set(delegate, TransactionState.ABORTED); } catch (Exception e) { throw new RuntimeException(e); }