Skip to content

Commit 676c603

Browse files
committed
Avoid testing tuple visibility without buffer lock in RI_FKey_check().
Despite the argumentation I wrote in commit 7a2fe85, it's unsafe to do this, because in corner cases it's possible for HeapTupleSatisfiesSelf to try to set hint bits on the target tuple; and at least since 8.2 we have required the buffer content lock to be held while setting hint bits. The added regression test exercises one such corner case. Unpatched, it causes an assertion failure in assert-enabled builds, or otherwise would cause a hint bit change in a buffer we don't hold lock on, which given the right race condition could result in checksum failures or other data consistency problems. The odds of a problem in the field are probably pretty small, but nonetheless back-patch to all supported branches. Report: <19391.1477244876@sss.pgh.pa.us>
1 parent 8acf92e commit 676c603

File tree

3 files changed

+54
-12
lines changed

3 files changed

+54
-12
lines changed

src/backend/utils/adt/ri_triggers.c

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
#include "parser/parse_coerce.h"
4545
#include "parser/parse_relation.h"
4646
#include "miscadmin.h"
47+
#include "storage/bufmgr.h"
4748
#include "utils/acl.h"
4849
#include "utils/builtins.h"
4950
#include "utils/fmgroids.h"
@@ -285,20 +286,17 @@ RI_FKey_check(TriggerData *trigdata)
285286
* We should not even consider checking the row if it is no longer valid,
286287
* since it was either deleted (so the deferred check should be skipped)
287288
* or updated (in which case only the latest version of the row should be
288-
* checked). Test its liveness according to SnapshotSelf.
289-
*
290-
* NOTE: The normal coding rule is that one must acquire the buffer
291-
* content lock to call HeapTupleSatisfiesVisibility. We can skip that
292-
* here because we know that AfterTriggerExecute just fetched the tuple
293-
* successfully, so there cannot be a VACUUM compaction in progress on the
294-
* page (either heap_fetch would have waited for the VACUUM, or the
295-
* VACUUM's LockBufferForCleanup would be waiting for us to drop pin). And
296-
* since this is a row inserted by our open transaction, no one else can
297-
* be entitled to change its xmin/xmax.
298-
*/
299-
Assert(new_row_buf != InvalidBuffer);
289+
* checked). Test its liveness according to SnapshotSelf. We need pin
290+
* and lock on the buffer to call HeapTupleSatisfiesVisibility. Caller
291+
* should be holding pin, but not lock.
292+
*/
293+
LockBuffer(new_row_buf, BUFFER_LOCK_SHARE);
300294
if (!HeapTupleSatisfiesVisibility(new_row, SnapshotSelf, new_row_buf))
295+
{
296+
LockBuffer(new_row_buf, BUFFER_LOCK_UNLOCK);
301297
return PointerGetDatum(NULL);
298+
}
299+
LockBuffer(new_row_buf, BUFFER_LOCK_UNLOCK);
302300

303301
/*
304302
* Get the relation descriptors of the FK and PK tables.

src/test/regress/expected/foreign_key.out

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1335,3 +1335,24 @@ update pp set f1=f1+1; -- fail
13351335
ERROR: update or delete on table "pp" violates foreign key constraint "cc_f1_fkey" on table "cc"
13361336
DETAIL: Key (f1)=(13) is still referenced from table "cc".
13371337
drop table pp, cc;
1338+
--
1339+
-- Test deferred FK check on a tuple deleted by a rolled-back subtransaction
1340+
--
1341+
create table pktable2(f1 int primary key);
1342+
create table fktable2(f1 int references pktable2 deferrable initially deferred);
1343+
insert into pktable2 values(1);
1344+
begin;
1345+
insert into fktable2 values(1);
1346+
savepoint x;
1347+
delete from fktable2;
1348+
rollback to x;
1349+
commit;
1350+
begin;
1351+
insert into fktable2 values(2);
1352+
savepoint x;
1353+
delete from fktable2;
1354+
rollback to x;
1355+
commit; -- fail
1356+
ERROR: insert or update on table "fktable2" violates foreign key constraint "fktable2_f1_fkey"
1357+
DETAIL: Key (f1)=(2) is not present in table "pktable2".
1358+
drop table pktable2, fktable2;

src/test/regress/sql/foreign_key.sql

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -982,3 +982,26 @@ update pp set f1=f1+1;
982982
insert into cc values(13);
983983
update pp set f1=f1+1; -- fail
984984
drop table pp, cc;
985+
986+
--
987+
-- Test deferred FK check on a tuple deleted by a rolled-back subtransaction
988+
--
989+
create table pktable2(f1 int primary key);
990+
create table fktable2(f1 int references pktable2 deferrable initially deferred);
991+
insert into pktable2 values(1);
992+
993+
begin;
994+
insert into fktable2 values(1);
995+
savepoint x;
996+
delete from fktable2;
997+
rollback to x;
998+
commit;
999+
1000+
begin;
1001+
insert into fktable2 values(2);
1002+
savepoint x;
1003+
delete from fktable2;
1004+
rollback to x;
1005+
commit; -- fail
1006+
1007+
drop table pktable2, fktable2;

0 commit comments

Comments
 (0)