Skip to content

Commit 395249e

Browse files
committed
Several changes to reduce the probability of running out of memory during
AbortTransaction, which would lead to recursion and eventual PANIC exit as illustrated in recent report from Jeff Davis. First, in xact.c create a special dedicated memory context for AbortTransaction to run in. This solves the problem as long as AbortTransaction doesn't need more than 32K (or whatever other size we create the context with). But in corner cases it might. Second, in trigger.c arrange to keep pending after-trigger event records in separate contexts that can be freed near the beginning of AbortTransaction, rather than having them persist until CleanupTransaction as before. Third, in portalmem.c arrange to free executor state data earlier as well. These two changes should result in backing off the out-of-memory condition before AbortTransaction needs any significant amount of memory, at least in typical cases such as memory overrun due to too many trigger events or too big an executor hash table. And all the same for subtransaction abort too, of course.
1 parent 2a55984 commit 395249e

File tree

3 files changed

+155
-50
lines changed

3 files changed

+155
-50
lines changed

src/backend/access/transam/xact.c

Lines changed: 61 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
*
1111
*
1212
* IDENTIFICATION
13-
* $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.228 2006/11/05 22:42:07 tgl Exp $
13+
* $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.229 2006/11/23 01:14:59 tgl Exp $
1414
*
1515
*-------------------------------------------------------------------------
1616
*/
@@ -171,6 +171,12 @@ static TimestampTz stmtStartTimestamp;
171171
*/
172172
static char *prepareGID;
173173

174+
/*
175+
* Private context for transaction-abort work --- we reserve space for this
176+
* at startup to ensure that AbortTransaction and AbortSubTransaction can work
177+
* when we've run out of memory.
178+
*/
179+
static MemoryContext TransactionAbortContext = NULL;
174180

175181
/*
176182
* List of add-on start- and end-of-xact callbacks
@@ -555,6 +561,21 @@ AtStart_Memory(void)
555561
{
556562
TransactionState s = CurrentTransactionState;
557563

564+
/*
565+
* If this is the first time through, create a private context for
566+
* AbortTransaction to work in. By reserving some space now, we can
567+
* insulate AbortTransaction from out-of-memory scenarios. Like
568+
* ErrorContext, we set it up with slow growth rate and a nonzero
569+
* minimum size, so that space will be reserved immediately.
570+
*/
571+
if (TransactionAbortContext == NULL)
572+
TransactionAbortContext =
573+
AllocSetContextCreate(TopMemoryContext,
574+
"TransactionAbortContext",
575+
32 * 1024,
576+
32 * 1024,
577+
32 * 1024);
578+
558579
/*
559580
* We shouldn't have a transaction context already.
560581
*/
@@ -1086,20 +1107,15 @@ static void
10861107
AtAbort_Memory(void)
10871108
{
10881109
/*
1089-
* Make sure we are in a valid context (not a child of
1090-
* TopTransactionContext...). Note that it is possible for this code to
1091-
* be called when we aren't in a transaction at all; go directly to
1092-
* TopMemoryContext in that case.
1110+
* Switch into TransactionAbortContext, which should have some free
1111+
* space even if nothing else does. We'll work in this context until
1112+
* we've finished cleaning up.
1113+
*
1114+
* It is barely possible to get here when we've not been able to create
1115+
* TransactionAbortContext yet; if so use TopMemoryContext.
10931116
*/
1094-
if (TopTransactionContext != NULL)
1095-
{
1096-
MemoryContextSwitchTo(TopTransactionContext);
1097-
1098-
/*
1099-
* We do not want to destroy the transaction's global state yet, so we
1100-
* can't free any memory here.
1101-
*/
1102-
}
1117+
if (TransactionAbortContext != NULL)
1118+
MemoryContextSwitchTo(TransactionAbortContext);
11031119
else
11041120
MemoryContextSwitchTo(TopMemoryContext);
11051121
}
@@ -1110,9 +1126,9 @@ AtAbort_Memory(void)
11101126
static void
11111127
AtSubAbort_Memory(void)
11121128
{
1113-
Assert(TopTransactionContext != NULL);
1129+
Assert(TransactionAbortContext != NULL);
11141130

1115-
MemoryContextSwitchTo(TopTransactionContext);
1131+
MemoryContextSwitchTo(TransactionAbortContext);
11161132
}
11171133

11181134

