Skip to content

Commit 8bd45a3

Browse files
committed
Fix bogus "out of memory" reports in tuplestore.c.
The tuplesort/tuplestore memory management logic assumed that the chunk allocation overhead for its memtuples array could not increase when increasing the array size. This is and always was true for tuplesort, but we (I, I think) blindly copied that logic into tuplestore.c without noticing that the assumption failed to hold for the much smaller array elements used by tuplestore. Given rather small work_mem, this could result in an improper complaint about "unexpected out-of-memory situation", as reported by Brent DeSpain in bug #13530. The easiest way to fix this is just to increase tuplestore's initial array size so that the assumption holds. Rather than relying on magic constants, though, let's export a #define from aset.c that represents the safe allocation threshold, and make tuplestore's calculation depend on that. Do the same in tuplesort.c to keep the logic looking parallel, even though tuplesort.c isn't actually at risk at present. This will keep us from breaking it if we ever muck with the allocation parameters in aset.c. Back-patch to all supported versions. The error message doesn't occur pre-9.3, not so much because the problem can't happen as because the pre-9.3 tuplestore code neglected to check for it. (The chance of trouble is a great deal larger as of 9.3, though, due to changes in the array-size-increasing strategy.) However, allowing LACKMEM() to become true unexpectedly could still result in less-than-desirable behavior, so let's patch it all the way back.
1 parent 33afbdd commit 8bd45a3

File tree

4 files changed

+42
-15
lines changed

4 files changed

+42
-15
lines changed

src/backend/utils/mmgr/aset.c

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,9 @@
8989
*
9090
* With the current parameters, request sizes up to 8K are treated as chunks,
9191
* larger requests go into dedicated blocks. Change ALLOCSET_NUM_FREELISTS
92-
* to adjust the boundary point. (But in contexts with small maxBlockSize,
93-
* we may set the allocChunkLimit to less than 8K, so as to avoid space
94-
* wastage.)
92+
* to adjust the boundary point; and adjust ALLOCSET_SEPARATE_THRESHOLD in
93+
* memutils.h to agree. (Note: in contexts with small maxBlockSize, we may
94+
* set the allocChunkLimit to less than 8K, so as to avoid space wastage.)
9595
*--------------------
9696
*/
9797

@@ -393,7 +393,12 @@ AllocSetContextCreate(MemoryContext parent,
393393
* We have to have allocChunkLimit a power of two, because the requested
394394
* and actually-allocated sizes of any chunk must be on the same side of
395395
* the limit, else we get confused about whether the chunk is "big".
396+
*
397+
* Also, allocChunkLimit must not exceed ALLOCSET_SEPARATE_THRESHOLD.
396398
*/
399+
StaticAssertStmt(ALLOC_CHUNK_LIMIT == ALLOCSET_SEPARATE_THRESHOLD,
400+
"ALLOC_CHUNK_LIMIT != ALLOCSET_SEPARATE_THRESHOLD");
401+
397402
context->allocChunkLimit = ALLOC_CHUNK_LIMIT;
398403
while ((Size) (context->allocChunkLimit + ALLOC_CHUNKHDRSZ) >
399404
(Size) ((maxBlockSize - ALLOC_BLOCKHDRSZ) / ALLOC_CHUNK_FRACTION))

src/backend/utils/sort/tuplesort.c

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -571,7 +571,14 @@ tuplesort_begin_common(int workMem, bool randomAccess)
571571
state->tapeset = NULL;
572572

573573
state->memtupcount = 0;
574-
state->memtupsize = 1024; /* initial guess */
574+
575+
/*
576+
* Initial size of array must be more than ALLOCSET_SEPARATE_THRESHOLD;
577+
* see comments in grow_memtuples().
578+
*/
579+
state->memtupsize = Max(1024,
580+
ALLOCSET_SEPARATE_THRESHOLD / sizeof(SortTuple) + 1);
581+
575582
state->growmemtuples = true;
576583
state->memtuples = (SortTuple *) palloc(state->memtupsize * sizeof(SortTuple));
577584

@@ -1055,10 +1062,10 @@ grow_memtuples(Tuplesortstate *state)
10551062
* never generate a dangerous request, but to be safe, check explicitly
10561063
* that the array growth fits within availMem. (We could still cause
10571064
* LACKMEM if the memory chunk overhead associated with the memtuples
1058-
* array were to increase. That shouldn't happen with any sane value of
1059-
* allowedMem, because at any array size large enough to risk LACKMEM,
1060-
* palloc would be treating both old and new arrays as separate chunks.
1061-
* But we'll check LACKMEM explicitly below just in case.)
1065+
* array were to increase. That shouldn't happen because we chose the
1066+
* initial array size large enough to ensure that palloc will be treating
1067+
* both old and new arrays as separate chunks. But we'll check LACKMEM
1068+
* explicitly below just in case.)
10621069
*/
10631070
if (state->availMem < (long) ((newmemtupsize - memtupsize) * sizeof(SortTuple)))
10641071
goto noalloc;
@@ -1071,7 +1078,7 @@ grow_memtuples(Tuplesortstate *state)
10711078
state->memtupsize * sizeof(SortTuple));
10721079
USEMEM(state, GetMemoryChunkSpace(state->memtuples));
10731080
if (LACKMEM(state))
1074-
elog(ERROR, "unexpected out-of-memory situation during sort");
1081+
elog(ERROR, "unexpected out-of-memory situation in tuplesort");
10751082
return true;
10761083

10771084
noalloc:

src/backend/utils/sort/tuplestore.c

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,14 @@ tuplestore_begin_common(int eflags, bool interXact, int maxKBytes)
264264

265265
state->memtupdeleted = 0;
266266
state->memtupcount = 0;
267-
state->memtupsize = 1024; /* initial guess */
267+
268+
/*
269+
* Initial size of array must be more than ALLOCSET_SEPARATE_THRESHOLD;
270+
* see comments in grow_memtuples().
271+
*/
272+
state->memtupsize = Max(16384 / sizeof(void *),
273+
ALLOCSET_SEPARATE_THRESHOLD / sizeof(void *) + 1);
274+
268275
state->growmemtuples = true;
269276
state->memtuples = (void **) palloc(state->memtupsize * sizeof(void *));
270277

@@ -625,10 +632,10 @@ grow_memtuples(Tuplestorestate *state)
625632
* never generate a dangerous request, but to be safe, check explicitly
626633
* that the array growth fits within availMem. (We could still cause
627634
* LACKMEM if the memory chunk overhead associated with the memtuples
628-
* array were to increase. That shouldn't happen with any sane value of
629-
* allowedMem, because at any array size large enough to risk LACKMEM,
630-
* palloc would be treating both old and new arrays as separate chunks.
631-
* But we'll check LACKMEM explicitly below just in case.)
635+
* array were to increase. That shouldn't happen because we chose the
636+
* initial array size large enough to ensure that palloc will be treating
637+
* both old and new arrays as separate chunks. But we'll check LACKMEM
638+
* explicitly below just in case.)
632639
*/
633640
if (state->availMem < (long) ((newmemtupsize - memtupsize) * sizeof(void *)))
634641
goto noalloc;
@@ -641,7 +648,7 @@ grow_memtuples(Tuplestorestate *state)
641648
state->memtupsize * sizeof(void *));
642649
USEMEM(state, GetMemoryChunkSpace(state->memtuples));
643650
if (LACKMEM(state))
644-
elog(ERROR, "unexpected out-of-memory situation during sort");
651+
elog(ERROR, "unexpected out-of-memory situation in tuplestore");
645652
return true;
646653

647654
noalloc:

src/include/utils/memutils.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,4 +141,12 @@ extern MemoryContext AllocSetContextCreate(MemoryContext parent,
141141
#define ALLOCSET_SMALL_INITSIZE (1 * 1024)
142142
#define ALLOCSET_SMALL_MAXSIZE (8 * 1024)
143143

144+
/*
145+
* Threshold above which a request in an AllocSet context is certain to be
146+
* allocated separately (and thereby have constant allocation overhead).
147+
* Few callers should be interested in this, but tuplesort/tuplestore need
148+
* to know it.
149+
*/
150+
#define ALLOCSET_SEPARATE_THRESHOLD 8192
151+
144152
#endif /* MEMUTILS_H */

0 commit comments

Comments
 (0)