vitess.io/vitess@v0.16.2/go/vt/vttablet/tabletmanager/rpc_lock_tables.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 tabletmanager 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "strings" 24 "time" 25 26 "github.com/spf13/pflag" 27 28 "vitess.io/vitess/go/sqlescape" 29 "vitess.io/vitess/go/vt/dbconnpool" 30 "vitess.io/vitess/go/vt/log" 31 "vitess.io/vitess/go/vt/servenv" 32 ) 33 34 var lockTablesTimeout = 1 * time.Minute 35 36 func registerLockTablesFlags(fs *pflag.FlagSet) { 37 fs.DurationVar(&lockTablesTimeout, "lock_tables_timeout", lockTablesTimeout, "How long to keep the table locked before timing out") 38 } 39 40 func init() { 41 servenv.OnParseFor("vtcombo", registerLockTablesFlags) 42 servenv.OnParseFor("vttablet", registerLockTablesFlags) 43 } 44 45 // LockTables will lock all tables with read locks, effectively pausing replication while the lock is held (idempotent) 46 func (tm *TabletManager) LockTables(ctx context.Context) error { 47 // get a connection 48 tm.mutex.Lock() 49 defer tm.mutex.Unlock() 50 51 if tm._lockTablesConnection != nil { 52 // tables are already locked, bail out 53 return errors.New("tables already locked on this tablet") 54 } 55 56 conn, err := tm.MysqlDaemon.GetDbaConnection(ctx) 57 if err != nil { 58 return err 59 } 60 // We successfully opened a connection. If we return for any reason before 61 // storing this connection in the TabletManager object, we need to close it 62 // to avoid leaking it. 63 defer func() { 64 if tm._lockTablesConnection != conn { 65 conn.Close() 66 } 67 }() 68 69 // FTWRL is preferable, so we'll try that first 70 _, err = conn.ExecuteFetch("FLUSH TABLES WITH READ LOCK", 0, false) 71 if err != nil { 72 // as fall back, we can lock each individual table as well. 73 // this requires slightly less privileges but achieves the same effect 74 err = tm.lockTablesUsingLockTables(conn) 75 if err != nil { 76 return err 77 } 78 } 79 log.Infof("[%v] Tables locked", conn.ConnectionID) 80 81 tm._lockTablesConnection = conn 82 tm._lockTablesTimer = time.AfterFunc(lockTablesTimeout, func() { 83 // Here we'll sleep until the timeout time has elapsed. 84 // If the table locks have not been released yet, we'll release them here 85 tm.mutex.Lock() 86 defer tm.mutex.Unlock() 87 88 // We need the mutex locked before we check this field 89 if tm._lockTablesConnection == conn { 90 log.Errorf("table lock timed out and released the lock - something went wrong") 91 err = tm.unlockTablesHoldingMutex() 92 if err != nil { 93 log.Errorf("failed to unlock tables: %v", err) 94 } 95 } 96 }) 97 98 return nil 99 } 100 101 func (tm *TabletManager) lockTablesUsingLockTables(conn *dbconnpool.DBConnection) error { 102 log.Warningf("failed to lock tables with FTWRL - falling back to LOCK TABLES") 103 104 // Ensure schema engine is Open. If vttablet came up in a non_serving role, 105 // the schema engine may not have been initialized. Open() is idempotent, so this 106 // is always safe 107 se := tm.QueryServiceControl.SchemaEngine() 108 if err := se.Open(); err != nil { 109 return err 110 } 111 112 tables := se.GetSchema() 113 tableNames := make([]string, 0, len(tables)) 114 for name := range tables { 115 if name == "dual" { 116 continue 117 } 118 tableNames = append(tableNames, fmt.Sprintf("%s READ", sqlescape.EscapeID(name))) 119 } 120 lockStatement := fmt.Sprintf("LOCK TABLES %v", strings.Join(tableNames, ", ")) 121 _, err := conn.ExecuteFetch("USE "+sqlescape.EscapeID(tm.DBConfigs.DBName), 0, false) 122 if err != nil { 123 return err 124 } 125 126 _, err = conn.ExecuteFetch(lockStatement, 0, false) 127 if err != nil { 128 return err 129 } 130 131 return nil 132 } 133 134 // UnlockTables will unlock all tables (idempotent) 135 func (tm *TabletManager) UnlockTables(ctx context.Context) error { 136 tm.mutex.Lock() 137 defer tm.mutex.Unlock() 138 139 if tm._lockTablesConnection == nil { 140 return fmt.Errorf("tables were not locked") 141 } 142 143 return tm.unlockTablesHoldingMutex() 144 } 145 146 func (tm *TabletManager) unlockTablesHoldingMutex() error { 147 // We are cleaning up manually, let's kill the timer 148 tm._lockTablesTimer.Stop() 149 _, err := tm._lockTablesConnection.ExecuteFetch("UNLOCK TABLES", 0, false) 150 if err != nil { 151 return err 152 } 153 log.Infof("[%v] Tables unlocked", tm._lockTablesConnection.ConnectionID) 154 tm._lockTablesConnection.Close() 155 tm._lockTablesConnection = nil 156 tm._lockTablesTimer = nil 157 158 return nil 159 }