Skip to content

Commit 45cd1c9

Browse files
craig[bot]arulajmani
andcommitted
107550: concurrency: allow shared locking requests into the lock table r=nvanbenschoten a=arulajmani This patch extends the list of permissible locking strengths to include SHARED and tests conflict resolution for shared locks with other lock strengths. Note that (currently) the lock table doesn't support multiple locks from different transactions on a single key; as such, 2 transactions cannot simultaneously hold shared locks on a single key. This restriction will be lifted in the future. Release note: None Co-authored-by: Arul Ajmani <[email protected]>
2 parents 306f3ac + 292c54e commit 45cd1c9

File tree

3 files changed

+119
-6
lines changed

3 files changed

+119
-6
lines changed

pkg/kv/kvserver/concurrency/lock_table.go

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -764,23 +764,24 @@ func (g *lockTableGuardImpl) curStrength() lock.Strength {
764764
// request. The value returned by this method are mutable as the request's scan
765765
// of the lock table progresses from lock to lock.
766766
func (g *lockTableGuardImpl) curLockMode() lock.Mode {
767-
var reqMode lock.Mode
768767
switch g.curStrength() {
769768
case lock.None:
770769
iso := isolation.Serializable
771770
if g.txn != nil {
772771
iso = g.txn.IsoLevel
773772
}
774-
reqMode = lock.MakeModeNone(g.ts, iso)
773+
return lock.MakeModeNone(g.ts, iso)
774+
case lock.Shared:
775+
assert(g.txn != nil, "only transactional requests can acquire shared locks")
776+
return lock.MakeModeShared()
775777
case lock.Exclusive:
776778
assert(g.txn != nil, "only transactional requests can acquire exclusive locks")
777-
reqMode = lock.MakeModeExclusive(g.ts, g.txn.IsoLevel)
779+
return lock.MakeModeExclusive(g.ts, g.txn.IsoLevel)
778780
case lock.Intent:
779-
reqMode = lock.MakeModeIntent(g.ts)
781+
return lock.MakeModeIntent(g.ts)
780782
default:
781783
panic(fmt.Sprintf("unhandled request strength: %s", g.curStrength()))
782784
}
783-
return reqMode
784785
}
785786

786787
// takeToResolveUnreplicated returns the list of unreplicated locks accumulated
@@ -1856,7 +1857,7 @@ func (l *lockState) getLockMode() lock.Mode {
18561857
case lock.Exclusive:
18571858
return lock.MakeModeExclusive(lockHolderTS, lockHolderTxn.IsoLevel)
18581859
case lock.Shared:
1859-
panic(fmt.Sprintf("unexpected lock strength %s", str))
1860+
return lock.MakeModeShared()
18601861
default:
18611862
panic(fmt.Sprintf("unexpected lock strength %s", str))
18621863
}
@@ -3423,6 +3424,8 @@ func (t *lockTableImpl) AcquireLock(acq *roachpb.LockAcquisition) error {
34233424
assert(acq.Durability == lock.Replicated, "incorrect durability")
34243425
case lock.Exclusive:
34253426
assert(acq.Durability == lock.Unreplicated, "incorrect durability")
3427+
case lock.Shared:
3428+
assert(acq.Durability == lock.Unreplicated, "incorrect durability")
34263429
default:
34273430
return errors.AssertionFailedf("unsupported lock strength %s", acq.Strength)
34283431
}

pkg/kv/kvserver/concurrency/lock_table_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,13 @@ func scanSpans(
713713
sa = spanset.SpanReadWrite
714714
case lock.Exclusive:
715715
sa = spanset.SpanReadWrite
716+
case lock.Shared:
717+
// Unlike non-locking reads, shared-locking reads are isolated at all
718+
// timestamps (not just the request's timestamp); so we acquire a read
719+
// latch at max timestamp. See
720+
// https://github.com/cockroachdb/cockroach/issues/102264.
721+
sa = spanset.SpanReadOnly
722+
ts = hlc.MaxTimestamp
716723
default:
717724
d.Fatalf(t, "unsupported lock strength: %s", str)
718725
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
new-lock-table maxlocks=10000
2+
----
3+
4+
new-txn txn=txn1 ts=10 epoch=0 seq=0
5+
----
6+
7+
new-txn txn=txn2 ts=10 epoch=0 seq=0
8+
----
9+
10+
new-request r=req1 txn=txn1 ts=10 spans=shared@a
11+
----
12+
13+
acquire r=req1 k=a durability=u strength=shared
14+
----
15+
num=1
16+
lock: "a"
17+
holder: txn: 00000000-0000-0000-0000-000000000001 epoch: 0, iso: Serializable, ts: 10.000000000,0, info: unrepl [(str: Shared seq: 0)]
18+
19+
# ------------------------------------------------------------------------------
20+
# Ensure conflict resolution semantics for shared locks are sane -- that is,
21+
# if a shared lock is held on a key, {shared, non} locking requests are allowed
22+
# to proceed; {intent, exclusive} locking requests are not.
23+
# ------------------------------------------------------------------------------
24+
25+
new-request r=req2 txn=txn2 ts=10 spans=none@a
26+
----
27+
28+
scan r=req2
29+
----
30+
start-waiting: false
31+
32+
new-request r=req3 txn=txn2 ts=10 spans=shared@a
33+
----
34+
35+
# req3 should not actively wait, as it's locking strength is shared, but it
36+
# should be able to acquire a joint claim.
37+
scan r=req3
38+
----
39+
start-waiting: false
40+
41+
print
42+
----
43+
num=1
44+
lock: "a"
45+
holder: txn: 00000000-0000-0000-0000-000000000001 epoch: 0, iso: Serializable, ts: 10.000000000,0, info: unrepl [(str: Shared seq: 0)]
46+
queued writers:
47+
active: false req: 2, txn: 00000000-0000-0000-0000-000000000002
48+
49+
# TODO(arul): This is currently a limitation of the lock table, as it doesn't
50+
# allow multiple locks on a single key from different transactions.
51+
acquire r=req3 k=a durability=u strength=shared
52+
----
53+
existing lock cannot be acquired by different transaction
54+
55+
new-request r=req4 txn=txn2 ts=10 spans=exclusive@a
56+
----
57+
58+
scan r=req4
59+
----
60+
start-waiting: true
61+
62+
new-request r=req5 txn=txn2 ts=10 spans=intent@a
63+
----
64+
65+
scan r=req5
66+
----
67+
start-waiting: true
68+
69+
print
70+
----
71+
num=1
72+
lock: "a"
73+
holder: txn: 00000000-0000-0000-0000-000000000001 epoch: 0, iso: Serializable, ts: 10.000000000,0, info: unrepl [(str: Shared seq: 0)]
74+
queued writers:
75+
active: false req: 2, txn: 00000000-0000-0000-0000-000000000002
76+
active: true req: 3, txn: 00000000-0000-0000-0000-000000000002
77+
active: true req: 4, txn: 00000000-0000-0000-0000-000000000002
78+
distinguished req: 3
79+
80+
# ------------------------------------------------------------------------------
81+
# Ensure requests with locking strength shared actively wait if there are active
82+
# waiters with conflicting lock strengths (even though the lock itself is
83+
# compatible with the shared lock request).
84+
# ------------------------------------------------------------------------------
85+
86+
new-request r=req6 txn=txn2 ts=10 spans=shared@a
87+
----
88+
89+
scan r=req6
90+
----
91+
start-waiting: true
92+
93+
print
94+
----
95+
num=1
96+
lock: "a"
97+
holder: txn: 00000000-0000-0000-0000-000000000001 epoch: 0, iso: Serializable, ts: 10.000000000,0, info: unrepl [(str: Shared seq: 0)]
98+
queued writers:
99+
active: false req: 2, txn: 00000000-0000-0000-0000-000000000002
100+
active: true req: 3, txn: 00000000-0000-0000-0000-000000000002
101+
active: true req: 4, txn: 00000000-0000-0000-0000-000000000002
102+
active: true req: 5, txn: 00000000-0000-0000-0000-000000000002
103+
distinguished req: 3

0 commit comments

Comments
 (0)