@@ -1272,13 +1288,19 @@ RecordSubTransactionAbort(void)
12721288
static void
12731289
AtCleanup_Memory(void)
12741290
{
1291+
Assert(CurrentTransactionState->parent == NULL);
1292+
12751293
/*
12761294
* Now that we're "out" of a transaction, have the system allocate things
12771295
* in the top memory context instead of per-transaction contexts.
12781296
*/
12791297
MemoryContextSwitchTo(TopMemoryContext);
12801298

1281-
Assert(CurrentTransactionState->parent == NULL);
1299+
/*
1300+
* Clear the special abort context for next time.
1301+
*/
1302+
if (TransactionAbortContext != NULL)
1303+
MemoryContextResetAndDeleteChildren(TransactionAbortContext);
12821304

12831305
/*
12841306
* Release all transaction-local memory.
@@ -1310,6 +1332,12 @@ AtSubCleanup_Memory(void)
13101332
MemoryContextSwitchTo(s->parent->curTransactionContext);
13111333
CurTransactionContext = s->parent->curTransactionContext;
13121334

1335+
/*
1336+
* Clear the special abort context for next time.
1337+
*/
1338+
if (TransactionAbortContext != NULL)
1339+
MemoryContextResetAndDeleteChildren(TransactionAbortContext);
1340+
13131341
/*
13141342
* Delete the subxact local memory contexts. Its CurTransactionContext can
13151343
* go too (note this also kills CurTransactionContexts from any children
@@ -1849,6 +1877,10 @@ AbortTransaction(void)
18491877
/* Prevent cancel/die interrupt while cleaning up */
18501878
HOLD_INTERRUPTS();
18511879

1880+
/* Make sure we have a valid memory context and resource owner */
1881+
AtAbort_Memory();
1882+
AtAbort_ResourceOwner();
1883+
18521884
/*
18531885
* Release any LW locks we might be holding as quickly as possible.
18541886
* (Regular locks, however, must be held till we finish aborting.)
@@ -1881,10 +1913,6 @@ AbortTransaction(void)
18811913
*/
18821914
s->state = TRANS_ABORT;
18831915

1884-
/* Make sure we have a valid memory context and resource owner */
1885-
AtAbort_Memory();
1886-
AtAbort_ResourceOwner();
1887-
18881916
/*
18891917
* Reset user id which might have been changed transiently. We cannot use
18901918
* s->currentUser, since it may not be set yet; instead rely on internal
@@ -3704,15 +3732,12 @@ AbortSubTransaction(void)
37043732
{
37053733
TransactionState s = CurrentTransactionState;
37063734

3707-
ShowTransactionState("AbortSubTransaction");
3708-
3709-
if (s->state != TRANS_INPROGRESS)
3710-
elog(WARNING, "AbortSubTransaction while in %s state",
3711-
TransStateAsString(s->state));
3712-
3735+
/* Prevent cancel/die interrupt while cleaning up */
37133736
HOLD_INTERRUPTS();
37143737

3715-
s->state = TRANS_ABORT;
3738+
/* Make sure we have a valid memory context and resource owner */
3739+
AtSubAbort_Memory();
3740+
AtSubAbort_ResourceOwner();
37163741

37173742
/*
37183743
* Release any LW locks we might be holding as quickly as possible.
@@ -3731,10 +3756,15 @@ AbortSubTransaction(void)
37313756
LockWaitCancel();
37323757

37333758
/*
3734-
* do abort processing
3759+
* check the current transaction state
37353760
*/
3736-
AtSubAbort_Memory();
3737-
AtSubAbort_ResourceOwner();
3761+
ShowTransactionState("AbortSubTransaction");
3762+
3763+
if (s->state != TRANS_INPROGRESS)
3764+
elog(WARNING, "AbortSubTransaction while in %s state",
3765+
TransStateAsString(s->state));
3766+
3767+
s->state = TRANS_ABORT;
37383768

37393769
/*
37403770
* We can skip all this stuff if the subxact failed before creating a

src/backend/commands/trigger.c

Lines changed: 76 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* Portions Copyright (c) 1994, Regents of the University of California
88
*
99
* IDENTIFICATION
10-
* $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.209 2006/10/04 00:29:51 momjian Exp $
10+
* $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.210 2006/11/23 01:14:59 tgl Exp $
1111
*
1212
*-------------------------------------------------------------------------
1313
*/
@@ -1801,8 +1801,8 @@ ltrmark:;
18011801
* during the current transaction tree. (BEFORE triggers are fired
18021802
* immediately so we don't need any persistent state about them.) The struct
18031803
* and most of its subsidiary data are kept in TopTransactionContext; however
1804-
* the individual event records are kept in CurTransactionContext, so that
1805-
* they will easily go away during subtransaction abort.
1804+
* the individual event records are kept in separate contexts, to make them
1805+
* easy to delete during subtransaction abort.
18061806
*
18071807
* Because the list of pending events can grow large, we go to some effort
18081808
* to minimize memory consumption. We do not use the generic List mechanism
@@ -1889,7 +1889,10 @@ typedef struct AfterTriggerEventList
18891889
* events is the current list of deferred events. This is global across
18901890
* all subtransactions of the current transaction. In a subtransaction
18911891
* abort, we know that the events added by the subtransaction are at the
1892-
* end of the list, so it is relatively easy to discard them.
1892+
* end of the list, so it is relatively easy to discard them. The event
1893+
* structs themselves are stored in event_cxt if generated by the top-level
1894+
* transaction, else in per-subtransaction contexts identified by the
1895+
* entries in cxt_stack.
18931896
*
18941897
* query_depth is the current depth of nested AfterTriggerBeginQuery calls
18951898
* (-1 when the stack is empty).
@@ -1931,6 +1934,8 @@ typedef struct AfterTriggersData
19311934
int query_depth; /* current query list index */
19321935
AfterTriggerEventList *query_stack; /* events pending from each query */
19331936
int maxquerydepth; /* allocated len of above array */
1937+
MemoryContext event_cxt; /* top transaction's event context, if any */
1938+
MemoryContext *cxt_stack; /* per-subtransaction event contexts */
19341939

19351940
/* these fields are just for resetting at subtrans abort: */
19361941

@@ -2464,7 +2469,11 @@ AfterTriggerBeginXact(void)
24642469
8 * sizeof(AfterTriggerEventList));
24652470
afterTriggers->maxquerydepth = 8;
24662471

2472+
/* Context for events is created only when needed */
2473+
afterTriggers->event_cxt = NULL;
2474+
24672475
/* Subtransaction stack is empty until/unless needed */
2476+
afterTriggers->cxt_stack = NULL;
24682477
afterTriggers->state_stack = NULL;
24692478
afterTriggers->events_stack = NULL;
24702479
afterTriggers->depth_stack = NULL;
@@ -2626,8 +2635,18 @@ AfterTriggerEndXact(bool isCommit)
26262635
* Forget everything we know about AFTER triggers.
26272636
*
26282637
* Since all the info is in TopTransactionContext or children thereof, we
2629-
* need do nothing special to reclaim memory.
2638+
* don't really need to do anything to reclaim memory. However, the
2639+
* pending-events list could be large, and so it's useful to discard
2640+
* it as soon as possible --- especially if we are aborting because we
2641+
* ran out of memory for the list!
2642+
*
2643+
* (Note: any event_cxts of child subtransactions could also be
2644+
* deleted here, but we have no convenient way to find them, so we
2645+
* leave it to TopTransactionContext reset to clean them up.)
26302646
*/
2647+
if (afterTriggers && afterTriggers->event_cxt)
2648+
MemoryContextDelete(afterTriggers->event_cxt);
2649+
26312650
afterTriggers = NULL;
26322651
}
26332652

