Skip to content

Commit fe88497

Browse files
committed
Fix race in SSI interaction with empty btrees.
When predicate-locking btrees, we have a special case for completely empty btrees, since there is no page to lock. This was racy, because, without buffer lock held, a matching key could be inserted between the _bt_search() and the PredicateLockRelation() calls. Fix, by rechecking _bt_search() after taking the relation-level SIREAD lock, if using SERIALIZABLE isolation and an empty btree is discovered. Back-patch to all supported releases. Fixes one aspect of bug #17949. Reported-by: Artem Anisimov <artem.anisimov.255@gmail.com> Reviewed-by: Dmitry Dolgov <9erthalion6@gmail.com> Reviewed-by: Heikki Linnakangas <hlinnaka@iki.fi> Discussion: https://postgr.es/m/17949-a0f17035294a55e2%40postgresql.org
1 parent bec0dcb commit fe88497

File tree

1 file changed

+26
-13
lines changed

1 file changed

+26
-13
lines changed

src/backend/access/nbtree/nbtsearch.c

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
#include "access/nbtree.h"
1919
#include "access/relscan.h"
20+
#include "access/xact.h"
2021
#include "miscadmin.h"
2122
#include "pgstat.h"
2223
#include "storage/predicate.h"
@@ -1258,22 +1259,34 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
12581259
{
12591260
/*
12601261
* We only get here if the index is completely empty. Lock relation
1261-
* because nothing finer to lock exists.
1262+
* because nothing finer to lock exists. Without a buffer lock, it's
1263+
* possible for another transaction to insert data between
1264+
* _bt_search() and PredicateLockRelation(). We have to try again
1265+
* after taking the relation-level predicate lock, to close a narrow
1266+
* window where we wouldn't scan concurrently inserted tuples, but the
1267+
* writer wouldn't see our predicate lock.
12621268
*/
1263-
PredicateLockRelation(rel, scan->xs_snapshot);
1264-
1265-
/*
1266-
* mark parallel scan as done, so that all the workers can finish
1267-
* their scan
1268-
*/
1269-
_bt_parallel_done(scan);
1270-
BTScanPosInvalidate(so->currPos);
1269+
if (IsolationIsSerializable())
1270+
{
1271+
PredicateLockRelation(rel, scan->xs_snapshot);
1272+
stack = _bt_search(rel, &inskey, &buf, BT_READ,
1273+
scan->xs_snapshot);
1274+
_bt_freestack(stack);
1275+
}
12711276

1272-
return false;
1277+
if (!BufferIsValid(buf))
1278+
{
1279+
/*
1280+
* Mark parallel scan as done, so that all the workers can finish
1281+
* their scan.
1282+
*/
1283+
_bt_parallel_done(scan);
1284+
BTScanPosInvalidate(so->currPos);
1285+
return false;
1286+
}
12731287
}
1274-
else
1275-
PredicateLockPage(rel, BufferGetBlockNumber(buf),
1276-
scan->xs_snapshot);
1288+
1289+
PredicateLockPage(rel, BufferGetBlockNumber(buf), scan->xs_snapshot);
12771290

12781291
_bt_initialize_more_data(so, dir);
12791292

0 commit comments

Comments
 (0)