Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit5246d3e

Browse files
committed
Avoid spurious deadlocks when upgrading a tuple lock
This puts back reverted commitde87a08, with some bug fixes.When two (or more) transactions are waiting for transaction T1 to release atuple-level lock, and transaction T1 upgrades its lock to a higher level, aspurious deadlock can be reported among the waiting transactions when T1finishes. The simplest example case seems to be:T1: select id from job where name = 'a' for key share;Y: select id from job where name = 'a' for update; -- starts waiting for T1Z: select id from job where name = 'a' for key share;T1: update job set name = 'b' where id = 1;Z: update job set name = 'c' where id = 1; -- starts waiting for T1T1: rollback;At this point, transaction Y is rolled back on account of a deadlock: Yholds the heavyweight tuple lock and is waiting for the Xmax to be released,while Z holds part of the multixact and tries to acquire the heavyweightlock (per protocol) and goes to sleep; once T1 releases its part of themultixact, Z is awakened only to be put back to sleep on the heavyweightlock that Y is holding while sleeping. Kaboom.This can be avoided by having Z skip the heavyweight lock acquisition. Asfar as I can see, the biggest downside is that if there are multiple Ztransactions, the order in which they resume after T1 finishes is notguaranteed.Backpatch to 9.6. The patch applies cleanly on 9.5, but the new tests don'twork there (because isolationtester is not smart enough), so I'm not goingto risk it.Author: Oleksii KliukinDiscussion:https://postgr.es/m/B9C9D7CD-EB94-4635-91B6-E558ACEC0EC3@hintbits.comDiscussion:https://postgr.es/m/2815.1560521451@sss.pgh.pa.us
1 parent14d8b53 commit5246d3e

File tree

5 files changed

+340
-21
lines changed

5 files changed

+340
-21
lines changed

‎src/backend/access/heap/README.tuplock

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,16 @@ do LockTuple as well, if there is any conflict, to ensure that they don't
3636
starve out waiting exclusive-lockers. However, if there is not any active
3737
conflict for a tuple, we don't incur any extra overhead.
3838

39+
We make an exception to the above rule for those lockers that already hold
40+
some lock on a tuple and attempt to acquire a stronger one on it. In that
41+
case, we skip the LockTuple() call even when there are conflicts, provided
42+
that the target tuple is being locked, updated or deleted by multiple sessions
43+
concurrently. Failing to skip the lock would risk a deadlock, e.g., between a
44+
session that was first to record its weaker lock in the tuple header and would
45+
be waiting on the LockTuple() call to upgrade to the stronger lock level, and
46+
another session that has already done LockTuple() and is waiting for the first
47+
session transaction to release its tuple header-level lock.
48+
3949
We provide four levels of tuple locking strength: SELECT FOR UPDATE obtains an
4050
exclusive lock which prevents any kind of modification of the tuple. This is
4151
the lock level that is implicitly taken by DELETE operations, and also by

‎src/backend/access/heap/heapam.c

