mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-09-04 18:07:20 +02:00
Avoid testing tuple visibility without buffer lock in RI_FKey_check().
Despite the argumentation I wrote in commit 7a2fe85b0
, 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>
This commit is contained in:
parent
13d5dd5095
commit
913e7e5983
@ -44,6 +44,7 @@
|
|||||||
#include "parser/parse_coerce.h"
|
#include "parser/parse_coerce.h"
|
||||||
#include "parser/parse_relation.h"
|
#include "parser/parse_relation.h"
|
||||||
#include "miscadmin.h"
|
#include "miscadmin.h"
|
||||||
|
#include "storage/bufmgr.h"
|
||||||
#include "utils/acl.h"
|
#include "utils/acl.h"
|
||||||
#include "utils/builtins.h"
|
#include "utils/builtins.h"
|
||||||
#include "utils/fmgroids.h"
|
#include "utils/fmgroids.h"
|
||||||
@ -286,20 +287,17 @@ RI_FKey_check(TriggerData *trigdata)
|
|||||||
* We should not even consider checking the row if it is no longer valid,
|
* We should not even consider checking the row if it is no longer valid,
|
||||||
* since it was either deleted (so the deferred check should be skipped)
|
* since it was either deleted (so the deferred check should be skipped)
|
||||||
* or updated (in which case only the latest version of the row should be
|
* or updated (in which case only the latest version of the row should be
|
||||||
* checked). Test its liveness according to SnapshotSelf.
|
* checked). Test its liveness according to SnapshotSelf. We need pin
|
||||||
*
|
* and lock on the buffer to call HeapTupleSatisfiesVisibility. Caller
|
||||||
* NOTE: The normal coding rule is that one must acquire the buffer
|
* should be holding pin, but not lock.
|
||||||
* content lock to call HeapTupleSatisfiesVisibility. We can skip that
|
|
||||||
* here because we know that AfterTriggerExecute just fetched the tuple
|
|
||||||
* successfully, so there cannot be a VACUUM compaction in progress on the
|
|
||||||
* page (either heap_fetch would have waited for the VACUUM, or the
|
|
||||||
* VACUUM's LockBufferForCleanup would be waiting for us to drop pin). And
|
|
||||||
* since this is a row inserted by our open transaction, no one else can
|
|
||||||
* be entitled to change its xmin/xmax.
|
|
||||||
*/
|
*/
|
||||||
Assert(new_row_buf != InvalidBuffer);
|
LockBuffer(new_row_buf, BUFFER_LOCK_SHARE);
|
||||||
if (!HeapTupleSatisfiesVisibility(new_row, SnapshotSelf, new_row_buf))
|
if (!HeapTupleSatisfiesVisibility(new_row, SnapshotSelf, new_row_buf))
|
||||||
|
{
|
||||||
|
LockBuffer(new_row_buf, BUFFER_LOCK_UNLOCK);
|
||||||
return PointerGetDatum(NULL);
|
return PointerGetDatum(NULL);
|
||||||
|
}
|
||||||
|
LockBuffer(new_row_buf, BUFFER_LOCK_UNLOCK);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Get the relation descriptors of the FK and PK tables.
|
* Get the relation descriptors of the FK and PK tables.
|
||||||
|
@ -1359,3 +1359,24 @@ update pp set f1=f1+1; -- fail
|
|||||||
ERROR: update or delete on table "pp" violates foreign key constraint "cc_f1_fkey" on table "cc"
|
ERROR: update or delete on table "pp" violates foreign key constraint "cc_f1_fkey" on table "cc"
|
||||||
DETAIL: Key (f1)=(13) is still referenced from table "cc".
|
DETAIL: Key (f1)=(13) is still referenced from table "cc".
|
||||||
drop table pp, cc;
|
drop table pp, cc;
|
||||||
|
--
|
||||||
|
-- Test deferred FK check on a tuple deleted by a rolled-back subtransaction
|
||||||
|
--
|
||||||
|
create table pktable2(f1 int primary key);
|
||||||
|
create table fktable2(f1 int references pktable2 deferrable initially deferred);
|
||||||
|
insert into pktable2 values(1);
|
||||||
|
begin;
|
||||||
|
insert into fktable2 values(1);
|
||||||
|
savepoint x;
|
||||||
|
delete from fktable2;
|
||||||
|
rollback to x;
|
||||||
|
commit;
|
||||||
|
begin;
|
||||||
|
insert into fktable2 values(2);
|
||||||
|
savepoint x;
|
||||||
|
delete from fktable2;
|
||||||
|
rollback to x;
|
||||||
|
commit; -- fail
|
||||||
|
ERROR: insert or update on table "fktable2" violates foreign key constraint "fktable2_f1_fkey"
|
||||||
|
DETAIL: Key (f1)=(2) is not present in table "pktable2".
|
||||||
|
drop table pktable2, fktable2;
|
||||||
|
@ -1009,3 +1009,26 @@ update pp set f1=f1+1;
|
|||||||
insert into cc values(13);
|
insert into cc values(13);
|
||||||
update pp set f1=f1+1; -- fail
|
update pp set f1=f1+1; -- fail
|
||||||
drop table pp, cc;
|
drop table pp, cc;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Test deferred FK check on a tuple deleted by a rolled-back subtransaction
|
||||||
|
--
|
||||||
|
create table pktable2(f1 int primary key);
|
||||||
|
create table fktable2(f1 int references pktable2 deferrable initially deferred);
|
||||||
|
insert into pktable2 values(1);
|
||||||
|
|
||||||
|
begin;
|
||||||
|
insert into fktable2 values(1);
|
||||||
|
savepoint x;
|
||||||
|
delete from fktable2;
|
||||||
|
rollback to x;
|
||||||
|
commit;
|
||||||
|
|
||||||
|
begin;
|
||||||
|
insert into fktable2 values(2);
|
||||||
|
savepoint x;
|
||||||
|
delete from fktable2;
|
||||||
|
rollback to x;
|
||||||
|
commit; -- fail
|
||||||
|
|
||||||
|
drop table pktable2, fktable2;
|
||||||
|
Loading…
Reference in New Issue
Block a user