vitess.io/vitess@v0.16.2/go/vt/topo/locks.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 topo 18 19 import ( 20 "context" 21 "encoding/json" 22 "os" 23 "os/user" 24 "path" 25 "sync" 26 "time" 27 28 "github.com/spf13/pflag" 29 30 _flag "vitess.io/vitess/go/internal/flag" 31 "vitess.io/vitess/go/trace" 32 "vitess.io/vitess/go/vt/log" 33 "vitess.io/vitess/go/vt/proto/vtrpc" 34 "vitess.io/vitess/go/vt/servenv" 35 "vitess.io/vitess/go/vt/vterrors" 36 ) 37 38 // This file contains utility methods and definitions to lock 39 // keyspaces and shards. 40 41 var ( 42 // LockTimeout is the maximum duration for which a 43 // shard / keyspace lock can be acquired for. 44 LockTimeout = 45 * time.Second 45 46 // RemoteOperationTimeout is used for operations where we have to 47 // call out to another process. 48 // Used for RPC calls (including topo server calls) 49 RemoteOperationTimeout = 15 * time.Second 50 ) 51 52 // Lock describes a long-running lock on a keyspace or a shard. 53 // It needs to be public as we JSON-serialize it. 54 type Lock struct { 55 // Action and the following fields are set at construction time. 56 Action string 57 HostName string 58 UserName string 59 Time string 60 61 // Status is the current status of the Lock. 62 Status string 63 } 64 65 func init() { 66 for _, cmd := range FlagBinaries { 67 servenv.OnParseFor(cmd, registerTopoLockFlags) 68 } 69 } 70 71 func registerTopoLockFlags(fs *pflag.FlagSet) { 72 fs.DurationVar(&RemoteOperationTimeout, "remote_operation_timeout", RemoteOperationTimeout, "time to wait for a remote operation") 73 fs.DurationVar(&LockTimeout, "lock-timeout", LockTimeout, "Maximum time for which a shard/keyspace lock can be acquired for") 74 } 75 76 // newLock creates a new Lock. 77 func newLock(action string) *Lock { 78 l := &Lock{ 79 Action: action, 80 HostName: "unknown", 81 UserName: "unknown", 82 Time: time.Now().Format(time.RFC3339), 83 Status: "Running", 84 } 85 if h, err := os.Hostname(); err == nil { 86 l.HostName = h 87 } 88 if u, err := user.Current(); err == nil { 89 l.UserName = u.Username 90 } 91 return l 92 } 93 94 // ToJSON returns a JSON representation of the object. 95 func (l *Lock) ToJSON() (string, error) { 96 data, err := json.MarshalIndent(l, "", " ") 97 if err != nil { 98 return "", vterrors.Wrapf(err, "cannot JSON-marshal node") 99 } 100 return string(data), nil 101 } 102 103 // lockInfo is an individual info structure for a lock 104 type lockInfo struct { 105 lockDescriptor LockDescriptor 106 actionNode *Lock 107 } 108 109 // locksInfo is the structure used to remember which locks we took 110 type locksInfo struct { 111 // mu protects the following members of the structure. 112 // Safer to be thread safe here, in case multiple go routines 113 // lock different things. 114 mu sync.Mutex 115 116 // info contains all the locks we took. It is indexed by 117 // keyspace (for keyspaces) or keyspace/shard (for shards). 118 info map[string]*lockInfo 119 } 120 121 // Context glue 122 type locksKeyType int 123 124 var locksKey locksKeyType 125 126 // LockKeyspace will lock the keyspace, and return: 127 // - a context with a locksInfo structure for future reference. 128 // - an unlock method 129 // - an error if anything failed. 130 // 131 // We lock a keyspace for the following operations to be guaranteed 132 // exclusive operation: 133 // * changing a keyspace sharding info fields (is this one necessary?) 134 // * changing a keyspace 'ServedFrom' field (is this one necessary?) 135 // * resharding operations: 136 // - horizontal resharding: includes changing the shard's 'ServedType', 137 // as well as the associated horizontal resharding operations. 138 // - vertical resharding: includes changing the keyspace 'ServedFrom' 139 // field, as well as the associated vertical resharding operations. 140 // - 'vtctl SetShardIsPrimaryServing' emergency operations 141 // - 'vtctl SetShardTabletControl' emergency operations 142 // - 'vtctl SourceShardAdd' and 'vtctl SourceShardDelete' emergency operations 143 // 144 // * keyspace-wide schema changes 145 func (ts *Server) LockKeyspace(ctx context.Context, keyspace, action string) (context.Context, func(*error), error) { 146 i, ok := ctx.Value(locksKey).(*locksInfo) 147 if !ok { 148 i = &locksInfo{ 149 info: make(map[string]*lockInfo), 150 } 151 ctx = context.WithValue(ctx, locksKey, i) 152 } 153 i.mu.Lock() 154 defer i.mu.Unlock() 155 156 // check that we're not already locked 157 if _, ok = i.info[keyspace]; ok { 158 return nil, nil, vterrors.Errorf(vtrpc.Code_INVALID_ARGUMENT, "lock for keyspace %v is already held", keyspace) 159 } 160 161 // lock 162 l := newLock(action) 163 lockDescriptor, err := l.lockKeyspace(ctx, ts, keyspace) 164 if err != nil { 165 return nil, nil, err 166 } 167 168 // and update our structure 169 i.info[keyspace] = &lockInfo{ 170 lockDescriptor: lockDescriptor, 171 actionNode: l, 172 } 173 return ctx, func(finalErr *error) { 174 i.mu.Lock() 175 defer i.mu.Unlock() 176 177 if _, ok := i.info[keyspace]; !ok { 178 if *finalErr != nil { 179 log.Errorf("trying to unlock keyspace %v multiple times", keyspace) 180 } else { 181 *finalErr = vterrors.Errorf(vtrpc.Code_INVALID_ARGUMENT, "trying to unlock keyspace %v multiple times", keyspace) 182 } 183 return 184 } 185 186 err := l.unlockKeyspace(ctx, ts, keyspace, lockDescriptor, *finalErr) 187 if *finalErr != nil { 188 if err != nil { 189 // both error are set, just log the unlock error 190 log.Errorf("unlockKeyspace(%v) failed: %v", keyspace, err) 191 } 192 } else { 193 *finalErr = err 194 } 195 delete(i.info, keyspace) 196 }, nil 197 } 198 199 // CheckKeyspaceLocked can be called on a context to make sure we have the lock 200 // for a given keyspace. 201 func CheckKeyspaceLocked(ctx context.Context, keyspace string) error { 202 // extract the locksInfo pointer 203 i, ok := ctx.Value(locksKey).(*locksInfo) 204 if !ok { 205 return vterrors.Errorf(vtrpc.Code_INVALID_ARGUMENT, "keyspace %v is not locked (no locksInfo)", keyspace) 206 } 207 i.mu.Lock() 208 defer i.mu.Unlock() 209 210 // find the individual entry 211 _, ok = i.info[keyspace] 212 if !ok { 213 return vterrors.Errorf(vtrpc.Code_INVALID_ARGUMENT, "keyspace %v is not locked (no lockInfo in map)", keyspace) 214 } 215 216 // TODO(alainjobart): check the lock server implementation 217 // still holds the lock. Will need to look at the lockInfo struct. 218 219 // and we're good for now. 220 return nil 221 } 222 223 // CheckKeyspaceLockedAndRenew can be called on a context to make sure we have the lock 224 // for a given keyspace. The function also attempts to renew the lock. 225 func CheckKeyspaceLockedAndRenew(ctx context.Context, keyspace string) error { 226 // extract the locksInfo pointer 227 i, ok := ctx.Value(locksKey).(*locksInfo) 228 if !ok { 229 return vterrors.Errorf(vtrpc.Code_INVALID_ARGUMENT, "keyspace %v is not locked (no locksInfo)", keyspace) 230 } 231 i.mu.Lock() 232 defer i.mu.Unlock() 233 234 // find the individual entry 235 entry, ok := i.info[keyspace] 236 if !ok { 237 return vterrors.Errorf(vtrpc.Code_INVALID_ARGUMENT, "keyspace %v is not locked (no lockInfo in map)", keyspace) 238 } 239 // try renewing lease: 240 return entry.lockDescriptor.Check(ctx) 241 } 242 243 // lockKeyspace will lock the keyspace in the topology server. 244 // unlockKeyspace should be called if this returns no error. 245 func (l *Lock) lockKeyspace(ctx context.Context, ts *Server, keyspace string) (LockDescriptor, error) { 246 log.Infof("Locking keyspace %v for action %v", keyspace, l.Action) 247 248 ctx, cancel := context.WithTimeout(ctx, getLockTimeout()) 249 defer cancel() 250 251 span, ctx := trace.NewSpan(ctx, "TopoServer.LockKeyspaceForAction") 252 span.Annotate("action", l.Action) 253 span.Annotate("keyspace", keyspace) 254 defer span.Finish() 255 256 keyspacePath := path.Join(KeyspacesPath, keyspace) 257 j, err := l.ToJSON() 258 if err != nil { 259 return nil, err 260 } 261 return ts.globalCell.Lock(ctx, keyspacePath, j) 262 } 263 264 // unlockKeyspace unlocks a previously locked keyspace. 265 func (l *Lock) unlockKeyspace(ctx context.Context, ts *Server, keyspace string, lockDescriptor LockDescriptor, actionError error) error { 266 // Detach from the parent timeout, but copy the trace span. 267 // We need to still release the lock even if the parent 268 // context timed out. 269 ctx = trace.CopySpan(context.TODO(), ctx) 270 ctx, cancel := context.WithTimeout(ctx, RemoteOperationTimeout) 271 defer cancel() 272 273 span, ctx := trace.NewSpan(ctx, "TopoServer.UnlockKeyspaceForAction") 274 span.Annotate("action", l.Action) 275 span.Annotate("keyspace", keyspace) 276 defer span.Finish() 277 278 // first update the actionNode 279 if actionError != nil { 280 log.Infof("Unlocking keyspace %v for action %v with error %v", keyspace, l.Action, actionError) 281 l.Status = "Error: " + actionError.Error() 282 } else { 283 log.Infof("Unlocking keyspace %v for successful action %v", keyspace, l.Action) 284 l.Status = "Done" 285 } 286 return lockDescriptor.Unlock(ctx) 287 } 288 289 // LockShard will lock the shard, and return: 290 // - a context with a locksInfo structure for future reference. 291 // - an unlock method 292 // - an error if anything failed. 293 // 294 // We are currently only using this method to lock actions that would 295 // impact each-other. Most changes of the Shard object are done by 296 // UpdateShardFields, which is not locking the shard object. The 297 // current list of actions that lock a shard are: 298 // * all Vitess-controlled re-parenting operations: 299 // - InitShardPrimary 300 // - PlannedReparentShard 301 // - EmergencyReparentShard 302 // 303 // * any vtorc recovery e.g 304 // - RecoverDeadPrimary 305 // - ElectNewPrimary 306 // - FixPrimary 307 // 308 // * before any replication repair from replication manager 309 // 310 // * operations that we don't want to conflict with re-parenting: 311 // - DeleteTablet when it's the shard's current primary 312 func (ts *Server) LockShard(ctx context.Context, keyspace, shard, action string) (context.Context, func(*error), error) { 313 return ts.internalLockShard(ctx, keyspace, shard, action, true) 314 } 315 316 // TryLockShard will lock the shard, and return: 317 // - a context with a locksInfo structure for future reference. 318 // - an unlock method 319 // - an error if anything failed. 320 // 321 // `TryLockShard` is different from `LockShard`. If there is already a lock on given shard, 322 // then unlike `LockShard` instead of waiting and blocking the client it returns with 323 // `Lock already exists` error. With current implementation it may not be able to fail-fast 324 // for some scenarios. For example there is a possibility that a thread checks for lock for 325 // a given shard but by the time it acquires the lock, some other thread has already acquired it, 326 // in this case the client will block until the other caller releases the lock or the 327 // client call times out (just like standard `LockShard' implementation). In short the lock checking 328 // and acquiring is not under the same mutex in current implementation of `TryLockShard`. 329 // 330 // We are currently using `TryLockShard` during tablet discovery in Vtorc recovery 331 func (ts *Server) TryLockShard(ctx context.Context, keyspace, shard, action string) (context.Context, func(*error), error) { 332 return ts.internalLockShard(ctx, keyspace, shard, action, false) 333 } 334 335 // isBlocking is used to indicate whether the call should fail-fast or not. 336 func (ts *Server) internalLockShard(ctx context.Context, keyspace, shard, action string, isBlocking bool) (context.Context, func(*error), error) { 337 i, ok := ctx.Value(locksKey).(*locksInfo) 338 if !ok { 339 i = &locksInfo{ 340 info: make(map[string]*lockInfo), 341 } 342 ctx = context.WithValue(ctx, locksKey, i) 343 } 344 i.mu.Lock() 345 defer i.mu.Unlock() 346 347 // check that we're not already locked 348 mapKey := keyspace + "/" + shard 349 if _, ok = i.info[mapKey]; ok { 350 return nil, nil, vterrors.Errorf(vtrpc.Code_INVALID_ARGUMENT, "lock for shard %v/%v is already held", keyspace, shard) 351 } 352 353 // lock 354 l := newLock(action) 355 var lockDescriptor LockDescriptor 356 var err error 357 if isBlocking { 358 lockDescriptor, err = l.lockShard(ctx, ts, keyspace, shard) 359 } else { 360 lockDescriptor, err = l.tryLockShard(ctx, ts, keyspace, shard) 361 } 362 if err != nil { 363 return nil, nil, err 364 } 365 366 // and update our structure 367 i.info[mapKey] = &lockInfo{ 368 lockDescriptor: lockDescriptor, 369 actionNode: l, 370 } 371 return ctx, func(finalErr *error) { 372 i.mu.Lock() 373 defer i.mu.Unlock() 374 375 if _, ok := i.info[mapKey]; !ok { 376 if *finalErr != nil { 377 log.Errorf("trying to unlock shard %v/%v multiple times", keyspace, shard) 378 } else { 379 *finalErr = vterrors.Errorf(vtrpc.Code_INTERNAL, "trying to unlock shard %v/%v multiple times", keyspace, shard) 380 } 381 return 382 } 383 384 err := l.unlockShard(ctx, ts, keyspace, shard, lockDescriptor, *finalErr) 385 if *finalErr != nil { 386 if err != nil { 387 // both error are set, just log the unlock error 388 log.Warningf("unlockShard(%s/%s) failed: %v", keyspace, shard, err) 389 } 390 } else { 391 *finalErr = err 392 } 393 delete(i.info, mapKey) 394 }, nil 395 } 396 397 // CheckShardLocked can be called on a context to make sure we have the lock 398 // for a given shard. 399 func CheckShardLocked(ctx context.Context, keyspace, shard string) error { 400 // extract the locksInfo pointer 401 i, ok := ctx.Value(locksKey).(*locksInfo) 402 if !ok { 403 return vterrors.Errorf(vtrpc.Code_INTERNAL, "shard %v/%v is not locked (no locksInfo)", keyspace, shard) 404 } 405 i.mu.Lock() 406 defer i.mu.Unlock() 407 408 // func the individual entry 409 mapKey := keyspace + "/" + shard 410 li, ok := i.info[mapKey] 411 if !ok { 412 return vterrors.Errorf(vtrpc.Code_INTERNAL, "shard %v/%v is not locked (no lockInfo in map)", keyspace, shard) 413 } 414 415 // Check the lock server implementation still holds the lock. 416 return li.lockDescriptor.Check(ctx) 417 } 418 419 // lockShard will lock the shard in the topology server. 420 // UnlockShard should be called if this returns no error. 421 func (l *Lock) lockShard(ctx context.Context, ts *Server, keyspace, shard string) (LockDescriptor, error) { 422 return l.internalLockShard(ctx, ts, keyspace, shard, true) 423 } 424 425 // tryLockShard will lock the shard in the topology server but unlike `lockShard` it fail-fast if not able to get lock 426 // UnlockShard should be called if this returns no error. 427 func (l *Lock) tryLockShard(ctx context.Context, ts *Server, keyspace, shard string) (LockDescriptor, error) { 428 return l.internalLockShard(ctx, ts, keyspace, shard, false) 429 } 430 431 func (l *Lock) internalLockShard(ctx context.Context, ts *Server, keyspace, shard string, isBlocking bool) (LockDescriptor, error) { 432 log.Infof("Locking shard %v/%v for action %v", keyspace, shard, l.Action) 433 434 ctx, cancel := context.WithTimeout(ctx, getLockTimeout()) 435 defer cancel() 436 437 span, ctx := trace.NewSpan(ctx, "TopoServer.LockShardForAction") 438 span.Annotate("action", l.Action) 439 span.Annotate("keyspace", keyspace) 440 span.Annotate("shard", shard) 441 defer span.Finish() 442 443 shardPath := path.Join(KeyspacesPath, keyspace, ShardsPath, shard) 444 j, err := l.ToJSON() 445 if err != nil { 446 return nil, err 447 } 448 if isBlocking { 449 return ts.globalCell.Lock(ctx, shardPath, j) 450 } 451 return ts.globalCell.TryLock(ctx, shardPath, j) 452 } 453 454 // unlockShard unlocks a previously locked shard. 455 func (l *Lock) unlockShard(ctx context.Context, ts *Server, keyspace, shard string, lockDescriptor LockDescriptor, actionError error) error { 456 // Detach from the parent timeout, but copy the trace span. 457 // We need to still release the lock even if the parent context timed out. 458 ctx = trace.CopySpan(context.TODO(), ctx) 459 ctx, cancel := context.WithTimeout(ctx, RemoteOperationTimeout) 460 defer cancel() 461 462 span, ctx := trace.NewSpan(ctx, "TopoServer.UnlockShardForAction") 463 span.Annotate("action", l.Action) 464 span.Annotate("keyspace", keyspace) 465 span.Annotate("shard", shard) 466 defer span.Finish() 467 468 // first update the actionNode 469 if actionError != nil { 470 log.Infof("Unlocking shard %v/%v for action %v with error %v", keyspace, shard, l.Action, actionError) 471 l.Status = "Error: " + actionError.Error() 472 } else { 473 log.Infof("Unlocking shard %v/%v for successful action %v", keyspace, shard, l.Action) 474 l.Status = "Done" 475 } 476 return lockDescriptor.Unlock(ctx) 477 } 478 479 // getLockTimeout is shim code used for backward compatibility with v15 480 // This code can be removed in v17+ and LockTimeout can be used directly 481 func getLockTimeout() time.Duration { 482 if _flag.IsFlagProvided("lock-timeout") { 483 return LockTimeout 484 } 485 if _flag.IsFlagProvided("remote_operation_timeout") { 486 return RemoteOperationTimeout 487 } 488 return LockTimeout 489 }