Add auxiliary lists to GUC data structures for better performance.

The previous patch made addition of new GUCs cheap, but other GUC
operations aren't improved and indeed get a bit slower, because
hash_seq_search() is slower than just scanning a pointer array.

However, most performance-critical GUC operations only need
to touch a relatively small fraction of the GUCs; especially
so for AtEOXact_GUC().  We can improve matters at the cost
of a bit more space by adding dlist or slist links to the
GUC data structures.  This patch invents lists that track

(1) all GUCs with non-default "source";

(2) all GUCs with nonempty state stack (implying they've
been changed in the current transaction);

(3) all GUCs due for reporting to the client.

All of guc.c's performance-critical cases can make use of one or
another of these lists to avoid searching the whole hash table.
In particular, the stack list means that transaction end
doesn't take time proportional to the number of GUCs, but
only to the number changed in the current transaction.

Discussion: https://postgr.es/m/2982579.1662416866@sss.pgh.pa.us
This commit is contained in:
Tom Lane 2022-10-14 12:36:14 -04:00
parent 3057465acf
commit f13b2088fa
2 changed files with 169 additions and 98 deletions

View File

@ -205,12 +205,23 @@ typedef struct
static HTAB *guc_hashtab; /* entries are GUCHashEntrys */ static HTAB *guc_hashtab; /* entries are GUCHashEntrys */
static bool guc_dirty; /* true if need to do commit/abort work */ /*
* In addition to the hash table, variables having certain properties are
* linked into these lists, so that we can find them without scanning the
* whole hash table. In most applications, only a small fraction of the
* GUCs appear in these lists at any given time. The usage of the stack
* and report lists is stylized enough that they can be slists, but the
* nondef list has to be a dlist to avoid O(N) deletes in common cases.
*/
static dlist_head guc_nondef_list; /* list of variables that have source
* different from PGC_S_DEFAULT */
static slist_head guc_stack_list; /* list of variables that have non-NULL
* stack */
static slist_head guc_report_list; /* list of variables that have the
* GUC_NEEDS_REPORT bit set in status */
static bool reporting_enabled; /* true to enable GUC_REPORT */ static bool reporting_enabled; /* true to enable GUC_REPORT */
static bool report_needed; /* true if any GUC_REPORT reports are needed */
static int GUCNestLevel = 0; /* 1 when in main transaction */ static int GUCNestLevel = 0; /* 1 when in main transaction */
@ -219,6 +230,8 @@ static uint32 guc_name_hash(const void *key, Size keysize);
static int guc_name_match(const void *key1, const void *key2, Size keysize); static int guc_name_match(const void *key1, const void *key2, Size keysize);
static void InitializeGUCOptionsFromEnvironment(void); static void InitializeGUCOptionsFromEnvironment(void);
static void InitializeOneGUCOption(struct config_generic *gconf); static void InitializeOneGUCOption(struct config_generic *gconf);
static void RemoveGUCFromLists(struct config_generic *gconf);
static void set_guc_source(struct config_generic *gconf, GucSource newsource);
static void pg_timezone_abbrev_initialize(void); static void pg_timezone_abbrev_initialize(void);
static void push_old_value(struct config_generic *gconf, GucAction action); static void push_old_value(struct config_generic *gconf, GucAction action);
static void ReportGUCOption(struct config_generic *record); static void ReportGUCOption(struct config_generic *record);
@ -465,7 +478,7 @@ ProcessConfigFileInternal(GucContext context, bool applySettings, int elevel)
if (gconf->reset_source == PGC_S_FILE) if (gconf->reset_source == PGC_S_FILE)
gconf->reset_source = PGC_S_DEFAULT; gconf->reset_source = PGC_S_DEFAULT;
if (gconf->source == PGC_S_FILE) if (gconf->source == PGC_S_FILE)
gconf->source = PGC_S_DEFAULT; set_guc_source(gconf, PGC_S_DEFAULT);
for (stack = gconf->stack; stack; stack = stack->prev) for (stack = gconf->stack; stack; stack = stack->prev)
{ {
if (stack->source == PGC_S_FILE) if (stack->source == PGC_S_FILE)
@ -1403,8 +1416,6 @@ InitializeGUCOptions(void)
InitializeOneGUCOption(hentry->gucvar); InitializeOneGUCOption(hentry->gucvar);
} }
guc_dirty = false;
reporting_enabled = false; reporting_enabled = false;
/* /*
@ -1600,6 +1611,23 @@ InitializeOneGUCOption(struct config_generic *gconf)
} }
} }
/*
* Summarily remove a GUC variable from any linked lists it's in.
*
* We use this in cases where the variable is about to be deleted or reset.
* These aren't common operations, so it's okay if this is a bit slow.
*/
static void
RemoveGUCFromLists(struct config_generic *gconf)
{
if (gconf->source != PGC_S_DEFAULT)
dlist_delete(&gconf->nondef_link);
if (gconf->stack != NULL)
slist_delete(&guc_stack_list, &gconf->stack_link);
if (gconf->status & GUC_NEEDS_REPORT)
slist_delete(&guc_report_list, &gconf->report_link);
}
/* /*
* Select the configuration files and data directory to be used, and * Select the configuration files and data directory to be used, and
@ -1835,13 +1863,13 @@ pg_timezone_abbrev_initialize(void)
void void
ResetAllOptions(void) ResetAllOptions(void)
{ {
HASH_SEQ_STATUS status; dlist_mutable_iter iter;
GUCHashEntry *hentry;
hash_seq_init(&status, guc_hashtab); /* We need only consider GUCs not already at PGC_S_DEFAULT */
while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL) dlist_foreach_modify(iter, &guc_nondef_list)
{ {
struct config_generic *gconf = hentry->gucvar; struct config_generic *gconf = dlist_container(struct config_generic,
nondef_link, iter.cur);
/* Don't reset non-SET-able values */ /* Don't reset non-SET-able values */
if (gconf->context != PGC_SUSET && if (gconf->context != PGC_SUSET &&
@ -1921,19 +1949,44 @@ ResetAllOptions(void)
} }
} }
gconf->source = gconf->reset_source; set_guc_source(gconf, gconf->reset_source);
gconf->scontext = gconf->reset_scontext; gconf->scontext = gconf->reset_scontext;
gconf->srole = gconf->reset_srole; gconf->srole = gconf->reset_srole;
if (gconf->flags & GUC_REPORT) if ((gconf->flags & GUC_REPORT) && !(gconf->status & GUC_NEEDS_REPORT))
{ {
gconf->status |= GUC_NEEDS_REPORT; gconf->status |= GUC_NEEDS_REPORT;
report_needed = true; slist_push_head(&guc_report_list, &gconf->report_link);
} }
} }
} }
/*
* Apply a change to a GUC variable's "source" field.
*
* Use this rather than just assigning, to ensure that the variable's
* membership in guc_nondef_list is updated correctly.
*/
static void
set_guc_source(struct config_generic *gconf, GucSource newsource)
{
/* Adjust nondef list membership if appropriate for change */
if (gconf->source == PGC_S_DEFAULT)
{
if (newsource != PGC_S_DEFAULT)
dlist_push_tail(&guc_nondef_list, &gconf->nondef_link);
}
else
{
if (newsource == PGC_S_DEFAULT)
dlist_delete(&gconf->nondef_link);
}
/* Now update the source field */
gconf->source = newsource;
}
/* /*
* push_old_value * push_old_value
* Push previous state during transactional assignment to a GUC variable. * Push previous state during transactional assignment to a GUC variable.
@ -1980,7 +2033,6 @@ push_old_value(struct config_generic *gconf, GucAction action)
Assert(stack->state == GUC_SAVE); Assert(stack->state == GUC_SAVE);
break; break;
} }
Assert(guc_dirty); /* must be set already */
return; return;
} }
@ -2011,10 +2063,9 @@ push_old_value(struct config_generic *gconf, GucAction action)
stack->srole = gconf->srole; stack->srole = gconf->srole;
set_stack_value(gconf, &stack->prior); set_stack_value(gconf, &stack->prior);
if (gconf->stack == NULL)
slist_push_head(&guc_stack_list, &gconf->stack_link);
gconf->stack = stack; gconf->stack = stack;
/* Ensure we remember to pop at end of xact */
guc_dirty = true;
} }
@ -2058,9 +2109,7 @@ NewGUCNestLevel(void)
void void
AtEOXact_GUC(bool isCommit, int nestLevel) AtEOXact_GUC(bool isCommit, int nestLevel)
{ {
bool still_dirty; slist_mutable_iter iter;
HASH_SEQ_STATUS status;
GUCHashEntry *hentry;
/* /*
* Note: it's possible to get here with GUCNestLevel == nestLevel-1 during * Note: it's possible to get here with GUCNestLevel == nestLevel-1 during
@ -2071,18 +2120,11 @@ AtEOXact_GUC(bool isCommit, int nestLevel)
(nestLevel <= GUCNestLevel || (nestLevel <= GUCNestLevel ||
(nestLevel == GUCNestLevel + 1 && !isCommit))); (nestLevel == GUCNestLevel + 1 && !isCommit)));
/* Quick exit if nothing's changed in this transaction */ /* We need only process GUCs having nonempty stacks */
if (!guc_dirty) slist_foreach_modify(iter, &guc_stack_list)
{ {
GUCNestLevel = nestLevel - 1; struct config_generic *gconf = slist_container(struct config_generic,
return; stack_link, iter.cur);
}
still_dirty = false;
hash_seq_init(&status, guc_hashtab);
while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL)
{
struct config_generic *gconf = hentry->gucvar;
GucStack *stack; GucStack *stack;
/* /*
@ -2315,30 +2357,30 @@ AtEOXact_GUC(bool isCommit, int nestLevel)
set_extra_field(gconf, &(stack->masked.extra), NULL); set_extra_field(gconf, &(stack->masked.extra), NULL);
/* And restore source information */ /* And restore source information */
gconf->source = newsource; set_guc_source(gconf, newsource);
gconf->scontext = newscontext; gconf->scontext = newscontext;
gconf->srole = newsrole; gconf->srole = newsrole;
} }
/* Finish popping the state stack */ /*
* Pop the GUC's state stack; if it's now empty, remove the GUC
* from guc_stack_list.
*/
gconf->stack = prev; gconf->stack = prev;
if (prev == NULL)
slist_delete_current(&iter);
pfree(stack); pfree(stack);
/* Report new value if we changed it */ /* Report new value if we changed it */
if (changed && (gconf->flags & GUC_REPORT)) if (changed && (gconf->flags & GUC_REPORT) &&
!(gconf->status & GUC_NEEDS_REPORT))
{ {
gconf->status |= GUC_NEEDS_REPORT; gconf->status |= GUC_NEEDS_REPORT;
report_needed = true; slist_push_head(&guc_report_list, &gconf->report_link);
} }
} /* end of stack-popping loop */ } /* end of stack-popping loop */
if (stack != NULL)
still_dirty = true;
} }
/* If there are no remaining stack entries, we can reset guc_dirty */
guc_dirty = still_dirty;
/* Update nesting level */ /* Update nesting level */
GUCNestLevel = nestLevel - 1; GUCNestLevel = nestLevel - 1;
} }
@ -2383,8 +2425,6 @@ BeginReportingGUCOptions(void)
if (conf->flags & GUC_REPORT) if (conf->flags & GUC_REPORT)
ReportGUCOption(conf); ReportGUCOption(conf);
} }
report_needed = false;
} }
/* /*
@ -2403,8 +2443,7 @@ BeginReportingGUCOptions(void)
void void
ReportChangedGUCOptions(void) ReportChangedGUCOptions(void)
{ {
HASH_SEQ_STATUS status; slist_mutable_iter iter;
GUCHashEntry *hentry;
/* Quick exit if not (yet) enabled */ /* Quick exit if not (yet) enabled */
if (!reporting_enabled) if (!reporting_enabled)
@ -2420,28 +2459,24 @@ ReportChangedGUCOptions(void)
SetConfigOption("in_hot_standby", "false", SetConfigOption("in_hot_standby", "false",
PGC_INTERNAL, PGC_S_OVERRIDE); PGC_INTERNAL, PGC_S_OVERRIDE);
/* Quick exit if no values have been changed */
if (!report_needed)
return;
/* Transmit new values of interesting variables */ /* Transmit new values of interesting variables */
hash_seq_init(&status, guc_hashtab); slist_foreach_modify(iter, &guc_report_list)
while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL)
{ {
struct config_generic *conf = hentry->gucvar; struct config_generic *conf = slist_container(struct config_generic,
report_link, iter.cur);
if ((conf->flags & GUC_REPORT) && (conf->status & GUC_NEEDS_REPORT)) Assert((conf->flags & GUC_REPORT) && (conf->status & GUC_NEEDS_REPORT));
ReportGUCOption(conf); ReportGUCOption(conf);
conf->status &= ~GUC_NEEDS_REPORT;
slist_delete_current(&iter);
} }
report_needed = false;
} }
/* /*
* ReportGUCOption: if appropriate, transmit option value to frontend * ReportGUCOption: if appropriate, transmit option value to frontend
* *
* We need not transmit the value if it's the same as what we last * We need not transmit the value if it's the same as what we last
* transmitted. However, clear the NEEDS_REPORT flag in any case. * transmitted.
*/ */
static void static void
ReportGUCOption(struct config_generic *record) ReportGUCOption(struct config_generic *record)
@ -2468,8 +2503,6 @@ ReportGUCOption(struct config_generic *record)
} }
pfree(val); pfree(val);
record->status &= ~GUC_NEEDS_REPORT;
} }
/* /*
@ -3524,7 +3557,7 @@ set_config_option_ext(const char *name, const char *value,
*conf->variable = newval; *conf->variable = newval;
set_extra_field(&conf->gen, &conf->gen.extra, set_extra_field(&conf->gen, &conf->gen.extra,
newextra); newextra);
conf->gen.source = source; set_guc_source(&conf->gen, source);
conf->gen.scontext = context; conf->gen.scontext = context;
conf->gen.srole = srole; conf->gen.srole = srole;
} }
@ -3622,7 +3655,7 @@ set_config_option_ext(const char *name, const char *value,
*conf->variable = newval; *conf->variable = newval;
set_extra_field(&conf->gen, &conf->gen.extra, set_extra_field(&conf->gen, &conf->gen.extra,
newextra); newextra);
conf->gen.source = source; set_guc_source(&conf->gen, source);
conf->gen.scontext = context; conf->gen.scontext = context;
conf->gen.srole = srole; conf->gen.srole = srole;
} }
@ -3720,7 +3753,7 @@ set_config_option_ext(const char *name, const char *value,
*conf->variable = newval; *conf->variable = newval;
set_extra_field(&conf->gen, &conf->gen.extra, set_extra_field(&conf->gen, &conf->gen.extra,
newextra); newextra);
conf->gen.source = source; set_guc_source(&conf->gen, source);
conf->gen.scontext = context; conf->gen.scontext = context;
conf->gen.srole = srole; conf->gen.srole = srole;
} }
@ -3844,7 +3877,7 @@ set_config_option_ext(const char *name, const char *value,
set_string_field(conf, conf->variable, newval); set_string_field(conf, conf->variable, newval);
set_extra_field(&conf->gen, &conf->gen.extra, set_extra_field(&conf->gen, &conf->gen.extra,
newextra); newextra);
conf->gen.source = source; set_guc_source(&conf->gen, source);
conf->gen.scontext = context; conf->gen.scontext = context;
conf->gen.srole = srole; conf->gen.srole = srole;
} }
@ -3947,7 +3980,7 @@ set_config_option_ext(const char *name, const char *value,
*conf->variable = newval; *conf->variable = newval;
set_extra_field(&conf->gen, &conf->gen.extra, set_extra_field(&conf->gen, &conf->gen.extra,
newextra); newextra);
conf->gen.source = source; set_guc_source(&conf->gen, source);
conf->gen.scontext = context; conf->gen.scontext = context;
conf->gen.srole = srole; conf->gen.srole = srole;
} }
@ -3987,10 +4020,11 @@ set_config_option_ext(const char *name, const char *value,
} }
} }
if (changeVal && (record->flags & GUC_REPORT)) if (changeVal && (record->flags & GUC_REPORT) &&
!(record->status & GUC_NEEDS_REPORT))
{ {
record->status |= GUC_NEEDS_REPORT; record->status |= GUC_NEEDS_REPORT;
report_needed = true; slist_push_head(&guc_report_list, &record->report_link);
} }
return changeVal ? 1 : -1; return changeVal ? 1 : -1;
@ -4663,6 +4697,11 @@ define_custom_variable(struct config_generic *variable)
hentry->gucname = name; hentry->gucname = name;
hentry->gucvar = variable; hentry->gucvar = variable;
/*
* Remove the placeholder from any lists it's in, too.
*/
RemoveGUCFromLists(&pHolder->gen);
/* /*
* Assign the string value(s) stored in the placeholder to the real * Assign the string value(s) stored in the placeholder to the real
* variable. Essentially, we need to duplicate all the active and stacked * variable. Essentially, we need to duplicate all the active and stacked
@ -4794,9 +4833,13 @@ reapply_stacked_values(struct config_generic *variable,
(void) set_config_option_ext(name, curvalue, (void) set_config_option_ext(name, curvalue,
curscontext, cursource, cursrole, curscontext, cursource, cursrole,
GUC_ACTION_SET, true, WARNING, false); GUC_ACTION_SET, true, WARNING, false);
if (variable->stack != NULL)
{
slist_delete(&guc_stack_list, &variable->stack_link);
variable->stack = NULL; variable->stack = NULL;
} }
} }
}
} }
/* /*
@ -4978,10 +5021,13 @@ MarkGUCPrefixReserved(const char *className)
var->name), var->name),
errdetail("\"%s\" is now a reserved prefix.", errdetail("\"%s\" is now a reserved prefix.",
className))); className)));
/* Remove it from the hash table */
hash_search(guc_hashtab, hash_search(guc_hashtab,
&var->name, &var->name,
HASH_REMOVE, HASH_REMOVE,
NULL); NULL);
/* Remove it from any lists it's in, too */
RemoveGUCFromLists(var);
} }
} }
@ -5002,8 +5048,7 @@ struct config_generic **
get_explain_guc_options(int *num) get_explain_guc_options(int *num)
{ {
struct config_generic **result; struct config_generic **result;
HASH_SEQ_STATUS status; dlist_iter iter;
GUCHashEntry *hentry;
*num = 0; *num = 0;
@ -5013,10 +5058,11 @@ get_explain_guc_options(int *num)
*/ */
result = palloc(sizeof(struct config_generic *) * hash_get_num_entries(guc_hashtab)); result = palloc(sizeof(struct config_generic *) * hash_get_num_entries(guc_hashtab));
hash_seq_init(&status, guc_hashtab); /* We need only consider GUCs with source not PGC_S_DEFAULT */
while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL) dlist_foreach(iter, &guc_nondef_list)
{ {
struct config_generic *conf = hentry->gucvar; struct config_generic *conf = dlist_container(struct config_generic,
nondef_link, iter.cur);
bool modified; bool modified;
/* return only parameters marked for inclusion in explain */ /* return only parameters marked for inclusion in explain */
@ -5251,8 +5297,7 @@ ShowGUCOption(struct config_generic *record, bool use_units)
static void static void
write_one_nondefault_variable(FILE *fp, struct config_generic *gconf) write_one_nondefault_variable(FILE *fp, struct config_generic *gconf)
{ {
if (gconf->source == PGC_S_DEFAULT) Assert(gconf->source != PGC_S_DEFAULT);
return;
fprintf(fp, "%s", gconf->name); fprintf(fp, "%s", gconf->name);
fputc(0, fp); fputc(0, fp);
@ -5321,8 +5366,7 @@ write_nondefault_variables(GucContext context)
{ {
int elevel; int elevel;
FILE *fp; FILE *fp;
HASH_SEQ_STATUS status; dlist_iter iter;
GUCHashEntry *hentry;
Assert(context == PGC_POSTMASTER || context == PGC_SIGHUP); Assert(context == PGC_POSTMASTER || context == PGC_SIGHUP);
@ -5341,10 +5385,13 @@ write_nondefault_variables(GucContext context)
return; return;
} }
hash_seq_init(&status, guc_hashtab); /* We need only consider GUCs with source not PGC_S_DEFAULT */
while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL) dlist_foreach(iter, &guc_nondef_list)
{ {
write_one_nondefault_variable(fp, hentry->gucvar); struct config_generic *gconf = dlist_container(struct config_generic,
nondef_link, iter.cur);
write_one_nondefault_variable(fp, gconf);
} }
if (FreeFile(fp)) if (FreeFile(fp))
@ -5618,17 +5665,23 @@ Size
EstimateGUCStateSpace(void) EstimateGUCStateSpace(void)
{ {
Size size; Size size;
HASH_SEQ_STATUS status; dlist_iter iter;
GUCHashEntry *hentry;
/* Add space reqd for saving the data size of the guc state */ /* Add space reqd for saving the data size of the guc state */
size = sizeof(Size); size = sizeof(Size);
/* Add up the space needed for each GUC variable */ /*
hash_seq_init(&status, guc_hashtab); * Add up the space needed for each GUC variable.
while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL) *
size = add_size(size, * We need only process non-default GUCs.
estimate_variable_size(hentry->gucvar)); */
dlist_foreach(iter, &guc_nondef_list)
{
struct config_generic *gconf = dlist_container(struct config_generic,
nondef_link, iter.cur);
size = add_size(size, estimate_variable_size(gconf));
}
return size; return size;
} }
@ -5767,17 +5820,21 @@ SerializeGUCState(Size maxsize, char *start_address)
char *curptr; char *curptr;
Size actual_size; Size actual_size;
Size bytes_left; Size bytes_left;
HASH_SEQ_STATUS status; dlist_iter iter;
GUCHashEntry *hentry;
/* Reserve space for saving the actual size of the guc state */ /* Reserve space for saving the actual size of the guc state */
Assert(maxsize > sizeof(actual_size)); Assert(maxsize > sizeof(actual_size));
curptr = start_address + sizeof(actual_size); curptr = start_address + sizeof(actual_size);
bytes_left = maxsize - sizeof(actual_size); bytes_left = maxsize - sizeof(actual_size);
hash_seq_init(&status, guc_hashtab); /* We need only consider GUCs with source not PGC_S_DEFAULT */
while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL) dlist_foreach(iter, &guc_nondef_list)
serialize_variable(&curptr, &bytes_left, hentry->gucvar); {
struct config_generic *gconf = dlist_container(struct config_generic,
nondef_link, iter.cur);
serialize_variable(&curptr, &bytes_left, gconf);
}
/* Store actual size without assuming alignment of start_address. */ /* Store actual size without assuming alignment of start_address. */
actual_size = maxsize - bytes_left - sizeof(actual_size); actual_size = maxsize - bytes_left - sizeof(actual_size);
@ -5862,8 +5919,7 @@ RestoreGUCState(void *gucstate)
char *srcptr = (char *) gucstate; char *srcptr = (char *) gucstate;
char *srcend; char *srcend;
Size len; Size len;
HASH_SEQ_STATUS status; dlist_mutable_iter iter;
GUCHashEntry *hentry;
ErrorContextCallback error_context_callback; ErrorContextCallback error_context_callback;
/* /*
@ -5888,10 +5944,10 @@ RestoreGUCState(void *gucstate)
* also ensures that set_config_option won't refuse to set them because of * also ensures that set_config_option won't refuse to set them because of
* source-priority comparisons. * source-priority comparisons.
*/ */
hash_seq_init(&status, guc_hashtab); dlist_foreach_modify(iter, &guc_nondef_list)
while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL)
{ {
struct config_generic *gconf = hentry->gucvar; struct config_generic *gconf = dlist_container(struct config_generic,
nondef_link, iter.cur);
/* Do nothing if non-shippable or if already at PGC_S_DEFAULT. */ /* Do nothing if non-shippable or if already at PGC_S_DEFAULT. */
if (can_skip_gucvar(gconf)) if (can_skip_gucvar(gconf))
@ -5902,7 +5958,8 @@ RestoreGUCState(void *gucstate)
* first we must free any existing subsidiary data to avoid leaking * first we must free any existing subsidiary data to avoid leaking
* memory. The stack must be empty, but we have to clean up all other * memory. The stack must be empty, but we have to clean up all other
* fields. Beware that there might be duplicate value or "extra" * fields. Beware that there might be duplicate value or "extra"
* pointers. * pointers. We also have to be sure to take it out of any lists it's
* in.
*/ */
Assert(gconf->stack == NULL); Assert(gconf->stack == NULL);
guc_free(gconf->extra); guc_free(gconf->extra);
@ -5954,6 +6011,8 @@ RestoreGUCState(void *gucstate)
break; break;
} }
} }
/* Remove it from any lists it's in. */
RemoveGUCFromLists(gconf);
/* Now we can reset the struct to PGS_S_DEFAULT state. */ /* Now we can reset the struct to PGS_S_DEFAULT state. */
InitializeOneGUCOption(gconf); InitializeOneGUCOption(gconf);
} }

