summaryrefslogtreecommitdiff
path: root/src/backend/access/gin/ginget.c
diff options
context:
space:
mode:
authorHeikki Linnakangas <heikki.linnakangas@iki.fi>2013-11-08 22:21:42 +0200
committerHeikki Linnakangas <heikki.linnakangas@iki.fi>2013-11-08 22:23:16 +0200
commit4b099e1bff528a149129732e4d5a1bf020f842ea (patch)
tree0b52413b42e6653ac25ca9de99a1e680cd326514 /src/backend/access/gin/ginget.c
parent987f05e162dd16aaf395ce1cf346f616a21a0a2c (diff)
Fix race condition in GIN posting tree page deletion.
If a page is deleted, and reused for something else, just as a search is following a rightlink to it from its left sibling, the search would continue scanning whatever the new contents of the page are. That could lead to incorrect query results, or even something more curious if the page is reused for a different kind of a page. To fix, modify the search algorithm to lock the next page before releasing the previous one, and refrain from deleting pages from the leftmost branch of the tree. Add a new Concurrency section to the README, explaining why this works. There is a lot more one could say about concurrency in GIN, but that's for another patch. Backpatch to all supported versions.
Diffstat (limited to 'src/backend/access/gin/ginget.c')
-rw-r--r--src/backend/access/gin/ginget.c25
1 files changed, 6 insertions, 19 deletions
diff --git a/src/backend/access/gin/ginget.c b/src/backend/access/gin/ginget.c
index a3af0e61c99..42ba8e3e14b 100644
--- a/src/backend/access/gin/ginget.c
+++ b/src/backend/access/gin/ginget.c
@@ -73,14 +73,11 @@ moveRightIfItNeeded(GinBtreeData *btree, GinBtreeStack *stack)
/*
* We scanned the whole page, so we should take right page
*/
- stack->blkno = GinPageGetOpaque(page)->rightlink;
-
if (GinPageRightMost(page))
return false; /* no more pages */
- LockBuffer(stack->buffer, GIN_UNLOCK);
- stack->buffer = ReleaseAndReadBuffer(stack->buffer, btree->index, stack->blkno);
- LockBuffer(stack->buffer, GIN_SHARE);
+ stack->buffer = ginStepRight(stack->buffer, btree->index, GIN_SHARE);
+ stack->blkno = BufferGetBlockNumber(stack->buffer);
stack->off = FirstOffsetNumber;
}
@@ -97,7 +94,6 @@ scanForItems(Relation index, GinScanEntry scanEntry, BlockNumber rootPostingTree
GinPostingTreeScan *gdi;
Buffer buffer;
Page page;
- BlockNumber blkno;
gdi = prepareScanPostingTree(index, rootPostingTree, TRUE);
@@ -122,16 +118,13 @@ scanForItems(Relation index, GinScanEntry scanEntry, BlockNumber rootPostingTree
scanEntry->predictNumberResult += GinPageGetOpaque(page)->maxoff;
}
- blkno = GinPageGetOpaque(page)->rightlink;
if (GinPageRightMost(page))
{
UnlockReleaseBuffer(buffer);
return; /* no more pages */
}
- LockBuffer(buffer, GIN_UNLOCK);
- buffer = ReleaseAndReadBuffer(buffer, index, blkno);
- LockBuffer(buffer, GIN_SHARE);
+ buffer = ginStepRight(buffer, index, GIN_SHARE);
}
}
@@ -455,7 +448,6 @@ static void
entryGetNextItem(Relation index, GinScanEntry entry)
{
Page page;
- BlockNumber blkno;
for (;;)
{
@@ -473,21 +465,16 @@ entryGetNextItem(Relation index, GinScanEntry entry)
* It's needed to go by right link. During that we should refind
* first ItemPointer greater that stored
*/
-
- blkno = GinPageGetOpaque(page)->rightlink;
-
- LockBuffer(entry->buffer, GIN_UNLOCK);
- if (blkno == InvalidBlockNumber)
+ if (GinPageRightMost(page))
{
- ReleaseBuffer(entry->buffer);
+ UnlockReleaseBuffer(entry->buffer);
ItemPointerSetInvalid(&entry->curItem);
entry->buffer = InvalidBuffer;
entry->isFinished = TRUE;
return;
}
- entry->buffer = ReleaseAndReadBuffer(entry->buffer, index, blkno);
- LockBuffer(entry->buffer, GIN_SHARE);
+ entry->buffer = ginStepRight(entry->buffer, index, GIN_SHARE);
page = BufferGetPage(entry->buffer);
entry->offset = InvalidOffsetNumber;