Skip to content

Commit 003a31a

Browse files
committed
Avoid palloc in critical section in GiST WAL-logging.
Memory allocation can fail if you run out of memory, and inside a critical section that will lead to a PANIC. Use conservatively-sized arrays in stack instead. There was previously no explicit limit on the number of pages a GiST split can produce, it was only limited by the number of LWLocks that can be held simultaneously (100 at the moment). This patch adds an explicit limit of 75 pages. That should be plenty, a typical split shouldn't produce more than 2-3 page halves. The bug has been there forever, but only backpatch down to 9.1. The code was changed significantly in 9.1, and it doesn't seem worth the risk or trouble to adapt this for 9.0 and 8.4.
1 parent 029decf commit 003a31a

File tree

4 files changed

+38
-9
lines changed

4 files changed

+38
-9
lines changed

src/backend/access/gist/README

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ that didn't need to be split.
135135

136136
This differs from the insertion algorithm in the original paper. In the
137137
original paper, you first walk down the tree until you reach a leaf page, and
138-
then you adjust the downlink in the parent, and propagating the adjustment up,
138+
then you adjust the downlink in the parent, and propagate the adjustment up,
139139
all the way up to the root in the worst case. But we adjust the downlinks to
140140
cover the new key already when we walk down, so that when we reach the leaf
141141
page, we don't need to update the parents anymore, except to insert the

src/backend/access/gist/gist.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ gistplacetopage(Relation rel, Size freespace, GISTSTATE *giststate,
206206
GistNSN oldnsn = {0, 0};
207207
SplitedPageLayout rootpg;
208208
bool is_rootsplit;
209+
int npage;
209210

210211
is_rootsplit = (blkno == GIST_ROOT_BLKNO);
211212

@@ -226,6 +227,19 @@ gistplacetopage(Relation rel, Size freespace, GISTSTATE *giststate,
226227
itvec = gistjoinvector(itvec, &tlen, itup, ntup);
227228
dist = gistSplit(rel, page, itvec, tlen, giststate);
228229

230+
/*
231+
* Check that split didn't produce too many pages.
232+
*/
233+
npage = 0;
234+
for (ptr = dist; ptr; ptr = ptr->next)
235+
npage++;
236+
/* in a root split, we'll add one more page to the list below */
237+
if (is_rootsplit)
238+
npage++;
239+
if (npage > GIST_MAX_SPLIT_PAGES)
240+
elog(ERROR, "GiST page split into too many halves (%d, maximum %d)",
241+
npage, GIST_MAX_SPLIT_PAGES);
242+
229243
/*
230244
* Set up pages to work with. Allocate new buffers for all but the
231245
* leftmost page. The original page becomes the new leftmost page, and

src/backend/access/gist/gistxlog.c

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,7 @@ gistXLogSplit(RelFileNode node, BlockNumber blkno, bool page_is_leaf,
432432
BlockNumber origrlink, GistNSN orignsn,
433433
Buffer leftchildbuf, bool markfollowright)
434434
{
435-
XLogRecData *rdata;
435+
XLogRecData rdata[GIST_MAX_SPLIT_PAGES * 2 + 2];
436436
gistxlogPageSplit xlrec;
437437
SplitedPageLayout *ptr;
438438
int npage = 0,
@@ -441,8 +441,12 @@ gistXLogSplit(RelFileNode node, BlockNumber blkno, bool page_is_leaf,
441441

442442
for (ptr = dist; ptr; ptr = ptr->next)
443443
npage++;
444-
445-
rdata = (XLogRecData *) palloc(sizeof(XLogRecData) * (npage * 2 + 2));
444+
/*
445+
* the caller should've checked this already, but doesn't hurt to check
446+
* again.
447+
*/
448+
if (npage > GIST_MAX_SPLIT_PAGES)
449+
elog(ERROR, "GiST page split into too many halves");
446450

447451
xlrec.node = node;
448452
xlrec.origblkno = blkno;
@@ -492,7 +496,6 @@ gistXLogSplit(RelFileNode node, BlockNumber blkno, bool page_is_leaf,
492496

493497
recptr = XLogInsert(RM_GIST_ID, XLOG_GIST_PAGE_SPLIT, rdata);
494498

495-
pfree(rdata);
496499
return recptr;
497500
}
498501

@@ -515,14 +518,12 @@ gistXLogUpdate(RelFileNode node, Buffer buffer,
515518
IndexTuple *itup, int ituplen,
516519
Buffer leftchildbuf)
517520
{
518-
XLogRecData *rdata;
521+
XLogRecData rdata[MaxIndexTuplesPerPage + 3];
519522
gistxlogPageUpdate xlrec;
520523
int cur,
521524
i;
522525
XLogRecPtr recptr;
523526

524-
rdata = (XLogRecData *) palloc(sizeof(XLogRecData) * (3 + ituplen));
525-
526527
xlrec.node = node;
527528
xlrec.blkno = BufferGetBlockNumber(buffer);
528529
xlrec.ntodelete = ntodelete;
@@ -569,6 +570,5 @@ gistXLogUpdate(RelFileNode node, Buffer buffer,
569570

570571
recptr = XLogInsert(RM_GIST_ID, XLOG_GIST_PAGE_UPDATE, rdata);
571572

572-
pfree(rdata);
573573
return recptr;
574574
}

src/include/access/gist_private.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,21 @@
2222
#include "utils/rbtree.h"
2323
#include "utils/hsearch.h"
2424

25+
/*
26+
* Maximum number of "halves" a page can be split into in one operation.
27+
* Typically a split produces 2 halves, but can be more if keys have very
28+
* different lengths, or when inserting multiple keys in one operation (as
29+
* when inserting downlinks to an internal node). There is no theoretical
30+
* limit on this, but in practice if you get more than a handful page halves
31+
* in one split, there's something wrong with the opclass implementation.
32+
* GIST_MAX_SPLIT_PAGES is an arbitrary limit on that, used to size some
33+
* local arrays used during split. Note that there is also a limit on the
34+
* number of buffers that can be held locked at a time, MAX_SIMUL_LWLOCKS,
35+
* so if you raise this higher than that limit, you'll just get a different
36+
* error.
37+
*/
38+
#define GIST_MAX_SPLIT_PAGES 75
39+
2540
/* Buffer lock modes */
2641
#define GIST_SHARE BUFFER_LOCK_SHARE
2742
#define GIST_EXCLUSIVE BUFFER_LOCK_EXCLUSIVE

0 commit comments

Comments
 (0)