View File

@ -14,6 +14,7 @@
#ifndef GUC_TABLES_H #ifndef GUC_TABLES_H
#define GUC_TABLES_H 1 #define GUC_TABLES_H 1
#include "lib/ilist.h"
#include "utils/guc.h" #include "utils/guc.h"
/* /*
@ -138,6 +139,11 @@ typedef struct guc_stack
* if the value came from an internal source or the config file. Similarly * if the value came from an internal source or the config file. Similarly
* for reset_srole (which is usually BOOTSTRAP_SUPERUSERID, but not always). * for reset_srole (which is usually BOOTSTRAP_SUPERUSERID, but not always).
* *
* Variables that are currently of active interest for maintenance
* operations are linked into various lists using the xxx_link fields.
* The link fields are unused/garbage in variables not currently having
* the specified properties.
*
* Note that sourcefile/sourceline are kept here, and not pushed into stacked * Note that sourcefile/sourceline are kept here, and not pushed into stacked
* values, although in principle they belong with some stacked value if the * values, although in principle they belong with some stacked value if the
* active value is session- or transaction-local. This is to avoid bloating * active value is session- or transaction-local. This is to avoid bloating
@ -163,6 +169,12 @@ struct config_generic
Oid reset_srole; /* role that set the reset value */ Oid reset_srole; /* role that set the reset value */
GucStack *stack; /* stacked prior values */ GucStack *stack; /* stacked prior values */
void *extra; /* "extra" pointer for current actual value */ void *extra; /* "extra" pointer for current actual value */
dlist_node nondef_link; /* list link for variables that have source
* different from PGC_S_DEFAULT */
slist_node stack_link; /* list link for variables that have non-NULL
* stack */
slist_node report_link; /* list link for variables that have the
* GUC_NEEDS_REPORT bit set in status */
char *last_reported; /* if variable is GUC_REPORT, value last sent char *last_reported; /* if variable is GUC_REPORT, value last sent
* to client (NULL if not yet sent) */ * to client (NULL if not yet sent) */
char *sourcefile; /* file current setting is from (NULL if not char *sourcefile; /* file current setting is from (NULL if not