diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 86a39cfcce..6f21752b84 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -205,12 +205,23 @@ typedef struct 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 report_needed; /* true if any GUC_REPORT reports are needed */ - 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 void InitializeGUCOptionsFromEnvironment(void); 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 push_old_value(struct config_generic *gconf, GucAction action); 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) gconf->reset_source = PGC_S_DEFAULT; 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) { if (stack->source == PGC_S_FILE) @@ -1403,8 +1416,6 @@ InitializeGUCOptions(void) InitializeOneGUCOption(hentry->gucvar); } - guc_dirty = 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 @@ -1835,13 +1863,13 @@ pg_timezone_abbrev_initialize(void) void ResetAllOptions(void) { - HASH_SEQ_STATUS status; - GUCHashEntry *hentry; + dlist_mutable_iter iter; - hash_seq_init(&status, guc_hashtab); - while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL) + /* We need only consider GUCs not already at PGC_S_DEFAULT */ + 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 */ 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->srole = gconf->reset_srole; - if (gconf->flags & GUC_REPORT) + if ((gconf->flags & GUC_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 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); break; } - Assert(guc_dirty); /* must be set already */ return; } @@ -2011,10 +2063,9 @@ push_old_value(struct config_generic *gconf, GucAction action) stack->srole = gconf->srole; set_stack_value(gconf, &stack->prior); + if (gconf->stack == NULL) + slist_push_head(&guc_stack_list, &gconf->stack_link); gconf->stack = stack; - - /* Ensure we remember to pop at end of xact */ - guc_dirty = true; } @@ -2058,9 +2109,7 @@ NewGUCNestLevel(void) void AtEOXact_GUC(bool isCommit, int nestLevel) { - bool still_dirty; - HASH_SEQ_STATUS status; - GUCHashEntry *hentry; + slist_mutable_iter iter; /* * 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 + 1 && !isCommit))); - /* Quick exit if nothing's changed in this transaction */ - if (!guc_dirty) + /* We need only process GUCs having nonempty stacks */ + slist_foreach_modify(iter, &guc_stack_list) { - GUCNestLevel = nestLevel - 1; - return; - } - - still_dirty = false; - hash_seq_init(&status, guc_hashtab); - while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL) - { - struct config_generic *gconf = hentry->gucvar; + struct config_generic *gconf = slist_container(struct config_generic, + stack_link, iter.cur); GucStack *stack; /* @@ -2315,30 +2357,30 @@ AtEOXact_GUC(bool isCommit, int nestLevel) set_extra_field(gconf, &(stack->masked.extra), NULL); /* And restore source information */ - gconf->source = newsource; + set_guc_source(gconf, newsource); gconf->scontext = newscontext; 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; + if (prev == NULL) + slist_delete_current(&iter); pfree(stack); /* 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; - report_needed = true; + slist_push_head(&guc_report_list, &gconf->report_link); } } /* 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 */ GUCNestLevel = nestLevel - 1; } @@ -2383,8 +2425,6 @@ BeginReportingGUCOptions(void) if (conf->flags & GUC_REPORT) ReportGUCOption(conf); } - - report_needed = false; } /* @@ -2403,8 +2443,7 @@ BeginReportingGUCOptions(void) void ReportChangedGUCOptions(void) { - HASH_SEQ_STATUS status; - GUCHashEntry *hentry; + slist_mutable_iter iter; /* Quick exit if not (yet) enabled */ if (!reporting_enabled) @@ -2420,28 +2459,24 @@ ReportChangedGUCOptions(void) SetConfigOption("in_hot_standby", "false", PGC_INTERNAL, PGC_S_OVERRIDE); - /* Quick exit if no values have been changed */ - if (!report_needed) - return; - /* Transmit new values of interesting variables */ - hash_seq_init(&status, guc_hashtab); - while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL) + slist_foreach_modify(iter, &guc_report_list) { - 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)) - ReportGUCOption(conf); + Assert((conf->flags & GUC_REPORT) && (conf->status & GUC_NEEDS_REPORT)); + ReportGUCOption(conf); + conf->status &= ~GUC_NEEDS_REPORT; + slist_delete_current(&iter); } - - report_needed = false; } /* * ReportGUCOption: if appropriate, transmit option value to frontend * * 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 ReportGUCOption(struct config_generic *record) @@ -2468,8 +2503,6 @@ ReportGUCOption(struct config_generic *record) } pfree(val); - - record->status &= ~GUC_NEEDS_REPORT; } /* @@ -3524,7 +3557,7 @@ set_config_option_ext(const char *name, const char *value, *conf->variable = newval; set_extra_field(&conf->gen, &conf->gen.extra, newextra); - conf->gen.source = source; + set_guc_source(&conf->gen, source); conf->gen.scontext = context; conf->gen.srole = srole; } @@ -3622,7 +3655,7 @@ set_config_option_ext(const char *name, const char *value, *conf->variable = newval; set_extra_field(&conf->gen, &conf->gen.extra, newextra); - conf->gen.source = source; + set_guc_source(&conf->gen, source); conf->gen.scontext = context; conf->gen.srole = srole; } @@ -3720,7 +3753,7 @@ set_config_option_ext(const char *name, const char *value, *conf->variable = newval; set_extra_field(&conf->gen, &conf->gen.extra, newextra); - conf->gen.source = source; + set_guc_source(&conf->gen, source); conf->gen.scontext = context; 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_extra_field(&conf->gen, &conf->gen.extra, newextra); - conf->gen.source = source; + set_guc_source(&conf->gen, source); conf->gen.scontext = context; conf->gen.srole = srole; } @@ -3947,7 +3980,7 @@ set_config_option_ext(const char *name, const char *value, *conf->variable = newval; set_extra_field(&conf->gen, &conf->gen.extra, newextra); - conf->gen.source = source; + set_guc_source(&conf->gen, source); conf->gen.scontext = context; 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; - report_needed = true; + slist_push_head(&guc_report_list, &record->report_link); } return changeVal ? 1 : -1; @@ -4663,6 +4697,11 @@ define_custom_variable(struct config_generic *variable) hentry->gucname = name; 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 * variable. Essentially, we need to duplicate all the active and stacked @@ -4794,7 +4833,11 @@ reapply_stacked_values(struct config_generic *variable, (void) set_config_option_ext(name, curvalue, curscontext, cursource, cursrole, GUC_ACTION_SET, true, WARNING, false); - variable->stack = NULL; + if (variable->stack != NULL) + { + slist_delete(&guc_stack_list, &variable->stack_link); + variable->stack = NULL; + } } } } @@ -4978,10 +5021,13 @@ MarkGUCPrefixReserved(const char *className) var->name), errdetail("\"%s\" is now a reserved prefix.", className))); + /* Remove it from the hash table */ hash_search(guc_hashtab, &var->name, HASH_REMOVE, 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) { struct config_generic **result; - HASH_SEQ_STATUS status; - GUCHashEntry *hentry; + dlist_iter iter; *num = 0; @@ -5013,10 +5058,11 @@ get_explain_guc_options(int *num) */ result = palloc(sizeof(struct config_generic *) * hash_get_num_entries(guc_hashtab)); - hash_seq_init(&status, guc_hashtab); - while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL) + /* We need only consider GUCs with source not PGC_S_DEFAULT */ + 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; /* return only parameters marked for inclusion in explain */ @@ -5251,8 +5297,7 @@ ShowGUCOption(struct config_generic *record, bool use_units) static void write_one_nondefault_variable(FILE *fp, struct config_generic *gconf) { - if (gconf->source == PGC_S_DEFAULT) - return; + Assert(gconf->source != PGC_S_DEFAULT); fprintf(fp, "%s", gconf->name); fputc(0, fp); @@ -5321,8 +5366,7 @@ write_nondefault_variables(GucContext context) { int elevel; FILE *fp; - HASH_SEQ_STATUS status; - GUCHashEntry *hentry; + dlist_iter iter; Assert(context == PGC_POSTMASTER || context == PGC_SIGHUP); @@ -5341,10 +5385,13 @@ write_nondefault_variables(GucContext context) return; } - hash_seq_init(&status, guc_hashtab); - while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL) + /* We need only consider GUCs with source not PGC_S_DEFAULT */ + 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)) @@ -5618,17 +5665,23 @@ Size EstimateGUCStateSpace(void) { Size size; - HASH_SEQ_STATUS status; - GUCHashEntry *hentry; + dlist_iter iter; /* Add space reqd for saving the data size of the guc state */ size = sizeof(Size); - /* Add up the space needed for each GUC variable */ - hash_seq_init(&status, guc_hashtab); - while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL) - size = add_size(size, - estimate_variable_size(hentry->gucvar)); + /* + * Add up the space needed for each GUC variable. + * + * We need only process non-default GUCs. + */ + 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; } @@ -5767,17 +5820,21 @@ SerializeGUCState(Size maxsize, char *start_address) char *curptr; Size actual_size; Size bytes_left; - HASH_SEQ_STATUS status; - GUCHashEntry *hentry; + dlist_iter iter; /* Reserve space for saving the actual size of the guc state */ Assert(maxsize > sizeof(actual_size)); curptr = start_address + sizeof(actual_size); bytes_left = maxsize - sizeof(actual_size); - hash_seq_init(&status, guc_hashtab); - while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL) - serialize_variable(&curptr, &bytes_left, hentry->gucvar); + /* We need only consider GUCs with source not PGC_S_DEFAULT */ + dlist_foreach(iter, &guc_nondef_list) + { + 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. */ actual_size = maxsize - bytes_left - sizeof(actual_size); @@ -5862,8 +5919,7 @@ RestoreGUCState(void *gucstate) char *srcptr = (char *) gucstate; char *srcend; Size len; - HASH_SEQ_STATUS status; - GUCHashEntry *hentry; + dlist_mutable_iter iter; 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 * source-priority comparisons. */ - hash_seq_init(&status, guc_hashtab); - 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); /* Do nothing if non-shippable or if already at PGC_S_DEFAULT. */ if (can_skip_gucvar(gconf)) @@ -5902,7 +5958,8 @@ RestoreGUCState(void *gucstate) * 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 * 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); guc_free(gconf->extra); @@ -5954,6 +6011,8 @@ RestoreGUCState(void *gucstate) break; } } + /* Remove it from any lists it's in. */ + RemoveGUCFromLists(gconf); /* Now we can reset the struct to PGS_S_DEFAULT state. */ InitializeOneGUCOption(gconf); } diff --git a/src/include/utils/guc_tables.h b/src/include/utils/guc_tables.h index 99740e7e48..51f7f09fde 100644 --- a/src/include/utils/guc_tables.h +++ b/src/include/utils/guc_tables.h @@ -14,6 +14,7 @@ #ifndef GUC_TABLES_H #define GUC_TABLES_H 1 +#include "lib/ilist.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 * 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 * values, although in principle they belong with some stacked value if the * 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 */ GucStack *stack; /* stacked prior values */ 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 * to client (NULL if not yet sent) */ char *sourcefile; /* file current setting is from (NULL if not