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  }