@@ -2649,7 +2668,10 @@ AfterTriggerBeginSubXact(void)
26492668
return;
26502669

26512670
/*
2652-
* Allocate more space in the stacks if needed.
2671+
* Allocate more space in the stacks if needed. (Note: because the
2672+
* minimum nest level of a subtransaction is 2, we waste the first
2673+
* couple entries of each array; not worth the notational effort to
2674+
* avoid it.)
26532675
*/
26542676
while (my_level >= afterTriggers->maxtransdepth)
26552677
{
@@ -2660,6 +2682,8 @@ AfterTriggerBeginSubXact(void)
26602682
old_cxt = MemoryContextSwitchTo(TopTransactionContext);
26612683

26622684
#define DEFTRIG_INITALLOC 8
2685+
afterTriggers->cxt_stack = (MemoryContext *)
2686+
palloc(DEFTRIG_INITALLOC * sizeof(MemoryContext));
26632687
afterTriggers->state_stack = (SetConstraintState *)
26642688
palloc(DEFTRIG_INITALLOC * sizeof(SetConstraintState));
26652689
afterTriggers->events_stack = (AfterTriggerEventList *)
@@ -2677,6 +2701,9 @@ AfterTriggerBeginSubXact(void)
26772701
/* repalloc will keep the stacks in the same context */
26782702
int new_alloc = afterTriggers->maxtransdepth * 2;
26792703

2704+
afterTriggers->cxt_stack = (MemoryContext *)
2705+
repalloc(afterTriggers->cxt_stack,
2706+
new_alloc * sizeof(MemoryContext));
26802707
afterTriggers->state_stack = (SetConstraintState *)
26812708
repalloc(afterTriggers->state_stack,
26822709
new_alloc * sizeof(SetConstraintState));
@@ -2695,8 +2722,10 @@ AfterTriggerBeginSubXact(void)
26952722

26962723
/*
26972724
* Push the current information into the stack. The SET CONSTRAINTS state
2698-
* is not saved until/unless changed.
2725+
* is not saved until/unless changed. Likewise, we don't make a
2726+
* per-subtransaction event context until needed.
26992727
*/
2728+
afterTriggers->cxt_stack[my_level] = NULL;
27002729
afterTriggers->state_stack[my_level] = NULL;
27012730
afterTriggers->events_stack[my_level] = afterTriggers->events;
27022731
afterTriggers->depth_stack[my_level] = afterTriggers->query_depth;
@@ -2742,7 +2771,23 @@ AfterTriggerEndSubXact(bool isCommit)
27422771
else
27432772
{
27442773
/*
2745-
* Aborting --- restore the pointers from the stacks.
2774+
* Aborting. We don't really need to release the subxact's event_cxt,
2775+
* since it will go away anyway when CurTransactionContext gets reset,
2776+
* but doing so early in subxact abort helps free space we might need.
2777+
*
2778+
* (Note: any event_cxts of child subtransactions could also be
2779+
* deleted here, but we have no convenient way to find them, so we
2780+
* leave it to CurTransactionContext reset to clean them up.)
2781+
*/
2782+
if (afterTriggers->cxt_stack[my_level])
2783+
{
2784+
MemoryContextDelete(afterTriggers->cxt_stack[my_level]);
2785+
/* avoid double delete if repeated aborts */
2786+
afterTriggers->cxt_stack[my_level] = NULL;
2787+
}
2788+
2789+
/*
2790+
* Restore the pointers from the stacks.
27462791
*/
27472792
afterTriggers->events = afterTriggers->events_stack[my_level];
27482793
afterTriggers->query_depth = afterTriggers->depth_stack[my_level];
@@ -2753,11 +2798,6 @@ AfterTriggerEndSubXact(bool isCommit)
27532798
if (afterTriggers->events.tail != NULL)
27542799
afterTriggers->events.tail->ate_next = NULL;
27552800

2756-
/*
2757-
* We don't need to free the subtransaction's items, since the
2758-
* CurTransactionContext will be reset shortly.
2759-
*/
2760-
27612801
/*
27622802
* Restore the trigger state. If the saved state is NULL, then this
27632803
* subxact didn't save it, so it doesn't need restoring.
@@ -3204,6 +3244,8 @@ AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
32043244
{
32053245
Relation rel = relinfo->ri_RelationDesc;
32063246
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
3247+
int my_level = GetCurrentTransactionNestLevel();
3248+
MemoryContext *cxtptr;
32073249
AfterTriggerEvent new_event;
32083250
int i;
32093251
int ntriggers;
@@ -3294,12 +3336,29 @@ AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
32943336
}
32953337

32963338
/*
3297-
* Create a new event. We use the CurTransactionContext so the event
3298-
* will automatically go away if the subtransaction aborts.
3339+
* If we don't yet have an event context for the current (sub)xact,
3340+
* create one. Make it a child of CurTransactionContext to ensure it
3341+
* will go away if the subtransaction aborts.
3342+
*/
3343+
if (my_level > 1) /* subtransaction? */
3344+
{
3345+
Assert(my_level < afterTriggers->maxtransdepth);
3346+
cxtptr = &afterTriggers->cxt_stack[my_level];
3347+
}
3348+
else
3349+
cxtptr = &afterTriggers->event_cxt;
3350+
if (*cxtptr == NULL)
3351+
*cxtptr = AllocSetContextCreate(CurTransactionContext,
3352+
"AfterTriggerEvents",
3353+
ALLOCSET_DEFAULT_MINSIZE,
3354+
ALLOCSET_DEFAULT_INITSIZE,
3355+
ALLOCSET_DEFAULT_MAXSIZE);
3356+
3357+
/*
3358+
* Create a new event.
32993359
*/
33003360
new_event = (AfterTriggerEvent)
3301-
MemoryContextAlloc(CurTransactionContext,
3302-
sizeof(AfterTriggerEventData));
3361+
MemoryContextAlloc(*cxtptr, sizeof(AfterTriggerEventData));
33033362
new_event->ate_next = NULL;
33043363
new_event->ate_event =
33053364
(event & TRIGGER_EVENT_OPMASK) |

0 commit comments

Comments
 (0)