Skip to content

Commit 90ba1ef

Browse files
committed
concurrency: use lock modes to detect conflicts during optimistic eval
This patch switches over conflict resolution performed by optimistic evaluation to use lock modes instead of ad-hoc logic. As a result of this, optimistic evaluation is able to handle shared locks. We add a test to show this. Closes cockroachdb#108142 Release note: None
1 parent dc28e19 commit 90ba1ef

File tree

2 files changed

+146
-15
lines changed

2 files changed

+146
-15
lines changed

pkg/kv/kvserver/concurrency/lock_table.go

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -667,7 +667,7 @@ func (g *lockTableGuardImpl) CheckOptimisticNoConflicts(
667667
ltRange := &lockState{key: startKey, endKey: span.EndKey}
668668
for iter.FirstOverlap(ltRange); iter.Valid(); iter.NextOverlap(ltRange) {
669669
l := iter.Cur()
670-
if !l.isNonConflictingLock(g, g.curStrength()) {
670+
if !l.isNonConflictingLock(g) {
671671
return false
672672
}
673673
}
@@ -2359,7 +2359,7 @@ func (l *lockState) claimBeforeProceeding(g *lockTableGuardImpl) {
23592359
panic("lock table bug: did not find enqueued request")
23602360
}
23612361

2362-
func (l *lockState) isNonConflictingLock(g *lockTableGuardImpl, str lock.Strength) bool {
2362+
func (l *lockState) isNonConflictingLock(g *lockTableGuardImpl) bool {
23632363
l.mu.Lock()
23642364
defer l.mu.Unlock()
23652365

@@ -2368,13 +2368,12 @@ func (l *lockState) isNonConflictingLock(g *lockTableGuardImpl, str lock.Strengt
23682368
return true
23692369
}
23702370
// Lock is not empty.
2371-
lockHolderTxn, lockHolderTS := l.getLockHolder()
2372-
if lockHolderTxn == nil {
2373-
// Transactions that have claimed the lock, but have not acquired it yet,
2374-
// are considered non-conflicting.
2375-
//
2376-
// Optimistic evaluation may call into this function with or without holding
2377-
// latches. It's worth considering both these cases separately:
2371+
if !l.isHeld() {
2372+
// If the lock is neither empty nor held it must be the case that another
2373+
// transaction has claimed the lock. Locks that have been claimed, but have
2374+
// not been acquired yet, are considered non-conflicting. Optimistic
2375+
// evaluation may call into this function with or without holding latches.
2376+
// It's worth considering both these cases separately:
23782377
//
23792378
// 1. If Optimistic evaluation is holding latches, then there cannot be a
23802379
// conflicting request that has claimed (but not acquired) the lock that is
@@ -2397,19 +2396,29 @@ func (l *lockState) isNonConflictingLock(g *lockTableGuardImpl, str lock.Strengt
23972396
// claimed the lock will know what happened and what to do about it.
23982397
return true
23992398
}
2399+
lockHolderTxn, _ := l.getLockHolder()
24002400
if g.isSameTxn(lockHolderTxn) {
2401-
// Already locked by this txn.
2401+
// NB: Unlike the pessimistic (normal) evaluation code path, we do not need
2402+
// to check the lock's strength if it is already held by this transaction --
2403+
// it's non-conflicting. There's two cases to consider:
2404+
//
2405+
// 1. If the lock is held with the same/higher lock strength on this key
2406+
// then this optimistic evaluation attempt already has all the protection it
2407+
// needs.
2408+
//
2409+
// 2. If the lock is held with a weaker lock strength other transactions may
2410+
// be able to acquire a lock on this key that conflicts with this optimistic
2411+
// evaluation attempt. This is okay, as we'll detect such cases -- however,
2412+
// the weaker lock in itself is not conflicting with the optimistic
2413+
// evaluation attempt.
24022414
return true
24032415
}
2416+
24042417
// NB: We do not look at the txnStatusCache in this optimistic evaluation
24052418
// path. A conflict with a finalized txn will be noticed when retrying
24062419
// pessimistically.
24072420

2408-
if str == lock.None && g.ts.Less(lockHolderTS) {
2409-
return true
2410-
}
2411-
// Conflicts.
2412-
return false
2421+
return !lock.Conflicts(l.getLockMode(), g.curLockMode(), &g.lt.settings.SV) // non-conflicting
24132422
}
24142423

24152424
// Acquires this lock. Any requests that are waiting in the lock's wait queues

pkg/kv/kvserver/concurrency/testdata/lock_table/optimistic

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,3 +132,125 @@ num=2
132132
holder: txn: 00000000-0000-0000-0000-000000000001 epoch: 0, iso: Serializable, ts: 10.000000000,1, info: unrepl [(str: Exclusive seq: 0)]
133133
lock: "g"
134134
holder: txn: 00000000-0000-0000-0000-000000000001 epoch: 0, iso: Serializable, ts: 10.000000000,1, info: unrepl [(str: Exclusive seq: 0)]
135+
136+
137+
# ------------------------------------------------------------------------------
138+
# Test that optimistic evaluation succeeds if the lock is held by our own
139+
# transaction, regardless of lock strengths.
140+
# ------------------------------------------------------------------------------
141+
142+
clear
143+
----
144+
num=0
145+
146+
new-request r=req5 txn=txn1 ts=10,1 spans=shared@a
147+
----
148+
149+
scan r=req5
150+
----
151+
start-waiting: false
152+
153+
should-wait r=req5
154+
----
155+
false
156+
157+
acquire r=req5 k=a durability=u strength=shared
158+
----
159+
num=1
160+
lock: "a"
161+
holder: txn: 00000000-0000-0000-0000-000000000001 epoch: 0, iso: Serializable, ts: 10.000000000,1, info: unrepl [(str: Shared seq: 0)]
162+
163+
# Ensure a optimistic evaluation attempt from the same transaction that covers
164+
# key "a" succeeds -- both with lower and higher lock strengths than the
165+
# strength of the lock already held (shared).
166+
167+
new-request r=req6 txn=txn1 ts=10,1 spans=exclusive@a,c
168+
----
169+
170+
scan-opt r=req6
171+
----
172+
start-waiting: false
173+
174+
should-wait r=req6
175+
----
176+
false
177+
178+
check-opt-no-conflicts r=req6 spans=exclusive@a,c
179+
----
180+
no-conflicts: true
181+
182+
dequeue r=req6
183+
----
184+
num=1
185+
lock: "a"
186+
holder: txn: 00000000-0000-0000-0000-000000000001 epoch: 0, iso: Serializable, ts: 10.000000000,1, info: unrepl [(str: Shared seq: 0)]
187+
188+
new-request r=req7 txn=txn1 ts=10,1 spans=none@a,c
189+
----
190+
191+
scan-opt r=req7
192+
----
193+
start-waiting: false
194+
195+
should-wait r=req7
196+
----
197+
false
198+
199+
check-opt-no-conflicts r=req7 spans=none@a,c
200+
----
201+
no-conflicts: true
202+
203+
dequeue r=req7
204+
----
205+
num=1
206+
lock: "a"
207+
holder: txn: 00000000-0000-0000-0000-000000000001 epoch: 0, iso: Serializable, ts: 10.000000000,1, info: unrepl [(str: Shared seq: 0)]
208+
209+
# ------------------------------------------------------------------------------
210+
# Test that optimistic evaluation works with SHARED locking strength -- if a
211+
# shared lock is held, another transaction should be able to perform optimistic
212+
# evaluation with shared locking strength and not conflict; optimistic evaluation
213+
# should conflict if run with exclusive lock strength.
214+
# ------------------------------------------------------------------------------
215+
216+
new-request r=req8 txn=txn2 ts=10,1 spans=none@a,c
217+
----
218+
219+
scan-opt r=req8
220+
----
221+
start-waiting: false
222+
223+
should-wait r=req8
224+
----
225+
false
226+
227+
check-opt-no-conflicts r=req8 spans=shared@a,c
228+
----
229+
no-conflicts: true
230+
231+
dequeue r=req8
232+
----
233+
num=1
234+
lock: "a"
235+
holder: txn: 00000000-0000-0000-0000-000000000001 epoch: 0, iso: Serializable, ts: 10.000000000,1, info: unrepl [(str: Shared seq: 0)]
236+
237+
new-request r=req9 txn=txn2 ts=10,1 spans=exclusive@a,c
238+
----
239+
240+
scan-opt r=req9
241+
----
242+
start-waiting: false
243+
244+
should-wait r=req9
245+
----
246+
false
247+
248+
check-opt-no-conflicts r=req9 spans=exclusive@a,c
249+
----
250+
no-conflicts: false
251+
252+
dequeue r=req9
253+
----
254+
num=1
255+
lock: "a"
256+
holder: txn: 00000000-0000-0000-0000-000000000001 epoch: 0, iso: Serializable, ts: 10.000000000,1, info: unrepl [(str: Shared seq: 0)]

0 commit comments

Comments
 (0)