@@ -71,6 +71,7 @@ public void createSpannerInstance() {
7171 SessionPoolOptions .newBuilder ()
7272 .setUseMultiplexedSession (true )
7373 .setUseMultiplexedSessionBlindWrite (true )
74+ .setUseMultiplexedSessionForRW (true )
7475 // Set the maintainer to loop once every 1ms
7576 .setMultiplexedSessionMaintenanceLoopFrequency (Duration .ofMillis (1L ))
7677 // Set multiplexed sessions to be replaced once every 1ms
@@ -467,6 +468,148 @@ public void testWriteAtLeastOnceWithExcludeTxnFromChangeStreams() {
467468 assertEquals (1L , client .multiplexedSessionDatabaseClient .getNumSessionsReleased ().get ());
468469 }
469470
471+ @ Test
472+ public void testReadWriteTransactionUsingTransactionRunner () {
473+ // Queries executed within a R/W transaction via TransactionRunner should use a multiplexed
474+ // session.
475+ // During a retry (due to an ABORTED error), the transaction should use the same multiplexed
476+ // session as before, assuming the maintainer hasn't run in the meantime.
477+ DatabaseClientImpl client =
478+ (DatabaseClientImpl ) spanner .getDatabaseClient (DatabaseId .of ("p" , "i" , "d" ));
479+ // Force the Commit RPC to return Aborted the first time it is called. The exception is cleared
480+ // after the first call, so the retry should succeed.
481+ mockSpanner .setCommitExecutionTime (
482+ SimulatedExecutionTime .ofException (
483+ mockSpanner .createAbortedException (ByteString .copyFromUtf8 ("test" ))));
484+
485+ client
486+ .readWriteTransaction ()
487+ .run (
488+ transaction -> {
489+ try (ResultSet resultSet = transaction .executeQuery (STATEMENT )) {
490+ //noinspection StatementWithEmptyBody
491+ while (resultSet .next ()) {
492+ // ignore
493+ }
494+ }
495+ return null ;
496+ });
497+
498+ List <ExecuteSqlRequest > executeSqlRequests =
499+ mockSpanner .getRequestsOfType (ExecuteSqlRequest .class );
500+ assertEquals (2 , executeSqlRequests .size ());
501+ assertEquals (executeSqlRequests .get (0 ).getSession (), executeSqlRequests .get (1 ).getSession ());
502+
503+ // Verify the requests are executed using multiplexed sessions
504+ for (ExecuteSqlRequest request : executeSqlRequests ) {
505+ assertTrue (mockSpanner .getSession (request .getSession ()).getMultiplexed ());
506+ }
507+
508+ assertNotNull (client .multiplexedSessionDatabaseClient );
509+ assertEquals (1L , client .multiplexedSessionDatabaseClient .getNumSessionsAcquired ().get ());
510+ // TODO: fix this
511+ // assertEquals(1L, client.multiplexedSessionDatabaseClient.getNumSessionsReleased().get());
512+ }
513+
514+ @ Test
515+ public void testReadWriteTransactionUsingTransactionManager () {
516+ // Queries executed within a R/W transaction via TransactionManager should use a multiplexed
517+ // session.
518+ // During a retry (due to an ABORTED error), the transaction should use the same multiplexed
519+ // session as before, assuming the maintainer hasn't run in the meantime.
520+ DatabaseClientImpl client =
521+ (DatabaseClientImpl ) spanner .getDatabaseClient (DatabaseId .of ("p" , "i" , "d" ));
522+ // Force the Commit RPC to return Aborted the first time it is called. The exception is cleared
523+ // after the first call, so the retry should succeed.
524+ mockSpanner .setCommitExecutionTime (
525+ SimulatedExecutionTime .ofException (
526+ mockSpanner .createAbortedException (ByteString .copyFromUtf8 ("test" ))));
527+
528+ try (TransactionManager manager = client .transactionManager ()) {
529+ TransactionContext transaction = manager .begin ();
530+ while (true ) {
531+ try {
532+ try (ResultSet resultSet = transaction .executeQuery (STATEMENT )) {
533+ //noinspection StatementWithEmptyBody
534+ while (resultSet .next ()) {
535+ // ignore
536+ }
537+ }
538+ manager .commit ();
539+ assertNotNull (manager .getCommitTimestamp ());
540+ break ;
541+ } catch (AbortedException e ) {
542+ transaction = manager .resetForRetry ();
543+ }
544+ }
545+ }
546+
547+ List <ExecuteSqlRequest > executeSqlRequests =
548+ mockSpanner .getRequestsOfType (ExecuteSqlRequest .class );
549+ assertEquals (2 , executeSqlRequests .size ());
550+ assertEquals (executeSqlRequests .get (0 ).getSession (), executeSqlRequests .get (1 ).getSession ());
551+
552+ // Verify the requests are executed using multiplexed sessions
553+ for (ExecuteSqlRequest request : executeSqlRequests ) {
554+ assertTrue (mockSpanner .getSession (request .getSession ()).getMultiplexed ());
555+ }
556+
557+ assertNotNull (client .multiplexedSessionDatabaseClient );
558+ assertEquals (1L , client .multiplexedSessionDatabaseClient .getNumSessionsAcquired ().get ());
559+ // TODO: fix this
560+ // assertEquals(1L, client.multiplexedSessionDatabaseClient.getNumSessionsReleased().get());
561+ }
562+
563+ @ Test
564+ public void testMutationUsingWrite () {
565+ DatabaseClientImpl client =
566+ (DatabaseClientImpl ) spanner .getDatabaseClient (DatabaseId .of ("p" , "i" , "d" ));
567+ // Force the Commit RPC to return Aborted the first time it is called. The exception is cleared
568+ // after the first call, so the retry should succeed.
569+ mockSpanner .setCommitExecutionTime (
570+ SimulatedExecutionTime .ofException (
571+ mockSpanner .createAbortedException (ByteString .copyFromUtf8 ("test" ))));
572+ Timestamp timestamp =
573+ client .write (
574+ Collections .singletonList (
575+ Mutation .newInsertBuilder ("FOO" ).set ("ID" ).to (1L ).set ("NAME" ).to ("Bar" ).build ()));
576+ assertNotNull (timestamp );
577+
578+ List <CommitRequest > commitRequests = mockSpanner .getRequestsOfType (CommitRequest .class );
579+ assertEquals (2 , commitRequests .size ());
580+ for (CommitRequest request : commitRequests ) {
581+ assertTrue (mockSpanner .getSession (request .getSession ()).getMultiplexed ());
582+ }
583+
584+ assertNotNull (client .multiplexedSessionDatabaseClient );
585+ assertEquals (1L , client .multiplexedSessionDatabaseClient .getNumSessionsAcquired ().get ());
586+ // assertEquals(1L, client.multiplexedSessionDatabaseClient.getNumSessionsReleased().get());
587+ }
588+
589+ @ Test
590+ public void testMutationUsingWriteWithOptions () {
591+ DatabaseClientImpl client =
592+ (DatabaseClientImpl ) spanner .getDatabaseClient (DatabaseId .of ("p" , "i" , "d" ));
593+ CommitResponse response =
594+ client .writeWithOptions (
595+ Collections .singletonList (
596+ Mutation .newInsertBuilder ("FOO" ).set ("ID" ).to (1L ).set ("NAME" ).to ("Bar" ).build ()),
597+ Options .tag ("app=spanner,env=test" ));
598+ assertNotNull (response );
599+ assertNotNull (response .getCommitTimestamp ());
600+
601+ List <CommitRequest > commitRequests = mockSpanner .getRequestsOfType (CommitRequest .class );
602+ assertThat (commitRequests ).hasSize (1 );
603+ CommitRequest commit = commitRequests .get (0 );
604+ assertNotNull (commit .getRequestOptions ());
605+ assertThat (commit .getRequestOptions ().getTransactionTag ()).isEqualTo ("app=spanner,env=test" );
606+ assertTrue (mockSpanner .getSession (commit .getSession ()).getMultiplexed ());
607+
608+ assertNotNull (client .multiplexedSessionDatabaseClient );
609+ assertEquals (1L , client .multiplexedSessionDatabaseClient .getNumSessionsAcquired ().get ());
610+ // assertEquals(1L, client.multiplexedSessionDatabaseClient.getNumSessionsReleased().get());
611+ }
612+
470613 private void waitForSessionToBeReplaced (DatabaseClientImpl client ) {
471614 assertNotNull (client .multiplexedSessionDatabaseClient );
472615 SessionReference sessionReference =
0 commit comments