Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit e16954f

Browse files
committed
Try again to make the visibility map crash safe.
My previous attempt was quite a bit less than half-baked with respect to heap_update().
1 parent 66a36ef commit e16954f

File tree

3 files changed

+100
-44
lines changed

3 files changed

+100
-44
lines changed

src/backend/access/heap/heapam.c

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1941,7 +1941,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
19411941
*/
19421942
buffer = RelationGetBufferForTuple(relation, heaptup->t_len,
19431943
InvalidBuffer, options, bistate,
1944-
&vmbuffer);
1944+
&vmbuffer, NULL);
19451945

19461946
/*
19471947
* We're about to do the actual insert -- check for conflict at the
@@ -2519,19 +2519,6 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
25192519

25202520
LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
25212521

2522-
/*
2523-
* If we didn't pin the visibility map page and the page has become all
2524-
* visible while we were busy locking the buffer, we'll have to unlock and
2525-
* re-lock, to avoid holding the buffer lock across an I/O. That's a bit
2526-
* unfortunate, but hopefully shouldn't happen often.
2527-
*/
2528-
if (vmbuffer == InvalidBuffer && PageIsAllVisible(page))
2529-
{
2530-
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
2531-
visibilitymap_pin(relation, block, &vmbuffer);
2532-
LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
2533-
}
2534-
25352522
lp = PageGetItemId(page, ItemPointerGetOffsetNumber(otid));
25362523
Assert(ItemIdIsNormal(lp));
25372524

@@ -2667,6 +2654,20 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
26672654
return result;
26682655
}
26692656

