@@ -1253,12 +1253,6 @@ public void testMutationOnlyUsingAsyncTransactionManager() {
12531253 }
12541254
12551255 private Spanner setupSpannerForAbortedBeginTransactionTests () {
1256- // Force the BeginTransaction RPC to return Aborted the first time it is called. The exception
1257- // is cleared after the first call, so the retry should succeed.
1258- mockSpanner .setBeginTransactionExecutionTime (
1259- SimulatedExecutionTime .ofException (
1260- mockSpanner .createAbortedException (ByteString .copyFromUtf8 ("test" ))));
1261-
12621256 return SpannerOptions .newBuilder ()
12631257 .setProjectId ("test-project" )
12641258 .setChannelProvider (channelProvider )
@@ -1304,6 +1298,13 @@ public void testMutationOnlyCaseAbortedDuringBeginTransaction() {
13041298 // 1. The mutation key is correctly included in the BeginTransaction request.
13051299 // 2. The precommit token is properly set in the Commit request.
13061300 Spanner spanner = setupSpannerForAbortedBeginTransactionTests ();
1301+
1302+ // Force the BeginTransaction RPC to return Aborted the first time it is called. The exception
1303+ // is cleared after the first call, so the retry should succeed.
1304+ mockSpanner .setBeginTransactionExecutionTime (
1305+ SimulatedExecutionTime .ofException (
1306+ mockSpanner .createAbortedException (ByteString .copyFromUtf8 ("test" ))));
1307+
13071308 DatabaseClientImpl client =
13081309 (DatabaseClientImpl ) spanner .getDatabaseClient (DatabaseId .of ("p" , "i" , "d" ));
13091310
@@ -1336,6 +1337,13 @@ public void testMutationOnlyUsingTransactionManagerAbortedDuringBeginTransaction
13361337 // 1. The mutation key is correctly included in the BeginTransaction request.
13371338 // 2. The precommit token is properly set in the Commit request.
13381339 Spanner spanner = setupSpannerForAbortedBeginTransactionTests ();
1340+
1341+ // Force the BeginTransaction RPC to return Aborted the first time it is called. The exception
1342+ // is cleared after the first call, so the retry should succeed.
1343+ mockSpanner .setBeginTransactionExecutionTime (
1344+ SimulatedExecutionTime .ofException (
1345+ mockSpanner .createAbortedException (ByteString .copyFromUtf8 ("test" ))));
1346+
13391347 DatabaseClientImpl client =
13401348 (DatabaseClientImpl ) spanner .getDatabaseClient (DatabaseId .of ("p" , "i" , "d" ));
13411349
@@ -1375,6 +1383,13 @@ public void testMutationOnlyUsingAsyncRunnerAbortedDuringBeginTransaction() {
13751383 // 2. The precommit token is properly set in the Commit request.
13761384
13771385 Spanner spanner = setupSpannerForAbortedBeginTransactionTests ();
1386+
1387+ // Force the BeginTransaction RPC to return Aborted the first time it is called. The exception
1388+ // is cleared after the first call, so the retry should succeed.
1389+ mockSpanner .setBeginTransactionExecutionTime (
1390+ SimulatedExecutionTime .ofException (
1391+ mockSpanner .createAbortedException (ByteString .copyFromUtf8 ("test" ))));
1392+
13781393 DatabaseClientImpl client =
13791394 (DatabaseClientImpl ) spanner .getDatabaseClient (DatabaseId .of ("p" , "i" , "d" ));
13801395
@@ -1408,6 +1423,13 @@ public void testMutationOnlyUsingTransactionManagerAsyncAbortedDuringBeginTransa
14081423 // request
14091424 // and precommit token is set in Commit request.
14101425 Spanner spanner = setupSpannerForAbortedBeginTransactionTests ();
1426+
1427+ // Force the BeginTransaction RPC to return Aborted the first time it is called. The exception
1428+ // is cleared after the first call, so the retry should succeed.
1429+ mockSpanner .setBeginTransactionExecutionTime (
1430+ SimulatedExecutionTime .ofException (
1431+ mockSpanner .createAbortedException (ByteString .copyFromUtf8 ("test" ))));
1432+
14111433 DatabaseClientImpl client =
14121434 (DatabaseClientImpl ) spanner .getDatabaseClient (DatabaseId .of ("p" , "i" , "d" ));
14131435
@@ -1589,7 +1611,7 @@ public void testPartitionedQuery_receivesUnimplemented_fallsBackToRegularSession
15891611
15901612 @ Test
15911613 public void
1592- testReadWriteUnimplementedErrorDuringInitialBeginTransactionRPC_firstReceivesError_secondFallsBackToRegularSessions () {
1614+ testReadWriteUnimplementedErrorDuringInitialBeginTransactionRPC_firstRetriedWithRegularSession_secondFallsBackToRegularSessions () {
15931615 // This test simulates the following scenario,
15941616 // 1. The server-side flag for RW multiplexed sessions is disabled.
15951617 // 2. Application starts. The initial BeginTransaction RPC during client initialization will
@@ -1638,6 +1660,8 @@ public void testPartitionedQuery_receivesUnimplemented_fallsBackToRegularSession
16381660 }
16391661 return null ;
16401662 });
1663+ assertNotNull (runner .getCommitTimestamp ());
1664+ assertNotNull (runner .getCommitResponse ());
16411665
16421666 // Wait until the client sees that MultiplexedSessions are not supported for read-write.
16431667 assertNotNull (client .multiplexedSessionDatabaseClient );
@@ -1689,7 +1713,8 @@ public void testPartitionedQuery_receivesUnimplemented_fallsBackToRegularSession
16891713 }
16901714
16911715 @ Test
1692- public void testReadWriteUnimplemented_firstReceivesError_secondFallsBackToRegularSessions () {
1716+ public void
1717+ testReadWriteUnimplemented_firstRetriedWithRegularSession_secondFallsBackToRegularSessions () {
16931718 // This test simulates the following scenario,
16941719 // 1. The server side flag for read-write multiplexed session is not disabled. When an
16951720 // application starts, the initial BeginTransaction RPC with read-write will succeed.
@@ -1716,6 +1741,10 @@ public void testReadWriteUnimplemented_firstReceivesError_secondFallsBackToRegul
17161741 assertNotNull (txn .getId ());
17171742 assertFalse (client .multiplexedSessionDatabaseClient .unimplementedForRW .get ());
17181743
1744+ // Initially, the first attempt executes an ExecuteSqlRequest using multiplexed sessions, but it
1745+ // fails with UNIMPLEMENTED.
1746+ // On retry, the request should automatically switch to regular sessions, ensuring the
1747+ // transaction completes successfully.
17191748 client
17201749 .readWriteTransaction ()
17211750 .run (
@@ -1728,11 +1757,12 @@ public void testReadWriteUnimplemented_firstReceivesError_secondFallsBackToRegul
17281757 return null ;
17291758 });
17301759
1731- // Verify that the previous failed transaction has marked multiplexed session client to be
1760+ // Verify that the previous failed transaction during first attempt has marked multiplexed
1761+ // session client to be
17321762 // unimplemented for read-write.
17331763 assertTrue (client .multiplexedSessionDatabaseClient .unimplementedForRW .get ());
17341764
1735- // The next read-write transaction will fall back to regular sessions and succeed.
1765+ // The next read-write transaction will automatically fall back to regular sessions and succeed.
17361766 client
17371767 .readWriteTransaction ()
17381768 .run (
@@ -1929,6 +1959,57 @@ public void testBatchWriteAtLeastOnce() {
19291959 assertEquals (1L , client .multiplexedSessionDatabaseClient .getNumSessionsReleased ().get ());
19301960 }
19311961
1962+ @ Test
1963+ public void
1964+ testReadWriteUnimplementedError_DuringExplicitBegin_RetriedWithRegularSessionForInFlightTransaction () {
1965+ // Test scenario:
1966+ // 1. The first attempt does an inline begin using a multiplexed session with an invalid
1967+ // statement, resulting in failure due to invalid syntax.
1968+ // 2. A retry occurs with an explicit begin using a multiplexed session, but we assume the
1969+ // backend flag is turned OFF, leading to UNIMPLEMENTED errors.
1970+ // 3. Upon encountering the UNIMPLEMENTED error, the entire transaction callable is retried
1971+ // using regular sessions, but the inline begin fails again.
1972+ // 4. A final retry executes the explicit BeginTransaction on a regular session.
1973+ Spanner spanner = setupSpannerForAbortedBeginTransactionTests ();
1974+ mockSpanner .setBeginTransactionExecutionTime (
1975+ SimulatedExecutionTime .ofException (
1976+ Status .UNIMPLEMENTED
1977+ .withDescription (
1978+ "Transaction type read_write not supported with multiplexed sessions" )
1979+ .asRuntimeException ()));
1980+
1981+ DatabaseClientImpl client =
1982+ (DatabaseClientImpl ) spanner .getDatabaseClient (DatabaseId .of ("p" , "i" , "d" ));
1983+ TransactionRunner runner = client .readWriteTransaction ();
1984+ Long updateCount =
1985+ runner .run (
1986+ transaction -> {
1987+ // This update statement carries the BeginTransaction, but fails. This will
1988+ // cause the entire transaction to be retried with an explicit
1989+ // BeginTransaction RPC to ensure all statements in the transaction are
1990+ // actually executed against the same transaction.
1991+ SpannerException e =
1992+ assertThrows (
1993+ SpannerException .class ,
1994+ () -> transaction .executeUpdate (INVALID_UPDATE_STATEMENT ));
1995+ assertEquals (ErrorCode .INVALID_ARGUMENT , e .getErrorCode ());
1996+ return transaction .executeUpdate (UPDATE_STATEMENT );
1997+ });
1998+
1999+ assertThat (updateCount ).isEqualTo (1L );
2000+ List <BeginTransactionRequest > beginTransactionRequests =
2001+ mockSpanner .getRequestsOfType (BeginTransactionRequest .class );
2002+ assertEquals (2 , beginTransactionRequests .size ());
2003+
2004+ // Verify the first BeginTransaction request is executed using multiplexed sessions.
2005+ assertTrue (
2006+ mockSpanner .getSession (beginTransactionRequests .get (0 ).getSession ()).getMultiplexed ());
2007+
2008+ // Verify the second BeginTransaction request is executed using regular sessions.
2009+ assertFalse (
2010+ mockSpanner .getSession (beginTransactionRequests .get (1 ).getSession ()).getMultiplexed ());
2011+ }
2012+
19322013 private void waitForSessionToBeReplaced (DatabaseClientImpl client ) {
19332014 assertNotNull (client .multiplexedSessionDatabaseClient );
19342015 SessionReference sessionReference =
0 commit comments