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 }