2657+
/*
2658+
* If we didn't pin the visibility map page and the page has become all
2659+
* visible while we were busy locking the buffer, or during some subsequent
2660+
* window during which we had it unlocked, we'll have to unlock and
2661+
* re-lock, to avoid holding the buffer lock across an I/O. That's a bit
2662+
* unfortunate, but hopefully shouldn't happen often.
2663+
*/
2664+
if (vmbuffer == InvalidBuffer && PageIsAllVisible(page))
2665+
{
2666+
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
2667+
visibilitymap_pin(relation, block, &vmbuffer);
2668+
LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
2669+
}
2670+
26702671
/*
26712672
* We're about to do the actual update -- check for conflict first, to
26722673
* avoid possibly having to roll back work we've just done.
@@ -2784,7 +2785,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
27842785
/* Assume there's no chance to put heaptup on same page. */
27852786
newbuf = RelationGetBufferForTuple(relation, heaptup->t_len,
27862787
buffer, 0, NULL,
2787-
&vmbuffer_new);
2788+
&vmbuffer_new, &vmbuffer);
27882789
}
27892790
else
27902791
{
@@ -2802,7 +2803,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
28022803
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
28032804
newbuf = RelationGetBufferForTuple(relation, heaptup->t_len,
28042805
buffer, 0, NULL,
2805-
&vmbuffer_new);
2806+
&vmbuffer_new, &vmbuffer);
28062807
}
28072808
else
28082809
{
@@ -2908,11 +2909,15 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
29082909
{
29092910
all_visible_cleared = true;
29102911
PageClearAllVisible(BufferGetPage(buffer));
2912+
visibilitymap_clear(relation, BufferGetBlockNumber(buffer),
2913+
vmbuffer);
29112914
}
29122915
if (newbuf != buffer && PageIsAllVisible(BufferGetPage(newbuf)))
29132916
{
29142917
all_visible_cleared_new = true;
29152918
PageClearAllVisible(BufferGetPage(newbuf));
2919+
visibilitymap_clear(relation, BufferGetBlockNumber(newbuf),
2920+
vmbuffer_new);
29162921
}
29172922

29182923
if (newbuf != buffer)
@@ -2949,14 +2954,6 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
29492954
*/
29502955
CacheInvalidateHeapTuple(relation, &oldtup);
29512956

2952-
/* Clear bits in visibility map */
2953-
if (all_visible_cleared)
2954-
visibilitymap_clear(relation, BufferGetBlockNumber(buffer),
2955-
vmbuffer);
2956-
if (all_visible_cleared_new)
2957-
visibilitymap_clear(relation, BufferGetBlockNumber(newbuf),
2958-
vmbuffer_new);
2959-
29602957
/* Now we can release the buffer(s) */
29612958
if (newbuf != buffer)
29622959
ReleaseBuffer(newbuf);

src/backend/access/heap/hio.c

Lines changed: 78 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,64 @@ ReadBufferBI(Relation relation, BlockNumber targetBlock,
9696
return buffer;
9797
}
9898

99+
/*
100+
* For each heap page which is all-visible, acquire a pin on the appropriate
101+
* visibility map page, if we haven't already got one.
102+
*
103+
* buffer2 may be InvalidBuffer, if only one buffer is involved. buffer1
104+
* must not be InvalidBuffer. If both buffers are specified, buffer1 must
105+
* be less than buffer2.
106+
*/
107+
static void
108+
GetVisibilityMapPins(Relation relation, Buffer buffer1, Buffer buffer2,
109+
BlockNumber block1, BlockNumber block2,
110+
Buffer *vmbuffer1, Buffer *vmbuffer2)
111+
{
112+
bool need_to_pin_buffer1;
113+
bool need_to_pin_buffer2;
114+
115+
Assert(BufferIsValid(buffer1));
116+
Assert(buffer2 == InvalidBuffer || buffer1 <= buffer2);
117+
118+
while (1)
119+
{
120+
/* Figure out which pins we need but don't have. */
121+
need_to_pin_buffer1 = PageIsAllVisible(BufferGetPage(buffer1))
122+
&& !visibilitymap_pin_ok(block1, *vmbuffer1);
123+
need_to_pin_buffer2 = buffer2 != InvalidBuffer
124+
&& PageIsAllVisible(BufferGetPage(buffer2))
125+
&& !visibilitymap_pin_ok(block2, *vmbuffer2);
126+
if (!need_to_pin_buffer1 && !need_to_pin_buffer2)
127+
return;
128+
129+
/* We must unlock both buffers before doing any I/O. */
130+
LockBuffer(buffer1, BUFFER_LOCK_UNLOCK);
131+
if (buffer2 != InvalidBuffer && buffer2 != buffer1)
132+
LockBuffer(buffer2, BUFFER_LOCK_UNLOCK);
133+
134+
/* Get pins. */
135+
if (need_to_pin_buffer1)
136+
visibilitymap_pin(relation, block1, vmbuffer1);
137+
if (need_to_pin_buffer2)
138+
visibilitymap_pin(relation, block2, vmbuffer2);
139+
140+
/* Relock buffers. */
141+
LockBuffer(buffer1, BUFFER_LOCK_EXCLUSIVE);
142+
if (buffer2 != InvalidBuffer && buffer2 != buffer1)
143+
LockBuffer(buffer2, BUFFER_LOCK_EXCLUSIVE);
144+
145+
/*
146+
* If there are two buffers involved and we pinned just one of them,
147+
* it's possible that the second one became all-visible while we were
148+
* busy pinning the first one. If it looks like that's a possible
149+
* scenario, we'll need to make a second pass through this loop.
150+
*/
151+
if (buffer2 == InvalidBuffer || buffer1 == buffer2
152+
|| (need_to_pin_buffer1 && need_to_pin_buffer2))
153+
break;
154+
}
155+
}
156+
99157
/*
100158
* RelationGetBufferForTuple
101159
*
@@ -152,7 +210,7 @@ Buffer
152210
RelationGetBufferForTuple(Relation relation, Size len,
153211
Buffer otherBuffer, int options,
154212
struct BulkInsertStateData * bistate,
155-
Buffer *vmbuffer)
213+
Buffer *vmbuffer, Buffer *vmbuffer_other)
156214
{
157215
bool use_fsm = !(options & HEAP_INSERT_SKIP_FSM);
158216
Buffer buffer = InvalidBuffer;
@@ -284,11 +342,17 @@ RelationGetBufferForTuple(Relation relation, Size len,
284342
}
285343

286344
/*
287-
* If the page is all visible but we don't have the right visibility
288-
* map page pinned, then give up our locks, go get the pin, and
289-
* re-lock. This is pretty painful, but hopefully shouldn't happen
290-
* often. Note that there's a small possibility that we didn't pin
291-
* the page above but still have the correct page pinned anyway, either
345+
* We now have the target page (and the other buffer, if any) pinned
346+
* and locked. However, since our initial PageIsAllVisible checks
347+
* were performed before acquiring the lock, the results might now
348+
* be out of date, either for the selected victim buffer, or for the
349+
* other buffer passed by the caller. In that case, we'll need to give
350+
* up our locks, go get the pin(s) we failed to get earlier, and
351+
* re-lock. That's pretty painful, but hopefully shouldn't happen
352+
* often.
353+
*
354+
* Note that there's a small possibility that we didn't pin the
355+
* page above but still have the correct page pinned anyway, either
292356
* because we've already made a previous pass through this loop, or
293357
* because caller passed us the right page anyway.
294358
*
@@ -297,19 +361,14 @@ RelationGetBufferForTuple(Relation relation, Size len,
297361
* cleared by some other backend anyway. In that case, we'll have done
298362
* a bit of extra work for no gain, but there's no real harm done.
299363
*/
300-
if (PageIsAllVisible(BufferGetPage(buffer))
301-
&& !visibilitymap_pin_ok(targetBlock, *vmbuffer))
302-
{
303-
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
304-
if (otherBlock != targetBlock)
305-
LockBuffer(otherBuffer, BUFFER_LOCK_UNLOCK);
306-
visibilitymap_pin(relation, targetBlock, vmbuffer);
307-
if (otherBuffer != InvalidBuffer && otherBlock < targetBlock)
308-
LockBuffer(otherBuffer, BUFFER_LOCK_EXCLUSIVE);
309-
LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
310-
if (otherBuffer != InvalidBuffer && otherBlock > targetBlock)
311-
LockBuffer(otherBuffer, BUFFER_LOCK_EXCLUSIVE);
312-
}
364+
if (otherBuffer == InvalidBuffer || buffer <= otherBuffer)
365+
GetVisibilityMapPins(relation, buffer, otherBuffer,
366+
targetBlock, otherBlock, vmbuffer,
367+
vmbuffer_other);
368+
else
369+
GetVisibilityMapPins(relation, otherBuffer, buffer,
370+
otherBlock, targetBlock, vmbuffer_other,
371+
vmbuffer);
313372

314373
/*
315374
* Now we can check to see if there's enough free space here. If so,

src/include/access/hio.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,6 @@ extern void RelationPutHeapTuple(Relation relation, Buffer buffer,
3939
extern Buffer RelationGetBufferForTuple(Relation relation, Size len,
4040
Buffer otherBuffer, int options,
4141
struct BulkInsertStateData * bistate,
42-
Buffer *vmbuffer);
42+
Buffer *vmbuffer, Buffer *vmbuffer_other);
4343

4444
#endif /* HIO_H */

0 commit comments

Comments
 (0)