Lines changed: 62 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ static void GetMultiXactIdHintBits(MultiXactId multi, uint16 *new_infomask,
120120
staticTransactionIdMultiXactIdGetUpdateXid(TransactionIdxmax,
121121
uint16t_infomask);
122122
staticboolDoesMultiXactIdConflict(MultiXactIdmulti,uint16infomask,
123-
LockTupleModelockmode);
123+
LockTupleModelockmode,bool*current_is_member);
124124
staticvoidMultiXactIdWait(MultiXactIdmulti,MultiXactStatusstatus,uint16infomask,
125125
Relationrel,ItemPointerctid,XLTW_Operoper,
126126
int*remaining);
@@ -3161,15 +3161,20 @@ heap_delete(Relation relation, ItemPointer tid,
31613161
*/
31623162
if (infomask&HEAP_XMAX_IS_MULTI)
31633163
{
3164-
/* wait for multixact */
3164+
boolcurrent_is_member= false;
3165+
31653166
if (DoesMultiXactIdConflict((MultiXactId)xwait,infomask,
3166-
LockTupleExclusive))
3167+
LockTupleExclusive,&current_is_member))
31673168
{
31683169
LockBuffer(buffer,BUFFER_LOCK_UNLOCK);
31693170

3170-
/* acquire tuple lock, if necessary */
3171-
heap_acquire_tuplock(relation,&(tp.t_self),LockTupleExclusive,
3172-
LockWaitBlock,&have_tuple_lock);
3171+
/*
3172+
* Acquire the lock, if necessary (but skip it when we're
3173+
* requesting a lock and already have one; avoids deadlock).
3174+
*/
3175+
if (!current_is_member)
3176+
heap_acquire_tuplock(relation,&(tp.t_self),LockTupleExclusive,
3177+
LockWaitBlock,&have_tuple_lock);
31733178

31743179
/* wait for multixact */
31753180
MultiXactIdWait((MultiXactId)xwait,MultiXactStatusUpdate,infomask,
@@ -3768,15 +3773,20 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
37683773
{
37693774
TransactionIdupdate_xact;
37703775
intremain;
3776+
boolcurrent_is_member= false;
37713777

37723778
if (DoesMultiXactIdConflict((MultiXactId)xwait,infomask,
3773-
*lockmode))
3779+
*lockmode,&current_is_member))
37743780
{
37753781
LockBuffer(buffer,BUFFER_LOCK_UNLOCK);
37763782

3777-
/* acquire tuple lock, if necessary */
3778-
heap_acquire_tuplock(relation,&(oldtup.t_self),*lockmode,
3779-
LockWaitBlock,&have_tuple_lock);
3783+
/*
3784+
* Acquire the lock, if necessary (but skip it when we're
3785+
* requesting a lock and already have one; avoids deadlock).
3786+
*/
3787+
if (!current_is_member)
3788+
heap_acquire_tuplock(relation,&(oldtup.t_self),*lockmode,
3789+
LockWaitBlock,&have_tuple_lock);
37803790

37813791
/* wait for multixact */
37823792
MultiXactIdWait((MultiXactId)xwait,mxact_status,infomask,
@@ -4701,6 +4711,7 @@ heap_lock_tuple(Relation relation, HeapTuple tuple,
47014711
new_infomask,
47024712
new_infomask2;
47034713
boolfirst_time= true;
4714+
boolskip_tuple_lock= false;
47044715
boolhave_tuple_lock= false;
47054716
boolcleared_all_frozen= false;
47064717

@@ -4799,6 +4810,21 @@ heap_lock_tuple(Relation relation, HeapTuple tuple,
47994810
result=HeapTupleMayBeUpdated;
48004811
gotoout_unlocked;
48014812
}
4813+
else
4814+
{
4815+
/*
4816+
* Disable acquisition of the heavyweight tuple lock.
4817+
* Otherwise, when promoting a weaker lock, we might
4818+
* deadlock with another locker that has acquired the
4819+
* heavyweight tuple lock and is waiting for our
4820+
* transaction to finish.
4821+
*
4822+
* Note that in this case we still need to wait for
4823+
* the multixact if required, to avoid acquiring
4824+
* conflicting locks.
4825+
*/
4826+
skip_tuple_lock= true;
4827+
}
48024828
}
48034829

48044830
if (members)
@@ -4953,7 +4979,7 @@ heap_lock_tuple(Relation relation, HeapTuple tuple,
49534979
if (infomask&HEAP_XMAX_IS_MULTI)
49544980
{
49554981
if (!DoesMultiXactIdConflict((MultiXactId)xwait,infomask,
4956-
mode))
4982+
mode,NULL))
49574983
{
49584984
/*
49594985
* No conflict, but if the xmax changed under us in the
@@ -5030,13 +5056,15 @@ heap_lock_tuple(Relation relation, HeapTuple tuple,
50305056
/*
50315057
* Acquire tuple lock to establish our priority for the tuple, or
50325058
* die trying. LockTuple will release us when we are next-in-line
5033-
* for the tuple. We must do this even if we are share-locking.
5059+
* for the tuple. We must do this even if we are share-locking,
5060+
* but not if we already have a weaker lock on the tuple.
50345061
*
50355062
* If we are forced to "start over" below, we keep the tuple lock;
50365063
* this arranges that we stay at the head of the line while
50375064
* rechecking tuple state.
50385065
*/
5039-
if (!heap_acquire_tuplock(relation,tid,mode,wait_policy,
5066+
if (!skip_tuple_lock&&
5067+
!heap_acquire_tuplock(relation,tid,mode,wait_policy,
50405068
&have_tuple_lock))
50415069
{
50425070
/*
@@ -7214,10 +7242,13 @@ HeapTupleGetUpdateXid(HeapTupleHeader tuple)
72147242
* tuple lock of the given strength?
72157243
*
72167244
* The passed infomask pairs up with the given multixact in the tuple header.
7245+
*
7246+
* If current_is_member is not NULL, it is set to 'true' if the current
7247+
* transaction is a member of the given multixact.
72177248
*/
72187249
staticbool
72197250
DoesMultiXactIdConflict(MultiXactIdmulti,uint16infomask,
7220-
LockTupleModelockmode)
7251+
LockTupleModelockmode,bool*current_is_member)
72217252
{
72227253
intnmembers;
72237254
MultiXactMember*members;
@@ -7238,15 +7269,24 @@ DoesMultiXactIdConflict(MultiXactId multi, uint16 infomask,
72387269
TransactionIdmemxid;
72397270
LOCKMODEmemlockmode;
72407271

7241-
memlockmode=LOCKMODE_from_mxstatus(members[i].status);
7272+
if (result&& (current_is_member==NULL||*current_is_member))
7273+
break;
72427274

7243-
/* ignore members that don't conflict with the lock we want */
7244-
if (!DoLockModesConflict(memlockmode,wanted))
7245-
continue;
7275+
memlockmode=LOCKMODE_from_mxstatus(members[i].status);
72467276

7247-
/* ignore members from current xact */
7277+
/* ignore members from current xact(but track their presence)*/
72487278
memxid=members[i].xid;
72497279
if (TransactionIdIsCurrentTransactionId(memxid))
7280+
{
7281+
if (current_is_member!=NULL)
7282+
*current_is_member= true;
7283+
continue;
7284+
}
7285+
elseif (result)
7286+
continue;
7287+
7288+
/* ignore members that don't conflict with the lock we want */
7289+
if (!DoLockModesConflict(memlockmode,wanted))
72507290
continue;
72517291

72527292
if (ISUPDATE_from_mxstatus(members[i].status))
@@ -7265,10 +7305,11 @@ DoesMultiXactIdConflict(MultiXactId multi, uint16 infomask,
72657305
/*
72667306
* Whatever remains are either live lockers that conflict with our
72677307
* wanted lock, and updaters that are not aborted. Those conflict
7268-
* with what we want, so return true.
7308+
* with what we want. Set up to return true, but keep going to
7309+
* look for the current transaction among the multixact members,
7310+
* if needed.
72697311
*/
72707312
result= true;
7271-
break;
72727313
}
72737314
pfree(members);
72747315
}
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
Parsed test spec with 4 sessions
2+
3+
starting permutation: s1_share s2_for_update s3_share s3_for_update s1_rollback s3_rollback s2_rollback
4+
step s1_share: select id from tlu_job where id = 1 for share;
5+
id
6+
7+
1
8+
step s2_for_update: select id from tlu_job where id = 1 for update; <waiting ...>
9+
step s3_share: select id from tlu_job where id = 1 for share;
10+
id
11+
12+
1
13+
step s3_for_update: select id from tlu_job where id = 1 for update; <waiting ...>
14+
step s1_rollback: rollback;
15+
step s3_for_update: <... completed>
16+
id
17+
18+
1
19+
step s3_rollback: rollback;
20+
step s2_for_update: <... completed>
21+
id
22+
23+
1
24+
step s2_rollback: rollback;
25+
26+
starting permutation: s1_keyshare s2_for_update s3_keyshare s1_update s3_update s1_rollback s3_rollback s2_rollback
27+
step s1_keyshare: select id from tlu_job where id = 1 for key share;
28+
id
29+
30+
1
31+
step s2_for_update: select id from tlu_job where id = 1 for update; <waiting ...>
32+
step s3_keyshare: select id from tlu_job where id = 1 for key share;
33+
id
34+
35+
1
36+
step s1_update: update tlu_job set name = 'b' where id = 1;
37+
step s3_update: update tlu_job set name = 'c' where id = 1; <waiting ...>
38+
step s1_rollback: rollback;
39+
step s3_update: <... completed>
40+
step s3_rollback: rollback;
41+
step s2_for_update: <... completed>
42+
id
43+
44+
1
45+
step s2_rollback: rollback;
46+
47+
starting permutation: s1_keyshare s2_for_update s3_keyshare s1_update s3_update s1_commit s3_rollback s2_rollback
48+
step s1_keyshare: select id from tlu_job where id = 1 for key share;
49+
id
50+
51+
1
52+
step s2_for_update: select id from tlu_job where id = 1 for update; <waiting ...>
53+
step s3_keyshare: select id from tlu_job where id = 1 for key share;
54+
id
55+
56+
1
57+
step s1_update: update tlu_job set name = 'b' where id = 1;
58+
step s3_update: update tlu_job set name = 'c' where id = 1; <waiting ...>
59+
step s1_commit: commit;
60+
step s3_update: <... completed>
61+
step s3_rollback: rollback;
62+
step s2_for_update: <... completed>
63+
id
64+
65+
1
66+
step s2_rollback: rollback;
67+
68+
starting permutation: s1_keyshare s2_for_update s3_keyshare s3_delete s1_rollback s3_rollback s2_rollback
69+
step s1_keyshare: select id from tlu_job where id = 1 for key share;
70+
id
71+
72+
1
73+
step s2_for_update: select id from tlu_job where id = 1 for update; <waiting ...>
74+
step s3_keyshare: select id from tlu_job where id = 1 for key share;
75+
id
76+
77+
1
78+
step s3_delete: delete from tlu_job where id = 1; <waiting ...>
79+
step s1_rollback: rollback;
80+
step s3_delete: <... completed>
81+
step s3_rollback: rollback;
82+
step s2_for_update: <... completed>
83+
id
84+
85+
1
86+
step s2_rollback: rollback;
87+
88+
starting permutation: s1_keyshare s2_for_update s3_keyshare s3_delete s1_rollback s3_commit s2_rollback
89+
step s1_keyshare: select id from tlu_job where id = 1 for key share;
90+
id
91+
92+
1
93+
step s2_for_update: select id from tlu_job where id = 1 for update; <waiting ...>
94+
step s3_keyshare: select id from tlu_job where id = 1 for key share;
95+
id
96+
97+
1
98+
step s3_delete: delete from tlu_job where id = 1; <waiting ...>
99+
step s1_rollback: rollback;
100+
step s3_delete: <... completed>
101+
step s3_commit: commit;
102+
step s2_for_update: <... completed>
103+
id
104+
105+
step s2_rollback: rollback;
106+
107+
starting permutation: s1_share s2_for_update s3_for_update s1_rollback s2_rollback s3_rollback
108+
step s1_share: select id from tlu_job where id = 1 for share;
109+
id
110+
111+
1
112+
step s2_for_update: select id from tlu_job where id = 1 for update; <waiting ...>
113+
step s3_for_update: select id from tlu_job where id = 1 for update; <waiting ...>
114+
step s1_rollback: rollback;
115+
step s2_for_update: <... completed>
116+
id
117+
118+
1
119+
step s2_rollback: rollback;
120+
step s3_for_update: <... completed>
121+
id
122+
123+
1
124+
step s3_rollback: rollback;
125+
126+
starting permutation: s1_share s2_update s3_update s1_rollback s2_rollback s3_rollback
127+
step s1_share: select id from tlu_job where id = 1 for share;
128+
id
129+
130+
1
131+
step s2_update: update tlu_job set name = 'b' where id = 1; <waiting ...>
132+
step s3_update: update tlu_job set name = 'c' where id = 1; <waiting ...>
133+
step s1_rollback: rollback;
134+
step s2_update: <... completed>
135+
step s2_rollback: rollback;
136+
step s3_update: <... completed>
137+
step s3_rollback: rollback;
138+
139+
starting permutation: s1_share s2_delete s3_delete s1_rollback s2_rollback s3_rollback
140+
step s1_share: select id from tlu_job where id = 1 for share;
141+
id
142+
143+
1
144+
step s2_delete: delete from tlu_job where id = 1; <waiting ...>
145+
step s3_delete: delete from tlu_job where id = 1; <waiting ...>
146+
step s1_rollback: rollback;
147+
step s2_delete: <... completed>
148+
step s2_rollback: rollback;
149+
step s3_delete: <... completed>
150+
step s3_rollback: rollback;
151+
152+
starting permutation: s1_keyshare s3_for_update s2_for_keyshare s1_savept_e s1_share s1_savept_f s1_fornokeyupd s2_fornokeyupd s0_begin s0_keyshare s1_rollback_f s0_keyshare s1_rollback_e s1_rollback s2_rollback s0_rollback s3_rollback
153+
step s1_keyshare: select id from tlu_job where id = 1 for key share;
154+
id
155+
156+
1
157+
step s3_for_update: select id from tlu_job where id = 1 for update; <waiting ...>
158+
step s2_for_keyshare: select id from tlu_job where id = 1 for key share;
159+
id
160+
161+
1
162+
step s1_savept_e: savepoint s1_e;
163+
step s1_share: select id from tlu_job where id = 1 for share;
164+
id
165+
166+
1
167+
step s1_savept_f: savepoint s1_f;
168+
step s1_fornokeyupd: select id from tlu_job where id = 1 for no key update;
169+
id
170+
171+
1
172+
step s2_fornokeyupd: select id from tlu_job where id = 1 for no key update; <waiting ...>
173+
step s0_begin: begin;
174+
step s0_keyshare: select id from tlu_job where id = 1 for key share;
175+
id
176+
177+
1
178+
step s1_rollback_f: rollback to s1_f;
179+
step s0_keyshare: select id from tlu_job where id = 1 for key share;
180+
id
181+
182+
1
183+
step s1_rollback_e: rollback to s1_e;
184+
step s2_fornokeyupd: <... completed>
185+
id
186+
187+
1
188+
step s1_rollback: rollback;
189+
step s2_rollback: rollback;
190+
step s0_rollback: rollback;
191+
step s3_for_update: <... completed>
192+
id
193+
194+
1
195+
step s3_rollback: rollback;

‎src/test/isolation/isolation_schedule

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ test: update-locked-tuple
4646
test: propagate-lock-delete
4747
test: tuplelock-conflict
4848
test: tuplelock-update
49+
test: tuplelock-upgrade-no-deadlock
4950
test: freeze-the-dead
5051
test: nowait
5152
test: nowait-2

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp