@@ -1294,6 +1294,204 @@ public void testMutationOnlyUsingAsyncTransactionManager() {
12941294 request .getPrecommitToken ().getPrecommitToken ());
12951295 }
12961296
1297+ private Spanner setupSpannerForAbortedBeginTransactionTests () {
1298+ // Force the BeginTransaction RPC to return Aborted the first time it is called. The exception
1299+ // is cleared after the first call, so the retry should succeed.
1300+ mockSpanner .setBeginTransactionExecutionTime (
1301+ SimulatedExecutionTime .ofException (
1302+ mockSpanner .createAbortedException (ByteString .copyFromUtf8 ("test" ))));
1303+
1304+ return SpannerOptions .newBuilder ()
1305+ .setProjectId ("test-project" )
1306+ .setChannelProvider (channelProvider )
1307+ .setCredentials (NoCredentials .getInstance ())
1308+ .setSessionPoolOption (
1309+ SessionPoolOptions .newBuilder ()
1310+ .setUseMultiplexedSession (true )
1311+ .setUseMultiplexedSessionForRW (true )
1312+ .setSkipVerifyingBeginTransactionForMuxRW (true )
1313+ .build ())
1314+ .build ()
1315+ .getService ();
1316+ }
1317+
1318+ private void verifyMutationKeySetInBeginTransactionRequests (
1319+ List <BeginTransactionRequest > beginTransactionRequests ) {
1320+ assertEquals (2 , beginTransactionRequests .size ());
1321+ // Verify the requests are executed using multiplexed sessions
1322+ for (BeginTransactionRequest request : beginTransactionRequests ) {
1323+ assertTrue (mockSpanner .getSession (request .getSession ()).getMultiplexed ());
1324+ assertTrue (request .hasMutationKey ());
1325+ assertTrue (request .getMutationKey ().hasInsert ());
1326+ }
1327+ }
1328+
1329+ private void verifyPreCommitTokenSetInCommitRequest (List <CommitRequest > commitRequests ) {
1330+ assertEquals (1L , commitRequests .size ());
1331+ for (CommitRequest request : commitRequests ) {
1332+ assertTrue (mockSpanner .getSession (request .getSession ()).getMultiplexed ());
1333+ assertNotNull (request .getPrecommitToken ());
1334+ assertEquals (
1335+ ByteString .copyFromUtf8 ("TransactionPrecommitToken" ),
1336+ request .getPrecommitToken ().getPrecommitToken ());
1337+ }
1338+ }
1339+
1340+ // The following 4 tests validate mutation-only cases where the BeginTransaction RPC fails with an
1341+ // ABORTED or retryable error
1342+ @ Test
1343+ public void testMutationOnlyCaseAbortedDuringBeginTransaction () {
1344+ // This test ensures that when a transaction containing only mutations is retried after an
1345+ // ABORT error in the BeginTransaction RPC:
1346+ // 1. The mutation key is correctly included in the BeginTransaction request.
1347+ // 2. The precommit token is properly set in the Commit request.
1348+ Spanner spanner = setupSpannerForAbortedBeginTransactionTests ();
1349+ DatabaseClientImpl client =
1350+ (DatabaseClientImpl ) spanner .getDatabaseClient (DatabaseId .of ("p" , "i" , "d" ));
1351+
1352+ client
1353+ .readWriteTransaction ()
1354+ .run (
1355+ transaction -> {
1356+ Mutation mutation =
1357+ Mutation .newInsertBuilder ("FOO" ).set ("ID" ).to (1L ).set ("NAME" ).to ("Bar" ).build ();
1358+ transaction .buffer (mutation );
1359+ return null ;
1360+ });
1361+
1362+ // Verify that for mutation only case, a mutation key is set in BeginTransactionRequest.
1363+ List <BeginTransactionRequest > beginTransactionRequests =
1364+ mockSpanner .getRequestsOfType (BeginTransactionRequest .class );
1365+ verifyMutationKeySetInBeginTransactionRequests (beginTransactionRequests );
1366+
1367+ // Verify that the latest precommit token is set in the CommitRequest
1368+ List <CommitRequest > commitRequests = mockSpanner .getRequestsOfType (CommitRequest .class );
1369+ verifyPreCommitTokenSetInCommitRequest (commitRequests );
1370+
1371+ spanner .close ();
1372+ }
1373+
1374+ @ Test
1375+ public void testMutationOnlyUsingTransactionManagerAbortedDuringBeginTransaction () {
1376+ // This test ensures that when a transaction containing only mutations is retried after an
1377+ // ABORT error in the BeginTransaction RPC:
1378+ // 1. The mutation key is correctly included in the BeginTransaction request.
1379+ // 2. The precommit token is properly set in the Commit request.
1380+ Spanner spanner = setupSpannerForAbortedBeginTransactionTests ();
1381+ DatabaseClientImpl client =
1382+ (DatabaseClientImpl ) spanner .getDatabaseClient (DatabaseId .of ("p" , "i" , "d" ));
1383+
1384+ try (TransactionManager manager = client .transactionManager ()) {
1385+ TransactionContext transaction = manager .begin ();
1386+ while (true ) {
1387+ try {
1388+ Mutation mutation =
1389+ Mutation .newInsertBuilder ("FOO" ).set ("ID" ).to (1L ).set ("NAME" ).to ("Bar" ).build ();
1390+ transaction .buffer (mutation );
1391+ manager .commit ();
1392+ assertNotNull (manager .getCommitTimestamp ());
1393+ break ;
1394+ } catch (AbortedException e ) {
1395+ transaction = manager .resetForRetry ();
1396+ }
1397+ }
1398+ }
1399+
1400+ // Verify that for mutation only case, a mutation key is set in BeginTransactionRequest.
1401+ List <BeginTransactionRequest > beginTransactionRequests =
1402+ mockSpanner .getRequestsOfType (BeginTransactionRequest .class );
1403+ verifyMutationKeySetInBeginTransactionRequests (beginTransactionRequests );
1404+
1405+ // Verify that the latest precommit token is set in the CommitRequest
1406+ List <CommitRequest > commitRequests = mockSpanner .getRequestsOfType (CommitRequest .class );
1407+ verifyPreCommitTokenSetInCommitRequest (commitRequests );
1408+
1409+ spanner .close ();
1410+ }
1411+
1412+ @ Test
1413+ public void testMutationOnlyUsingAsyncRunnerAbortedDuringBeginTransaction () {
1414+ // This test ensures that when a transaction containing only mutations is retried after an
1415+ // ABORT error in the BeginTransaction RPC:
1416+ // 1. The mutation key is correctly included in the BeginTransaction request.
1417+ // 2. The precommit token is properly set in the Commit request.
1418+
1419+ Spanner spanner = setupSpannerForAbortedBeginTransactionTests ();
1420+ DatabaseClientImpl client =
1421+ (DatabaseClientImpl ) spanner .getDatabaseClient (DatabaseId .of ("p" , "i" , "d" ));
1422+
1423+ AsyncRunner runner = client .runAsync ();
1424+ get (
1425+ runner .runAsync (
1426+ txn -> {
1427+ txn .buffer (
1428+ Mutation .newInsertBuilder ("FOO" ).set ("ID" ).to (1L ).set ("NAME" ).to ("Bar" ).build ());
1429+ return ApiFutures .immediateFuture (null );
1430+ },
1431+ MoreExecutors .directExecutor ()));
1432+
1433+ // Verify that for mutation only case, a mutation key is set in BeginTransactionRequest.
1434+ List <BeginTransactionRequest > beginTransactionRequests =
1435+ mockSpanner .getRequestsOfType (BeginTransactionRequest .class );
1436+ verifyMutationKeySetInBeginTransactionRequests (beginTransactionRequests );
1437+
1438+ // Verify that the latest precommit token is set in the CommitRequest
1439+ List <CommitRequest > commitRequests = mockSpanner .getRequestsOfType (CommitRequest .class );
1440+ verifyPreCommitTokenSetInCommitRequest (commitRequests );
1441+
1442+ spanner .close ();
1443+ }
1444+
1445+ @ Test
1446+ public void testMutationOnlyUsingTransactionManagerAsyncAbortedDuringBeginTransaction ()
1447+ throws Exception {
1448+ // This test verifies that in the case of mutations-only, when a transaction is retried after an
1449+ // ABORT in BeginTransaction RPC, the mutation key is correctly set in the BeginTransaction
1450+ // request
1451+ // and precommit token is set in Commit request.
1452+ Spanner spanner = setupSpannerForAbortedBeginTransactionTests ();
1453+ DatabaseClientImpl client =
1454+ (DatabaseClientImpl ) spanner .getDatabaseClient (DatabaseId .of ("p" , "i" , "d" ));
1455+
1456+ try (AsyncTransactionManager manager = client .transactionManagerAsync ()) {
1457+ TransactionContextFuture transaction = manager .beginAsync ();
1458+ while (true ) {
1459+ CommitTimestampFuture commitTimestamp =
1460+ transaction
1461+ .then (
1462+ (txn , input ) -> {
1463+ txn .buffer (
1464+ Mutation .newInsertBuilder ("FOO" )
1465+ .set ("ID" )
1466+ .to (1L )
1467+ .set ("NAME" )
1468+ .to ("Bar" )
1469+ .build ());
1470+ return ApiFutures .immediateFuture (null );
1471+ },
1472+ MoreExecutors .directExecutor ())
1473+ .commitAsync ();
1474+ try {
1475+ assertThat (commitTimestamp .get ()).isNotNull ();
1476+ break ;
1477+ } catch (AbortedException e ) {
1478+ transaction = manager .resetForRetryAsync ();
1479+ }
1480+ }
1481+ }
1482+
1483+ // Verify that for mutation only case, a mutation key is set in BeginTransactionRequest.
1484+ List <BeginTransactionRequest > beginTransactionRequests =
1485+ mockSpanner .getRequestsOfType (BeginTransactionRequest .class );
1486+ verifyMutationKeySetInBeginTransactionRequests (beginTransactionRequests );
1487+
1488+ // Verify that the latest precommit token is set in the CommitRequest
1489+ List <CommitRequest > commitRequests = mockSpanner .getRequestsOfType (CommitRequest .class );
1490+ verifyPreCommitTokenSetInCommitRequest (commitRequests );
1491+
1492+ spanner .close ();
1493+ }
1494+
12971495 // Tests the behavior of the server-side kill switch for read-write multiplexed sessions..
12981496 @ Test
12991497 public void testInitialBeginTransactionWithRW_receivesUnimplemented_fallsBackToRegularSession () {
0 commit comments