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 }