vitess.io/vitess@v0.16.2/go/vt/vttablet/tabletserver/tx_pool_test.go (about)

     1  /*
     2  Copyright 2019 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package tabletserver
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"sync"
    23  	"testing"
    24  	"time"
    25  
    26  	"vitess.io/vitess/go/vt/callerid"
    27  	vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc"
    28  	"vitess.io/vitess/go/vt/vterrors"
    29  
    30  	"vitess.io/vitess/go/vt/vttablet/tabletserver/tx"
    31  
    32  	"github.com/stretchr/testify/require"
    33  
    34  	"vitess.io/vitess/go/mysql/fakesqldb"
    35  	"vitess.io/vitess/go/sqltypes"
    36  	"vitess.io/vitess/go/vt/vttablet/tabletserver/tabletenv"
    37  
    38  	querypb "vitess.io/vitess/go/vt/proto/query"
    39  )
    40  
    41  func TestTxPoolExecuteCommit(t *testing.T) {
    42  	db, txPool, _, closer := setup(t)
    43  	defer closer()
    44  
    45  	sql := "select 'this is a query'"
    46  	// begin a transaction and then return the connection
    47  	conn, _, _, err := txPool.Begin(ctx, &querypb.ExecuteOptions{}, false, 0, nil, nil)
    48  	require.NoError(t, err)
    49  
    50  	id := conn.ReservedID()
    51  	conn.Unlock()
    52  
    53  	// get the connection and execute a query on it
    54  	conn2, err := txPool.GetAndLock(id, "")
    55  	require.NoError(t, err)
    56  	_, _ = conn2.Exec(ctx, sql, 1, true)
    57  	conn2.Unlock()
    58  
    59  	// get the connection again and now commit it
    60  	conn3, err := txPool.GetAndLock(id, "")
    61  	require.NoError(t, err)
    62  
    63  	_, err = txPool.Commit(ctx, conn3)
    64  	require.NoError(t, err)
    65  
    66  	// try committing again. this should fail
    67  	_, err = txPool.Commit(ctx, conn)
    68  	require.EqualError(t, err, "not in a transaction")
    69  
    70  	// wrap everything up and assert
    71  	requireLogs(t, db.QueryLog(), "begin", sql, "commit")
    72  	conn3.Release(tx.TxCommit)
    73  }
    74  
    75  func TestTxPoolExecuteRollback(t *testing.T) {
    76  	db, txPool, _, closer := setup(t)
    77  	defer closer()
    78  
    79  	conn, _, _, err := txPool.Begin(ctx, &querypb.ExecuteOptions{}, false, 0, nil, nil)
    80  	require.NoError(t, err)
    81  	defer conn.Release(tx.TxRollback)
    82  
    83  	err = txPool.Rollback(ctx, conn)
    84  	require.NoError(t, err)
    85  
    86  	// try rolling back again, this should be no-op.
    87  	err = txPool.Rollback(ctx, conn)
    88  	require.NoError(t, err, "not in a transaction")
    89  
    90  	requireLogs(t, db.QueryLog(), "begin", "rollback")
    91  }
    92  
    93  func TestTxPoolExecuteRollbackOnClosedConn(t *testing.T) {
    94  	db, txPool, _, closer := setup(t)
    95  	defer closer()
    96  
    97  	conn, _, _, err := txPool.Begin(ctx, &querypb.ExecuteOptions{}, false, 0, nil, nil)
    98  	require.NoError(t, err)
    99  	defer conn.Release(tx.TxRollback)
   100  
   101  	conn.Close()
   102  
   103  	// rollback should not be logged.
   104  	err = txPool.Rollback(ctx, conn)
   105  	require.NoError(t, err)
   106  
   107  	requireLogs(t, db.QueryLog(), "begin")
   108  }
   109  
   110  func TestTxPoolRollbackNonBusy(t *testing.T) {
   111  	db, txPool, _, closer := setup(t)
   112  	defer closer()
   113  
   114  	// start two transactions, and mark one of them as unused
   115  	conn1, _, _, err := txPool.Begin(ctx, &querypb.ExecuteOptions{}, false, 0, nil, nil)
   116  	require.NoError(t, err)
   117  	conn2, _, _, err := txPool.Begin(ctx, &querypb.ExecuteOptions{}, false, 0, nil, nil)
   118  	require.NoError(t, err)
   119  	conn2.Unlock() // this marks conn2 as NonBusy
   120  
   121  	// This should rollback only txid2.
   122  	txPool.Shutdown(ctx)
   123  
   124  	// committing tx1 should not be an issue
   125  	_, err = txPool.Commit(ctx, conn1)
   126  	require.NoError(t, err)
   127  
   128  	// Trying to get back to conn2 should not work since the transaction has been rolled back
   129  	_, err = txPool.GetAndLock(conn2.ReservedID(), "")
   130  	require.Error(t, err)
   131  
   132  	conn1.Release(tx.TxCommit)
   133  
   134  	requireLogs(t, db.QueryLog(), "begin", "begin", "rollback", "commit")
   135  }
   136  
   137  func TestTxPoolTransactionIsolation(t *testing.T) {
   138  	db, txPool, _, closer := setup(t)
   139  	defer closer()
   140  
   141  	c2, _, _, err := txPool.Begin(ctx, &querypb.ExecuteOptions{TransactionIsolation: querypb.ExecuteOptions_READ_COMMITTED}, false, 0, nil, nil)
   142  	require.NoError(t, err)
   143  	c2.Release(tx.TxClose)
   144  
   145  	requireLogs(t, db.QueryLog(), "begin")
   146  }
   147  
   148  func TestTxPoolAutocommit(t *testing.T) {
   149  	db, txPool, _, closer := setup(t)
   150  	defer closer()
   151  
   152  	// Start a transaction with autocommit. This will ensure that the executor does not send begin/commit statements
   153  	// to mysql.
   154  	// This test is meaningful because if txPool.Begin were to send a BEGIN statement to the connection, it will fatal
   155  	// because is not in the list of expected queries (i.e db.AddQuery hasn't been called).
   156  	conn1, _, _, err := txPool.Begin(ctx, &querypb.ExecuteOptions{TransactionIsolation: querypb.ExecuteOptions_AUTOCOMMIT}, false, 0, nil, nil)
   157  	require.NoError(t, err)
   158  
   159  	// run a query to see it in the query log
   160  	query := "select 3"
   161  	conn1.Exec(ctx, query, 1, false)
   162  
   163  	_, err = txPool.Commit(ctx, conn1)
   164  	require.NoError(t, err)
   165  	conn1.Release(tx.TxCommit)
   166  
   167  	// finally, we should only see the query + the initial collation set, no begin/commit
   168  	requireLogs(t, db.QueryLog(), "select 3")
   169  }
   170  
   171  // TestTxPoolBeginWithPoolConnectionError_TransientErrno2006 tests the case
   172  // where we see a transient errno 2006 e.g. because MySQL killed the
   173  // db connection. DBConn.Exec() is going to reconnect and retry automatically
   174  // due to this connection error and the BEGIN will succeed.
   175  func TestTxPoolBeginWithPoolConnectionError_Errno2006_Transient(t *testing.T) {
   176  	db, txPool := primeTxPoolWithConnection(t)
   177  	defer db.Close()
   178  	defer txPool.Close()
   179  
   180  	// Close the connection on the server side.
   181  	db.CloseAllConnections()
   182  	err := db.WaitForClose(2 * time.Second)
   183  	require.NoError(t, err)
   184  
   185  	txConn, _, _, err := txPool.Begin(ctx, &querypb.ExecuteOptions{}, false, 0, nil, nil)
   186  	require.NoError(t, err, "Begin should have succeeded after the retry in DBConn.Exec()")
   187  	txConn.Release(tx.TxCommit)
   188  }
   189  
   190  // primeTxPoolWithConnection is a helper function. It reconstructs the
   191  // scenario where future transactions are going to reuse an open db connection.
   192  func primeTxPoolWithConnection(t *testing.T) (*fakesqldb.DB, *TxPool) {
   193  	t.Helper()
   194  	db := fakesqldb.New(t)
   195  	txPool, _ := newTxPool()
   196  	// Set the capacity to 1 to ensure that the db connection is reused.
   197  	txPool.scp.conns.SetCapacity(1)
   198  	txPool.Open(db.ConnParams(), db.ConnParams(), db.ConnParams())
   199  
   200  	// Run a query to trigger a database connection. That connection will be
   201  	// reused by subsequent transactions.
   202  	db.AddQuery("begin", &sqltypes.Result{})
   203  	db.AddQuery("rollback", &sqltypes.Result{})
   204  	txConn, _, _, err := txPool.Begin(ctx, &querypb.ExecuteOptions{}, false, 0, nil, nil)
   205  	require.NoError(t, err)
   206  	txConn.Release(tx.TxCommit)
   207  
   208  	return db, txPool
   209  }
   210  
   211  func TestTxPoolBeginWithError(t *testing.T) {
   212  	db, txPool, limiter, closer := setup(t)
   213  	defer closer()
   214  	db.AddRejectedQuery("begin", errRejected)
   215  
   216  	im := &querypb.VTGateCallerID{
   217  		Username: "user",
   218  	}
   219  	ef := &vtrpcpb.CallerID{
   220  		Principal: "principle",
   221  	}
   222  
   223  	ctxWithCallerID := callerid.NewContext(ctx, ef, im)
   224  	_, _, _, err := txPool.Begin(ctxWithCallerID, &querypb.ExecuteOptions{}, false, 0, nil, nil)
   225  	require.Error(t, err)
   226  	require.Contains(t, err.Error(), "error: rejected")
   227  	require.Equal(t, vtrpcpb.Code_UNKNOWN, vterrors.Code(err), "wrong error code for Begin error")
   228  
   229  	// Regression test for #6727: make sure the tx limiter is decremented if grabbing a connection
   230  	// errors for whatever reason.
   231  	require.Equal(t,
   232  		[]fakeLimiterEntry{
   233  			{
   234  				immediate: im,
   235  				effective: ef,
   236  				isRelease: false,
   237  			},
   238  			{
   239  				immediate: im,
   240  				effective: ef,
   241  				isRelease: true,
   242  			},
   243  		}, limiter.Actions())
   244  }
   245  
   246  func TestTxPoolBeginWithPreQueryError(t *testing.T) {
   247  	db, txPool, _, closer := setup(t)
   248  	defer closer()
   249  	db.AddRejectedQuery("pre_query", errRejected)
   250  	_, _, _, err := txPool.Begin(ctx, &querypb.ExecuteOptions{}, false, 0, []string{"pre_query"}, nil)
   251  	require.Error(t, err)
   252  	require.Contains(t, err.Error(), "error: rejected")
   253  	require.Equal(t, vtrpcpb.Code_UNKNOWN, vterrors.Code(err), "wrong error code for Begin error")
   254  }
   255  
   256  func TestTxPoolCancelledContextError(t *testing.T) {
   257  	// given
   258  	db, txPool, _, closer := setup(t)
   259  	defer closer()
   260  	ctx, cancel := context.WithCancel(ctx)
   261  	cancel()
   262  
   263  	// when
   264  	_, _, _, err := txPool.Begin(ctx, &querypb.ExecuteOptions{}, false, 0, nil, nil)
   265  
   266  	// then
   267  	require.Error(t, err)
   268  	require.Contains(t, err.Error(), "transaction pool aborting request due to already expired context")
   269  	require.Equal(t, vtrpcpb.Code_DEADLINE_EXCEEDED, vterrors.Code(err))
   270  	require.Empty(t, db.QueryLog())
   271  }
   272  
   273  func TestTxPoolWaitTimeoutError(t *testing.T) {
   274  	env := newEnv("TabletServerTest")
   275  	env.Config().TxPool.Size = 1
   276  	env.Config().TxPool.MaxWaiters = 0
   277  	env.Config().TxPool.TimeoutSeconds = 1
   278  	// given
   279  	db, txPool, _, closer := setupWithEnv(t, env)
   280  	defer closer()
   281  
   282  	// lock the only connection in the pool.
   283  	conn, _, _, err := txPool.Begin(ctx, &querypb.ExecuteOptions{}, false, 0, nil, nil)
   284  	require.NoError(t, err)
   285  	defer conn.Unlock()
   286  
   287  	// try locking one more connection.
   288  	_, _, _, err = txPool.Begin(ctx, &querypb.ExecuteOptions{}, false, 0, nil, nil)
   289  
   290  	// then
   291  	require.Error(t, err)
   292  	require.Contains(t, err.Error(), "transaction pool connection limit exceeded")
   293  	require.Equal(t, vtrpcpb.Code_RESOURCE_EXHAUSTED, vterrors.Code(err))
   294  
   295  	requireLogs(t, db.QueryLog(), "begin")
   296  	require.True(t, conn.TxProperties().LogToFile)
   297  }
   298  
   299  func TestTxPoolRollbackFailIsPassedThrough(t *testing.T) {
   300  	sql := "alter table test_table add test_column int"
   301  	db, txPool, _, closer := setup(t)
   302  	defer closer()
   303  	db.AddRejectedQuery("rollback", errRejected)
   304  
   305  	conn1, _, _, err := txPool.Begin(ctx, &querypb.ExecuteOptions{}, false, 0, nil, nil)
   306  	require.NoError(t, err)
   307  
   308  	_, err = conn1.Exec(ctx, sql, 1, true)
   309  	require.NoError(t, err)
   310  
   311  	// rollback is refused by the underlying db and the error is passed on
   312  	err = txPool.Rollback(ctx, conn1)
   313  	require.Error(t, err)
   314  	require.Contains(t, err.Error(), "error: rejected")
   315  
   316  	conn1.Unlock()
   317  }
   318  
   319  func TestTxPoolGetConnRecentlyRemovedTransaction(t *testing.T) {
   320  	db, txPool, _, _ := setup(t)
   321  	defer db.Close()
   322  	conn1, _, _, _ := txPool.Begin(ctx, &querypb.ExecuteOptions{}, false, 0, nil, nil)
   323  	id := conn1.ReservedID()
   324  	conn1.Unlock()
   325  	txPool.Close()
   326  
   327  	assertErrorMatch := func(id int64, reason string) {
   328  		conn, err := txPool.GetAndLock(id, "for query")
   329  		if err == nil { //
   330  			conn.Releasef("fail")
   331  			t.Errorf("expected to get an error")
   332  			return
   333  		}
   334  
   335  		want := fmt.Sprintf("transaction %v: ended at .* \\(%v\\)", id, reason)
   336  		require.Regexp(t, want, err.Error())
   337  	}
   338  
   339  	assertErrorMatch(id, "pool closed")
   340  
   341  	txPool, _ = newTxPool()
   342  	txPool.Open(db.ConnParams(), db.ConnParams(), db.ConnParams())
   343  
   344  	conn1, _, _, _ = txPool.Begin(ctx, &querypb.ExecuteOptions{}, false, 0, nil, nil)
   345  	id = conn1.ReservedID()
   346  	_, err := txPool.Commit(ctx, conn1)
   347  	require.NoError(t, err)
   348  
   349  	conn1.Releasef("transaction committed")
   350  
   351  	assertErrorMatch(id, "transaction committed")
   352  
   353  	env := txPool.env
   354  	env.Config().SetTxTimeoutForWorkload(1*time.Millisecond, querypb.ExecuteOptions_OLTP)
   355  	env.Config().SetTxTimeoutForWorkload(1*time.Millisecond, querypb.ExecuteOptions_OLAP)
   356  	txPool, _ = newTxPoolWithEnv(env)
   357  	txPool.Open(db.ConnParams(), db.ConnParams(), db.ConnParams())
   358  	defer txPool.Close()
   359  
   360  	conn1, _, _, err = txPool.Begin(ctx, &querypb.ExecuteOptions{}, false, 0, nil, nil)
   361  	require.NoError(t, err, "unable to start transaction: %v", err)
   362  	conn1.Unlock()
   363  	id = conn1.ReservedID()
   364  	time.Sleep(20 * time.Millisecond)
   365  
   366  	assertErrorMatch(id, "exceeded timeout: 1ms")
   367  }
   368  
   369  func TestTxPoolCloseKillsStrayTransactions(t *testing.T) {
   370  	_, txPool, _, closer := setup(t)
   371  	defer closer()
   372  
   373  	startingStray := txPool.env.Stats().InternalErrors.Counts()["StrayTransactions"]
   374  
   375  	// Start stray transaction.
   376  	conn, _, _, err := txPool.Begin(context.Background(), &querypb.ExecuteOptions{}, false, 0, nil, nil)
   377  	require.NoError(t, err)
   378  	conn.Unlock()
   379  
   380  	// Close kills stray transaction.
   381  	txPool.Close()
   382  	require.Equal(t, int64(1), txPool.env.Stats().InternalErrors.Counts()["StrayTransactions"]-startingStray)
   383  	require.Equal(t, 0, txPool.scp.Capacity())
   384  }
   385  
   386  func TestTxTimeoutKillsTransactions(t *testing.T) {
   387  	env := newEnv("TabletServerTest")
   388  	env.Config().TxPool.Size = 1
   389  	env.Config().TxPool.MaxWaiters = 0
   390  	env.Config().Oltp.TxTimeoutSeconds = 1
   391  	_, txPool, limiter, closer := setupWithEnv(t, env)
   392  	defer closer()
   393  	startingKills := txPool.env.Stats().KillCounters.Counts()["Transactions"]
   394  
   395  	im := &querypb.VTGateCallerID{
   396  		Username: "user",
   397  	}
   398  	ef := &vtrpcpb.CallerID{
   399  		Principal: "principle",
   400  	}
   401  
   402  	ctxWithCallerID := callerid.NewContext(ctx, ef, im)
   403  
   404  	// Start transaction.
   405  	conn, _, _, err := txPool.Begin(ctxWithCallerID, &querypb.ExecuteOptions{}, false, 0, nil, nil)
   406  	require.NoError(t, err)
   407  	conn.Unlock()
   408  
   409  	// Let it time out and get killed by the tx killer.
   410  	time.Sleep(1200 * time.Millisecond)
   411  
   412  	// Verify that the tx killer ran.
   413  	require.Equal(t, int64(1), txPool.env.Stats().KillCounters.Counts()["Transactions"]-startingKills)
   414  
   415  	// Regression test for #6727: make sure the tx limiter is decremented when the tx killer closes
   416  	// a transaction.
   417  	require.Equal(t,
   418  		[]fakeLimiterEntry{
   419  			{
   420  				immediate: im,
   421  				effective: ef,
   422  				isRelease: false,
   423  			},
   424  			{
   425  				immediate: im,
   426  				effective: ef,
   427  				isRelease: true,
   428  			},
   429  		}, limiter.Actions())
   430  }
   431  
   432  func TestTxTimeoutDoesNotKillShortLivedTransactions(t *testing.T) {
   433  	env := newEnv("TabletServerTest")
   434  	env.Config().TxPool.Size = 1
   435  	env.Config().TxPool.MaxWaiters = 0
   436  	env.Config().Oltp.TxTimeoutSeconds = 1
   437  	_, txPool, _, closer := setupWithEnv(t, env)
   438  	defer closer()
   439  	startingKills := txPool.env.Stats().KillCounters.Counts()["Transactions"]
   440  
   441  	im := &querypb.VTGateCallerID{
   442  		Username: "user",
   443  	}
   444  	ef := &vtrpcpb.CallerID{
   445  		Principal: "principle",
   446  	}
   447  
   448  	ctxWithCallerID := callerid.NewContext(ctx, ef, im)
   449  
   450  	// Start transaction.
   451  	conn, _, _, err := txPool.Begin(ctxWithCallerID, &querypb.ExecuteOptions{}, false, 0, nil, nil)
   452  	require.NoError(t, err)
   453  	conn.Unlock()
   454  
   455  	// Sleep for less than the tx timeout
   456  	time.Sleep(800 * time.Millisecond)
   457  
   458  	// Verify that the tx killer did not run.
   459  	require.Equal(t, int64(0), txPool.env.Stats().KillCounters.Counts()["Transactions"]-startingKills)
   460  }
   461  
   462  func TestTxTimeoutKillsOlapTransactions(t *testing.T) {
   463  	env := newEnv("TabletServerTest")
   464  	env.Config().TxPool.Size = 1
   465  	env.Config().TxPool.MaxWaiters = 0
   466  	env.Config().Oltp.TxTimeoutSeconds = 1
   467  	env.Config().Olap.TxTimeoutSeconds = 2
   468  	_, txPool, _, closer := setupWithEnv(t, env)
   469  	defer closer()
   470  	startingKills := txPool.env.Stats().KillCounters.Counts()["Transactions"]
   471  
   472  	im := &querypb.VTGateCallerID{
   473  		Username: "user",
   474  	}
   475  	ef := &vtrpcpb.CallerID{
   476  		Principal: "principle",
   477  	}
   478  
   479  	ctxWithCallerID := callerid.NewContext(ctx, ef, im)
   480  
   481  	// Start transaction.
   482  	conn, _, _, err := txPool.Begin(ctxWithCallerID, &querypb.ExecuteOptions{
   483  		Workload: querypb.ExecuteOptions_OLAP,
   484  	}, false, 0, nil, nil)
   485  	require.NoError(t, err)
   486  	conn.Unlock()
   487  
   488  	// After the OLTP timeout elapses, the tx should not have been killed.
   489  	time.Sleep(1200 * time.Millisecond)
   490  	require.Equal(t, int64(0), txPool.env.Stats().KillCounters.Counts()["Transactions"]-startingKills)
   491  
   492  	// After the OLAP timeout elapses, the tx should have been killed.
   493  	time.Sleep(1000 * time.Millisecond)
   494  	require.Equal(t, int64(1), txPool.env.Stats().KillCounters.Counts()["Transactions"]-startingKills)
   495  }
   496  
   497  func TestTxTimeoutNotEnforcedForZeroLengthTimeouts(t *testing.T) {
   498  	env := newEnv("TabletServerTest")
   499  	env.Config().TxPool.Size = 2
   500  	env.Config().TxPool.MaxWaiters = 0
   501  	env.Config().Oltp.TxTimeoutSeconds = 0
   502  	env.Config().Olap.TxTimeoutSeconds = 0
   503  	_, txPool, _, closer := setupWithEnv(t, env)
   504  	defer closer()
   505  	startingKills := txPool.env.Stats().KillCounters.Counts()["Transactions"]
   506  
   507  	im := &querypb.VTGateCallerID{
   508  		Username: "user",
   509  	}
   510  	ef := &vtrpcpb.CallerID{
   511  		Principal: "principle",
   512  	}
   513  
   514  	ctxWithCallerID := callerid.NewContext(ctx, ef, im)
   515  
   516  	// Start transactions.
   517  	conn0, _, _, err := txPool.Begin(ctxWithCallerID, &querypb.ExecuteOptions{}, false, 0, nil, nil)
   518  	require.NoError(t, err)
   519  	conn1, _, _, err := txPool.Begin(ctxWithCallerID, &querypb.ExecuteOptions{
   520  		Workload: querypb.ExecuteOptions_OLAP,
   521  	}, false, 0, nil, nil)
   522  	require.NoError(t, err)
   523  	conn0.Unlock()
   524  	conn1.Unlock()
   525  
   526  	// Not really a great test, but we don't want to make unit tests take a
   527  	// long time by using a long sleep. Probably a better approach would be to
   528  	// either monkeypatch time.Now() or pass in a mock Clock to TxPool.
   529  	time.Sleep(2000 * time.Millisecond)
   530  
   531  	// OLTP tx is not killed.
   532  	require.Equal(t, int64(0), txPool.env.Stats().KillCounters.Counts()["Transactions"]-startingKills)
   533  	// OLAP tx is not killed.
   534  	require.Equal(t, int64(0), txPool.env.Stats().KillCounters.Counts()["Transactions"]-startingKills)
   535  }
   536  
   537  func TestTxTimeoutReservedConn(t *testing.T) {
   538  	env := newEnv("TabletServerTest")
   539  	env.Config().TxPool.Size = 1
   540  	env.Config().TxPool.MaxWaiters = 0
   541  	env.Config().Oltp.TxTimeoutSeconds = 1
   542  	env.Config().Olap.TxTimeoutSeconds = 2
   543  	_, txPool, _, closer := setupWithEnv(t, env)
   544  	defer closer()
   545  	startingRcKills := txPool.env.Stats().KillCounters.Counts()["ReservedConnection"]
   546  	startingTxKills := txPool.env.Stats().KillCounters.Counts()["Transactions"]
   547  
   548  	im := &querypb.VTGateCallerID{
   549  		Username: "user",
   550  	}
   551  	ef := &vtrpcpb.CallerID{
   552  		Principal: "principle",
   553  	}
   554  
   555  	ctxWithCallerID := callerid.NewContext(ctx, ef, im)
   556  
   557  	// Start OLAP transaction and return it to pool right away.
   558  	conn0, _, _, err := txPool.Begin(ctxWithCallerID, &querypb.ExecuteOptions{
   559  		Workload: querypb.ExecuteOptions_OLAP,
   560  	}, false, 0, nil, nil)
   561  	require.NoError(t, err)
   562  	// Taint the connection.
   563  	conn0.Taint(ctxWithCallerID, nil)
   564  	conn0.Unlock()
   565  
   566  	// tx should not timeout after OLTP timeout.
   567  	time.Sleep(1200 * time.Millisecond)
   568  	require.Equal(t, int64(0), txPool.env.Stats().KillCounters.Counts()["ReservedConnection"]-startingRcKills)
   569  	require.Equal(t, int64(0), txPool.env.Stats().KillCounters.Counts()["Transactions"]-startingTxKills)
   570  
   571  	// tx should timeout after OLAP timeout.
   572  	time.Sleep(1000 * time.Millisecond)
   573  	require.Equal(t, int64(1), txPool.env.Stats().KillCounters.Counts()["ReservedConnection"]-startingRcKills)
   574  	require.Equal(t, int64(1), txPool.env.Stats().KillCounters.Counts()["Transactions"]-startingTxKills)
   575  }
   576  
   577  func TestTxTimeoutReusedReservedConn(t *testing.T) {
   578  	env := newEnv("TabletServerTest")
   579  	env.Config().TxPool.Size = 1
   580  	env.Config().TxPool.MaxWaiters = 0
   581  	env.Config().Oltp.TxTimeoutSeconds = 1
   582  	env.Config().Olap.TxTimeoutSeconds = 2
   583  	_, txPool, _, closer := setupWithEnv(t, env)
   584  	defer closer()
   585  	startingRcKills := txPool.env.Stats().KillCounters.Counts()["ReservedConnection"]
   586  	startingTxKills := txPool.env.Stats().KillCounters.Counts()["Transactions"]
   587  
   588  	im := &querypb.VTGateCallerID{
   589  		Username: "user",
   590  	}
   591  	ef := &vtrpcpb.CallerID{
   592  		Principal: "principle",
   593  	}
   594  
   595  	ctxWithCallerID := callerid.NewContext(ctx, ef, im)
   596  
   597  	// Start OLAP transaction and return it to pool right away.
   598  	conn0, _, _, err := txPool.Begin(ctxWithCallerID, &querypb.ExecuteOptions{
   599  		Workload: querypb.ExecuteOptions_OLAP,
   600  	}, false, 0, nil, nil)
   601  	require.NoError(t, err)
   602  	// Taint the connection.
   603  	conn0.Taint(ctxWithCallerID, nil)
   604  	conn0.Unlock()
   605  
   606  	// Reuse underlying connection in an OLTP transaction.
   607  	conn1, _, _, err := txPool.Begin(ctxWithCallerID, &querypb.ExecuteOptions{}, false, conn0.ReservedID(), nil, nil)
   608  	require.NoError(t, err)
   609  	require.Equal(t, conn1.ReservedID(), conn0.ReservedID())
   610  	conn1.Unlock()
   611  
   612  	// tx should timeout after OLTP timeout.
   613  	time.Sleep(1200 * time.Millisecond)
   614  	require.Equal(t, int64(1), txPool.env.Stats().KillCounters.Counts()["ReservedConnection"]-startingRcKills)
   615  	require.Equal(t, int64(1), txPool.env.Stats().KillCounters.Counts()["Transactions"]-startingTxKills)
   616  }
   617  
   618  func TestTxPoolBeginStatements(t *testing.T) {
   619  	_, txPool, _, closer := setup(t)
   620  	defer closer()
   621  
   622  	testCases := []struct {
   623  		txIsolationLevel querypb.ExecuteOptions_TransactionIsolation
   624  		txAccessModes    []querypb.ExecuteOptions_TransactionAccessMode
   625  		readOnly         bool
   626  
   627  		expBeginSQL string
   628  		expErr      string
   629  	}{{
   630  		txIsolationLevel: querypb.ExecuteOptions_DEFAULT,
   631  		expBeginSQL:      "begin",
   632  	}, {
   633  		txIsolationLevel: querypb.ExecuteOptions_DEFAULT,
   634  		readOnly:         true,
   635  		expBeginSQL:      "start transaction read only",
   636  	}, {
   637  		txIsolationLevel: querypb.ExecuteOptions_READ_UNCOMMITTED,
   638  		expBeginSQL:      "set transaction isolation level read uncommitted; begin",
   639  	}, {
   640  		txIsolationLevel: querypb.ExecuteOptions_READ_UNCOMMITTED,
   641  		readOnly:         true,
   642  		expBeginSQL:      "set transaction isolation level read uncommitted; start transaction read only",
   643  	}, {
   644  		txIsolationLevel: querypb.ExecuteOptions_READ_COMMITTED,
   645  		expBeginSQL:      "set transaction isolation level read committed; begin",
   646  	}, {
   647  		txIsolationLevel: querypb.ExecuteOptions_READ_COMMITTED,
   648  		readOnly:         true,
   649  		expBeginSQL:      "set transaction isolation level read committed; start transaction read only",
   650  	}, {
   651  		txIsolationLevel: querypb.ExecuteOptions_REPEATABLE_READ,
   652  		expBeginSQL:      "set transaction isolation level repeatable read; begin",
   653  	}, {
   654  		txIsolationLevel: querypb.ExecuteOptions_REPEATABLE_READ,
   655  		readOnly:         true,
   656  		expBeginSQL:      "set transaction isolation level repeatable read; start transaction read only",
   657  	}, {
   658  		txIsolationLevel: querypb.ExecuteOptions_SERIALIZABLE,
   659  		expBeginSQL:      "set transaction isolation level serializable; begin",
   660  	}, {
   661  		txIsolationLevel: querypb.ExecuteOptions_SERIALIZABLE,
   662  		readOnly:         true,
   663  		expBeginSQL:      "set transaction isolation level serializable; start transaction read only",
   664  	}, {
   665  		txIsolationLevel: querypb.ExecuteOptions_CONSISTENT_SNAPSHOT_READ_ONLY,
   666  		expBeginSQL:      "set session session_track_gtids = START_GTID; set transaction isolation level repeatable read; start transaction with consistent snapshot, read only",
   667  	}, {
   668  		txIsolationLevel: querypb.ExecuteOptions_CONSISTENT_SNAPSHOT_READ_ONLY,
   669  		readOnly:         true,
   670  		expBeginSQL:      "set session session_track_gtids = START_GTID; set transaction isolation level repeatable read; start transaction with consistent snapshot, read only",
   671  	}, {
   672  		txIsolationLevel: querypb.ExecuteOptions_AUTOCOMMIT,
   673  		expBeginSQL:      "",
   674  	}, {
   675  		txIsolationLevel: querypb.ExecuteOptions_AUTOCOMMIT,
   676  		readOnly:         true,
   677  		expBeginSQL:      "",
   678  	}, {
   679  		txIsolationLevel: querypb.ExecuteOptions_DEFAULT,
   680  		txAccessModes: []querypb.ExecuteOptions_TransactionAccessMode{
   681  			querypb.ExecuteOptions_CONSISTENT_SNAPSHOT,
   682  		},
   683  		expBeginSQL: "start transaction with consistent snapshot",
   684  	}, {
   685  		txIsolationLevel: querypb.ExecuteOptions_READ_COMMITTED,
   686  		txAccessModes: []querypb.ExecuteOptions_TransactionAccessMode{
   687  			querypb.ExecuteOptions_READ_ONLY,
   688  		},
   689  		expBeginSQL: "set transaction isolation level read committed; start transaction read only",
   690  	}, {
   691  		txIsolationLevel: querypb.ExecuteOptions_REPEATABLE_READ,
   692  		txAccessModes: []querypb.ExecuteOptions_TransactionAccessMode{
   693  			querypb.ExecuteOptions_READ_WRITE,
   694  		},
   695  		expBeginSQL: "set transaction isolation level repeatable read; start transaction read write",
   696  	}, {
   697  		txIsolationLevel: querypb.ExecuteOptions_SERIALIZABLE,
   698  		txAccessModes: []querypb.ExecuteOptions_TransactionAccessMode{
   699  			querypb.ExecuteOptions_CONSISTENT_SNAPSHOT,
   700  			querypb.ExecuteOptions_READ_WRITE,
   701  		},
   702  		expBeginSQL: "set transaction isolation level serializable; start transaction with consistent snapshot, read write",
   703  	}, {
   704  		// read write access mode set when readOnly is true. This should fail.
   705  		txIsolationLevel: querypb.ExecuteOptions_DEFAULT,
   706  		txAccessModes: []querypb.ExecuteOptions_TransactionAccessMode{
   707  			querypb.ExecuteOptions_CONSISTENT_SNAPSHOT,
   708  			querypb.ExecuteOptions_READ_WRITE,
   709  		},
   710  		readOnly: true,
   711  		expErr:   "cannot start read write transaction on a read only tablet",
   712  	}, {
   713  		txIsolationLevel: querypb.ExecuteOptions_DEFAULT,
   714  		txAccessModes: []querypb.ExecuteOptions_TransactionAccessMode{
   715  			querypb.ExecuteOptions_CONSISTENT_SNAPSHOT,
   716  			querypb.ExecuteOptions_READ_ONLY,
   717  		},
   718  		readOnly:    true,
   719  		expBeginSQL: "start transaction with consistent snapshot, read only",
   720  	}, {
   721  		txIsolationLevel: querypb.ExecuteOptions_REPEATABLE_READ,
   722  		txAccessModes: []querypb.ExecuteOptions_TransactionAccessMode{
   723  			querypb.ExecuteOptions_CONSISTENT_SNAPSHOT,
   724  		},
   725  		readOnly:    true,
   726  		expBeginSQL: "set transaction isolation level repeatable read; start transaction with consistent snapshot, read only",
   727  	}}
   728  
   729  	for _, tc := range testCases {
   730  		t.Run(fmt.Sprintf("%v:%v:readOnly:%v", tc.txIsolationLevel, tc.txAccessModes, tc.readOnly), func(t *testing.T) {
   731  			options := &querypb.ExecuteOptions{
   732  				TransactionIsolation:  tc.txIsolationLevel,
   733  				TransactionAccessMode: tc.txAccessModes,
   734  			}
   735  			conn, beginSQL, _, err := txPool.Begin(ctx, options, tc.readOnly, 0, nil, nil)
   736  			if tc.expErr != "" {
   737  				require.Error(t, err)
   738  				require.Contains(t, err.Error(), tc.expErr)
   739  				require.Nil(t, conn)
   740  				return
   741  			}
   742  			require.NoError(t, err)
   743  			conn.Release(tx.ConnRelease)
   744  			require.Equal(t, tc.expBeginSQL, beginSQL)
   745  		})
   746  	}
   747  }
   748  
   749  func newTxPool() (*TxPool, *fakeLimiter) {
   750  	return newTxPoolWithEnv(newEnv("TabletServerTest"))
   751  }
   752  
   753  func newTxPoolWithEnv(env tabletenv.Env) (*TxPool, *fakeLimiter) {
   754  	limiter := &fakeLimiter{}
   755  	return NewTxPool(env, limiter), limiter
   756  }
   757  
   758  func newEnv(exporterName string) tabletenv.Env {
   759  	config := tabletenv.NewDefaultConfig()
   760  	config.TxPool.Size = 300
   761  	config.Oltp.TxTimeoutSeconds = 30
   762  	config.TxPool.TimeoutSeconds = 40
   763  	config.TxPool.MaxWaiters = 500000
   764  	config.OltpReadPool.IdleTimeoutSeconds = 30
   765  	config.OlapReadPool.IdleTimeoutSeconds = 30
   766  	config.TxPool.IdleTimeoutSeconds = 30
   767  	env := tabletenv.NewEnv(config, exporterName)
   768  	return env
   769  }
   770  
   771  type fakeLimiterEntry struct {
   772  	immediate *querypb.VTGateCallerID
   773  	effective *vtrpcpb.CallerID
   774  	isRelease bool
   775  }
   776  
   777  type fakeLimiter struct {
   778  	actions []fakeLimiterEntry
   779  	mu      sync.Mutex
   780  }
   781  
   782  func (fl *fakeLimiter) Get(immediate *querypb.VTGateCallerID, effective *vtrpcpb.CallerID) bool {
   783  	fl.mu.Lock()
   784  	defer fl.mu.Unlock()
   785  	fl.actions = append(fl.actions, fakeLimiterEntry{
   786  		immediate: immediate,
   787  		effective: effective,
   788  		isRelease: false,
   789  	})
   790  	return true
   791  }
   792  
   793  func (fl *fakeLimiter) Release(immediate *querypb.VTGateCallerID, effective *vtrpcpb.CallerID) {
   794  	fl.mu.Lock()
   795  	defer fl.mu.Unlock()
   796  	fl.actions = append(fl.actions, fakeLimiterEntry{
   797  		immediate: immediate,
   798  		effective: effective,
   799  		isRelease: true,
   800  	})
   801  }
   802  
   803  func (fl *fakeLimiter) Actions() []fakeLimiterEntry {
   804  	fl.mu.Lock()
   805  	defer fl.mu.Unlock()
   806  	result := make([]fakeLimiterEntry, len(fl.actions))
   807  	copy(result, fl.actions)
   808  	return result
   809  }
   810  
   811  func setup(t *testing.T) (*fakesqldb.DB, *TxPool, *fakeLimiter, func()) {
   812  	db := fakesqldb.New(t)
   813  	db.AddQueryPattern(".*", &sqltypes.Result{})
   814  
   815  	txPool, limiter := newTxPool()
   816  	txPool.Open(db.ConnParams(), db.ConnParams(), db.ConnParams())
   817  
   818  	return db, txPool, limiter, func() {
   819  		txPool.Close()
   820  		db.Close()
   821  	}
   822  }
   823  
   824  func setupWithEnv(t *testing.T, env tabletenv.Env) (*fakesqldb.DB, *TxPool, *fakeLimiter, func()) {
   825  	db := fakesqldb.New(t)
   826  	db.AddQueryPattern(".*", &sqltypes.Result{})
   827  
   828  	txPool, limiter := newTxPoolWithEnv(env)
   829  	txPool.Open(db.ConnParams(), db.ConnParams(), db.ConnParams())
   830  
   831  	return db, txPool, limiter, func() {
   832  		txPool.Close()
   833  		db.Close()
   834  	}
   835  }