github.com/dolthub/go-mysql-server@v0.18.0/sql/lock_subsystem.go (about)

     1  // Copyright 2020-2021 Dolthub, 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  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package sql
    16  
    17  import (
    18  	"sync"
    19  	"sync/atomic"
    20  	"time"
    21  	"unsafe"
    22  
    23  	"gopkg.in/src-d/go-errors.v1"
    24  )
    25  
    26  // ErrLockTimeout is the kind of error returned when acquiring a lock takes longer than the user specified timeout
    27  var ErrLockTimeout = errors.NewKind("Timeout acquiring lock '%s'.")
    28  
    29  // ErrLockDoesNotExist is the kind of error returned when a named lock does not exist and the operation does not created it
    30  var ErrLockDoesNotExist = errors.NewKind("Lock '%s' does not exist.")
    31  
    32  // ErrLockNotOwned is the kind of error returned when attempting an operation against a lock that the given context does not own.
    33  var ErrLockNotOwned = errors.NewKind("Operation '%s' failed as the lock '%s' has a different owner.")
    34  
    35  type ownedLock struct {
    36  	Owner int64
    37  	Count int64
    38  }
    39  
    40  // LockSubsystem manages reentrant named locks
    41  type LockSubsystem struct {
    42  	lockLock *sync.RWMutex
    43  	locks    map[string]**ownedLock
    44  }
    45  
    46  // NewLockSubsystem creates a LockSubsystem object
    47  func NewLockSubsystem() *LockSubsystem {
    48  	return &LockSubsystem{&sync.RWMutex{}, make(map[string]**ownedLock)}
    49  }
    50  
    51  func (ls *LockSubsystem) getNamedLock(name string) **ownedLock {
    52  	ls.lockLock.RLock()
    53  	defer ls.lockLock.RUnlock()
    54  
    55  	return ls.locks[name]
    56  }
    57  
    58  func (ls *LockSubsystem) createLock(name string) **ownedLock {
    59  	ls.lockLock.Lock()
    60  	defer ls.lockLock.Unlock()
    61  
    62  	nl, ok := ls.locks[name]
    63  
    64  	if !ok {
    65  		newLock := &ownedLock{}
    66  		ls.locks[name] = &newLock
    67  		nl = &newLock
    68  	}
    69  
    70  	return nl
    71  }
    72  
    73  // Lock attempts to acquire a lock with a given name for the Id associated with the given ctx.Session within the given
    74  // timeout
    75  func (ls *LockSubsystem) Lock(ctx *Context, name string, timeout time.Duration) error {
    76  	nl := ls.getNamedLock(name)
    77  
    78  	if nl == nil {
    79  		nl = ls.createLock(name)
    80  	}
    81  
    82  	userId := int64(ctx.Session.ID())
    83  	for i, start := 0, time.Now(); i == 0 || timeout < 0 || time.Since(start) < timeout; i++ {
    84  		dest := (*unsafe.Pointer)(unsafe.Pointer(nl))
    85  		curr := atomic.LoadPointer(dest)
    86  		currLock := *(*ownedLock)(curr)
    87  
    88  		if currLock.Owner == 0 {
    89  			newVal := &ownedLock{userId, 1}
    90  			if atomic.CompareAndSwapPointer(dest, curr, unsafe.Pointer(newVal)) {
    91  				return ctx.Session.AddLock(name)
    92  			}
    93  		} else if currLock.Owner == userId {
    94  			newVal := &ownedLock{userId, currLock.Count + 1}
    95  			if atomic.CompareAndSwapPointer(dest, curr, unsafe.Pointer(newVal)) {
    96  				return nil
    97  			}
    98  		}
    99  
   100  		time.Sleep(100 * time.Microsecond)
   101  	}
   102  
   103  	return ErrLockTimeout.New(name)
   104  }
   105  
   106  // Unlock releases a lock with a given name for the ID associated with the given ctx.Session
   107  func (ls *LockSubsystem) Unlock(ctx *Context, name string) error {
   108  	nl := ls.getNamedLock(name)
   109  
   110  	if nl == nil {
   111  		return ErrLockDoesNotExist.New(name)
   112  	}
   113  
   114  	userId := int64(ctx.Session.ID())
   115  	for {
   116  		dest := (*unsafe.Pointer)(unsafe.Pointer(nl))
   117  		curr := atomic.LoadPointer(dest)
   118  		currLock := *(*ownedLock)(curr)
   119  
   120  		if currLock.Owner != userId {
   121  			return ErrLockNotOwned.New("unlock", name)
   122  		}
   123  
   124  		newVal := &ownedLock{}
   125  		if currLock.Count > 1 {
   126  			newVal = &ownedLock{userId, currLock.Count - 1}
   127  		}
   128  
   129  		if atomic.CompareAndSwapPointer(dest, curr, unsafe.Pointer(newVal)) {
   130  			if newVal.Count == 0 {
   131  				return ctx.Session.DelLock(name)
   132  			}
   133  
   134  			return nil
   135  		}
   136  	}
   137  }
   138  
   139  // ReleaseAll releases all locks the ID associated with the given ctx.Session, and returns the number of locks that were
   140  // succeessfully released.
   141  func (ls *LockSubsystem) ReleaseAll(ctx *Context) (int, error) {
   142  	releaseCount := 0
   143  	_ = ctx.Session.IterLocks(func(name string) error {
   144  		nl := ls.getNamedLock(name)
   145  
   146  		if nl != nil {
   147  			userId := ctx.Session.ID()
   148  			for {
   149  				dest := (*unsafe.Pointer)(unsafe.Pointer(nl))
   150  				curr := atomic.LoadPointer(dest)
   151  				currLock := *(*ownedLock)(curr)
   152  
   153  				if currLock.Owner != int64(userId) {
   154  					break
   155  				}
   156  
   157  				if atomic.CompareAndSwapPointer(dest, curr, unsafe.Pointer(&ownedLock{})) {
   158  					releaseCount++
   159  					break
   160  				}
   161  			}
   162  		}
   163  
   164  		return nil
   165  	})
   166  
   167  	return releaseCount, nil
   168  }
   169  
   170  // LockState represents the different states a lock can be in
   171  type LockState int
   172  
   173  const (
   174  	// LockDoesNotExist is the state where a lock has never been created
   175  	LockDoesNotExist LockState = iota
   176  	// LockInUse is the state where a lock has been acquired by a user
   177  	LockInUse
   178  	// LockFree is the state where a lock has been created, but is not currently owned by any user
   179  	LockFree
   180  )
   181  
   182  // GetLockState returns the LockState and owner ID for a lock with a given name.
   183  func (ls *LockSubsystem) GetLockState(name string) (state LockState, owner uint32) {
   184  	nl := ls.getNamedLock(name)
   185  
   186  	if nl == nil {
   187  		return LockDoesNotExist, 0
   188  	}
   189  
   190  	dest := (*unsafe.Pointer)(unsafe.Pointer(nl))
   191  	curr := atomic.LoadPointer(dest)
   192  	currLock := *(*ownedLock)(curr)
   193  
   194  	if currLock.Owner == 0 {
   195  		return LockFree, 0
   196  	} else {
   197  		return LockInUse, uint32(currLock.Owner)
   198  	}
   199  }