Skip to content

Commit 04e298b

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 fc75250 commit 04e298b

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
@@ -220,6 +220,7 @@ gistplacetopage(Relation rel, Size freespace, GISTSTATE *giststate,
220220
GistNSN oldnsn = 0;
221221
SplitedPageLayout rootpg;
222222
bool is_rootsplit;
223+
int npage;
223224

224225
is_rootsplit = (blkno == GIST_ROOT_BLKNO);
225226

@@ -240,6 +241,19 @@ gistplacetopage(Relation rel, Size freespace, GISTSTATE *giststate,
240241
itvec = gistjoinvector(itvec, &tlen, itup, ntup);
241242
dist = gistSplit(rel, page, itvec, tlen, giststate);
242243

244+
/*
245+
* Check that split didn't produce too many pages.
246+
*/
247+
npage = 0;
248+
for (ptr = dist; ptr; ptr = ptr->next)
249+
npage++;
250+
/* in a root split, we'll add one more page to the list below */
251+
if (is_rootsplit)
252+
npage++;
253+
if (npage > GIST_MAX_SPLIT_PAGES)
254+
elog(ERROR, "GiST page split into too many halves (%d, maximum %d)",
255+
npage, GIST_MAX_SPLIT_PAGES);
256+
243257
/*
244258
* Set up pages to work with. Allocate new buffers for all but the
245259
* 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
@@ -379,7 +379,7 @@ gistXLogSplit(RelFileNode node, BlockNumber blkno, bool page_is_leaf,
379379
BlockNumber origrlink, GistNSN orignsn,
380380
Buffer leftchildbuf, bool markfollowright)
381381
{
382-
XLogRecData *rdata;
382+
XLogRecData rdata[GIST_MAX_SPLIT_PAGES * 2 + 2];
383383
gistxlogPageSplit xlrec;
384384
SplitedPageLayout *ptr;
385385
int npage = 0,
@@ -388,8 +388,12 @@ gistXLogSplit(RelFileNode node, BlockNumber blkno, bool page_is_leaf,
388388

389389
for (ptr = dist; ptr; ptr = ptr->next)
390390
npage++;
391-
392-
rdata = (XLogRecData *) palloc(sizeof(XLogRecData) * (npage * 2 + 2));
391+
/*
392+
* the caller should've checked this already, but doesn't hurt to check
393+
* again.
394+
*/
395+
if (npage > GIST_MAX_SPLIT_PAGES)
396+
elog(ERROR, "GiST page split into too many halves");
393397

394398
xlrec.node = node;
395399
xlrec.origblkno = blkno;
@@ -439,7 +443,6 @@ gistXLogSplit(RelFileNode node, BlockNumber blkno, bool page_is_leaf,
439443

440444
recptr = XLogInsert(RM_GIST_ID, XLOG_GIST_PAGE_SPLIT, rdata);
441445

442-
pfree(rdata);
443446
return recptr;
444447
}
445448

@@ -462,14 +465,12 @@ gistXLogUpdate(RelFileNode node, Buffer buffer,
462465
IndexTuple *itup, int ituplen,
463466
Buffer leftchildbuf)
464467
{
465-
XLogRecData *rdata;
468+
XLogRecData rdata[MaxIndexTuplesPerPage + 3];
466469
gistxlogPageUpdate xlrec;
467470
int cur,
468471
i;
469472
XLogRecPtr recptr;
470473

471-
rdata = (XLogRecData *) palloc(sizeof(XLogRecData) * (3 + ituplen));
472-
473474
xlrec.node = node;
474475
xlrec.blkno = BufferGetBlockNumber(buffer);
475476
xlrec.ntodelete = ntodelete;
@@ -516,6 +517,5 @@ gistXLogUpdate(RelFileNode node, Buffer buffer,
516517

517518
recptr = XLogInsert(RM_GIST_ID, XLOG_GIST_PAGE_UPDATE, rdata);
518519

519-
pfree(rdata);
520520
return recptr;
521521
}

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)