github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/engine/chaos/cases/dm/db.go (about) 1 // Copyright 2022 PingCAP, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package dm 15 16 import ( 17 "context" 18 "fmt" 19 "time" 20 21 "github.com/go-sql-driver/mysql" 22 "github.com/pingcap/tidb/pkg/errno" 23 "github.com/pingcap/tiflow/dm/pkg/conn" 24 tcontext "github.com/pingcap/tiflow/dm/pkg/context" 25 "github.com/pingcap/tiflow/dm/pkg/log" 26 "github.com/pingcap/tiflow/dm/pkg/retry" 27 "github.com/pingcap/tiflow/pkg/errors" 28 "go.uber.org/zap" 29 ) 30 31 type dbConn struct { 32 db *conn.BaseDB 33 con *conn.BaseConn 34 currDB string 35 } 36 37 func newDBConn(ctx context.Context, cfg conn.ScopedDBConfig, currDB string) (*dbConn, error) { 38 db, err := conn.DefaultDBProvider.Apply(cfg) 39 if err != nil { 40 return nil, err 41 } 42 con, err := db.GetBaseConn(ctx) 43 if err != nil { 44 return nil, err 45 } 46 47 return &dbConn{ 48 db: db, 49 con: con, 50 currDB: currDB, 51 }, nil 52 } 53 54 func (dc *dbConn) resetConn(ctx context.Context) error { 55 err := dc.db.ForceCloseConn(dc.con) 56 if err != nil { 57 log.L().Warn("fail to close connection", zap.Error(err)) 58 } 59 dc.con, err = dc.db.GetBaseConn(ctx) 60 if err != nil { 61 return err 62 } 63 _, err = dc.con.ExecuteSQL(tcontext.NewContext(ctx, log.L()), nil, "chaos-cases", []string{fmt.Sprintf("USE %s", dc.currDB)}) 64 return err 65 } 66 67 func ignoreExecSQLError(err error) bool { 68 err = errors.Cause(err) // check the original error 69 mysqlErr, ok := err.(*mysql.MySQLError) 70 if !ok { 71 return false 72 } 73 74 switch mysqlErr.Number { 75 case errno.ErrDupEntry: // HACK: we tolerate `invalid connection`, then `Duplicate entry` may be reported. 76 return true 77 default: 78 return false 79 } 80 } 81 82 func (dc *dbConn) ExecuteSQLs(queries ...string) (int, error) { 83 ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) 84 defer cancel() 85 params := retry.Params{ 86 RetryCount: 3, 87 FirstRetryDuration: time.Second, 88 BackoffStrategy: retry.Stable, 89 IsRetryableFn: func(_ int, err error) bool { 90 if retry.IsConnectionError(err) { 91 // HACK: for some errors like `invalid connection`, `sql: connection is already closed`, we can ignore them just for testing. 92 err = dc.resetConn(ctx) 93 return err == nil 94 } 95 return false 96 }, 97 } 98 99 ret, _, err := dc.con.ApplyRetryStrategy(tcontext.NewContext(ctx, log.L()), params, 100 func(tctx *tcontext.Context) (interface{}, error) { 101 ret, err2 := dc.con.ExecuteSQLWithIgnoreError(tctx, nil, "chaos-cases", ignoreExecSQLError, queries) 102 return ret, err2 103 }) 104 return ret.(int), err 105 }