mirror of
https://github.com/postgres/postgres.git
synced 2025-07-07 00:36:50 +03:00
Fix more crash-safe visibility map bugs, and improve comments.
In lazy_scan_heap, we could issue bogus warnings about incorrect information in the visibility map, because we checked the visibility map bit before locking the heap page, creating a race condition. Fix by rechecking the visibility map bit before we complain. Rejigger some related logic so that we rely on the possibly-outdated all_visible_according_to_vm value as little as possible. In heap_multi_insert, it's not safe to clear the visibility map bit before beginning the critical section. The visibility map is not crash-safe unless we treat clearing the bit as a critical operation. Specifically, if the transaction were to error out after we set the bit and before entering the critical section, we could end up writing the heap page to disk (with the bit cleared) and crashing before the visibility map page made it to disk. That would be bad. heap_insert has this correct, but somehow the order of operations got rearranged when heap_multi_insert was added. Also, add some more comments to visibilitymap_test, lazy_scan_heap, and IndexOnlyNext, expounding on concurrency issues. Per extensive code review by Andres Freund, and further review by Tom Lane, who also made the original report about the bogus warnings.
This commit is contained in:
@ -2149,15 +2149,6 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
|
||||
&vmbuffer, NULL);
|
||||
page = BufferGetPage(buffer);
|
||||
|
||||
if (PageIsAllVisible(page))
|
||||
{
|
||||
all_visible_cleared = true;
|
||||
PageClearAllVisible(page);
|
||||
visibilitymap_clear(relation,
|
||||
BufferGetBlockNumber(buffer),
|
||||
vmbuffer);
|
||||
}
|
||||
|
||||
/* NO EREPORT(ERROR) from here till changes are logged */
|
||||
START_CRIT_SECTION();
|
||||
|
||||
@ -2172,6 +2163,15 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
|
||||
RelationPutHeapTuple(relation, buffer, heaptup);
|
||||
}
|
||||
|
||||
if (PageIsAllVisible(page))
|
||||
{
|
||||
all_visible_cleared = true;
|
||||
PageClearAllVisible(page);
|
||||
visibilitymap_clear(relation,
|
||||
BufferGetBlockNumber(buffer),
|
||||
vmbuffer);
|
||||
}
|
||||
|
||||
/*
|
||||
* XXX Should we set PageSetPrunable on this page ? See heap_insert()
|
||||
*/
|
||||
|
@ -293,6 +293,13 @@ visibilitymap_set(Relation rel, BlockNumber heapBlk, XLogRecPtr recptr,
|
||||
* relation. On return, *buf is a valid buffer with the map page containing
|
||||
* the bit for heapBlk, or InvalidBuffer. The caller is responsible for
|
||||
* releasing *buf after it's done testing and setting bits.
|
||||
*
|
||||
* NOTE: This function is typically called without a lock on the heap page,
|
||||
* so somebody else could change the bit just after we look at it. In fact,
|
||||
* since we don't lock the visibility map page either, it's even possible that
|
||||
* someone else could have changed the bit just before we look at it, but yet
|
||||
* we might see the old value. It is the caller's responsibility to deal with
|
||||
* all concurrency issues!
|
||||
*/
|
||||
bool
|
||||
visibilitymap_test(Relation rel, BlockNumber heapBlk, Buffer *buf)
|
||||
@ -327,7 +334,9 @@ visibilitymap_test(Relation rel, BlockNumber heapBlk, Buffer *buf)
|
||||
map = PageGetContents(BufferGetPage(*buf));
|
||||
|
||||
/*
|
||||
* We don't need to lock the page, as we're only looking at a single bit.
|
||||
* A single-bit read is atomic. There could be memory-ordering effects
|
||||
* here, but for performance reasons we make it the caller's job to worry
|
||||
* about that.
|
||||
*/
|
||||
result = (map[mapByte] & (1 << mapBit)) ? true : false;
|
||||
|
||||
|
Reference in New Issue
Block a user