Commit f0c2a5bb authored by Tom Lane's avatar Tom Lane

Avoid leaking memory in RestoreGUCState(), and improve comments.

RestoreGUCState applied InitializeOneGUCOption to already-live
GUC entries, causing any malloc'd subsidiary data to be forgotten.
We do want the effect of resetting the GUC to its compiled-in
default, and InitializeOneGUCOption seems like the best way to do
that, so add code to free any existing subsidiary data beforehand.

The interaction between can_skip_gucvar, SerializeGUCState, and
RestoreGUCState is way more subtle than their opaque comments
would suggest to an unwary reader.  Rewrite and enlarge the
comments to try to make it clearer what's happening.

Remove a long-obsolete assertion in read_nondefault_variables: the
behavior of set_config_option hasn't depended on IsInitProcessingMode
since f5d9698a installed a better way of controlling it.

Although this is fixing a clear memory leak, the leak is quite unlikely
to involve any large amount of data, and it can only happen once in the
lifetime of a worker process.  So it seems unnecessary to take any
risk of back-patching.

Discussion: https://postgr.es/m/4105247.1616174862@sss.pgh.pa.us
parent 61752afb
...@@ -7121,6 +7121,10 @@ parse_and_validate_value(struct config_generic *record, ...@@ -7121,6 +7121,10 @@ parse_and_validate_value(struct config_generic *record,
* its standard choice of ereport level. However some callers need to be * its standard choice of ereport level. However some callers need to be
* able to override that choice; they should pass the ereport level to use. * able to override that choice; they should pass the ereport level to use.
* *
* is_reload should be true only when called from read_nondefault_variables()
* or RestoreGUCState(), where we are trying to load some other process's
* GUC settings into a new process.
*
* Return value: * Return value:
* +1: the value is valid and was successfully applied. * +1: the value is valid and was successfully applied.
* 0: the name or value is invalid (but see below). * 0: the name or value is invalid (but see below).
...@@ -10258,12 +10262,6 @@ read_nondefault_variables(void) ...@@ -10258,12 +10262,6 @@ read_nondefault_variables(void)
GucSource varsource; GucSource varsource;
GucContext varscontext; GucContext varscontext;
/*
* Assert that PGC_BACKEND/PGC_SU_BACKEND case in set_config_option() will
* do the right thing.
*/
Assert(IsInitProcessingMode());
/* /*
* Open file * Open file
*/ */
...@@ -10317,30 +10315,43 @@ read_nondefault_variables(void) ...@@ -10317,30 +10315,43 @@ read_nondefault_variables(void)
/* /*
* can_skip_gucvar: * can_skip_gucvar:
* When serializing, determine whether to skip this GUC. When restoring, the * Decide whether SerializeGUCState can skip sending this GUC variable,
* negation of this test determines whether to restore the compiled-in default * or whether RestoreGUCState can skip resetting this GUC to default.
* value before processing serialized values.
* *
* A PGC_S_DEFAULT setting on the serialize side will typically match new * It is somewhat magical and fragile that the same test works for both cases.
* postmaster children, but that can be false when got_SIGHUP == true and the * Realize in particular that we are very likely selecting different sets of
* pending configuration change modifies this setting. Nonetheless, we omit * GUCs on the leader and worker sides! Be sure you've understood the
* PGC_S_DEFAULT settings from serialization and make up for that by restoring * comments here and in RestoreGUCState thoroughly before changing this.
* defaults before applying serialized values.
*
* PGC_POSTMASTER variables always have the same value in every child of a
* particular postmaster. Most PGC_INTERNAL variables are compile-time
* constants; a few, like server_encoding and lc_ctype, are handled specially
* outside the serialize/restore procedure. Therefore, SerializeGUCState()
* never sends these, and RestoreGUCState() never changes them.
*
* Role is a special variable in the sense that its current value can be an
* invalid value and there are multiple ways by which that can happen (like
* after setting the role, someone drops it). So we handle it outside of
* serialize/restore machinery.
*/ */
static bool static bool
can_skip_gucvar(struct config_generic *gconf) can_skip_gucvar(struct config_generic *gconf)
{ {
/*
* We can skip GUCs that are guaranteed to have the same values in leaders
* and workers. (Note it is critical that the leader and worker have the
* same idea of which GUCs fall into this category. It's okay to consider
* context and name for this purpose, since those are unchanging
* properties of a GUC.)
*
* PGC_POSTMASTER variables always have the same value in every child of a
* particular postmaster, so the worker will certainly have the right
* value already. Likewise, PGC_INTERNAL variables are set by special
* mechanisms (if indeed they aren't compile-time constants). So we may
* always skip these.
*
* Role must be handled specially because its current value can be an
* invalid value (for instance, if someone dropped the role since we set
* it). So if we tried to serialize it normally, we might get a failure.
* We skip it here, and use another mechanism to ensure the worker has the
* right value.
*
* For all other GUCs, we skip if the GUC has its compiled-in default
* value (i.e., source == PGC_S_DEFAULT). On the leader side, this means
* we don't send GUCs that have their default values, which typically
* saves lots of work. On the worker side, this means we don't need to
* reset the GUC to default because it already has that value. See
* comments in RestoreGUCState for more info.
*/
return gconf->context == PGC_POSTMASTER || return gconf->context == PGC_POSTMASTER ||
gconf->context == PGC_INTERNAL || gconf->source == PGC_S_DEFAULT || gconf->context == PGC_INTERNAL || gconf->source == PGC_S_DEFAULT ||
strcmp(gconf->name, "role") == 0; strcmp(gconf->name, "role") == 0;
...@@ -10358,6 +10369,7 @@ estimate_variable_size(struct config_generic *gconf) ...@@ -10358,6 +10369,7 @@ estimate_variable_size(struct config_generic *gconf)
Size size; Size size;
Size valsize = 0; Size valsize = 0;
/* Skippable GUCs consume zero space. */
if (can_skip_gucvar(gconf)) if (can_skip_gucvar(gconf))
return 0; return 0;
...@@ -10522,6 +10534,7 @@ static void ...@@ -10522,6 +10534,7 @@ static void
serialize_variable(char **destptr, Size *maxbytes, serialize_variable(char **destptr, Size *maxbytes,
struct config_generic *gconf) struct config_generic *gconf)
{ {
/* Ignore skippable GUCs. */
if (can_skip_gucvar(gconf)) if (can_skip_gucvar(gconf))
return; return;
...@@ -10669,8 +10682,14 @@ guc_restore_error_context_callback(void *arg) ...@@ -10669,8 +10682,14 @@ guc_restore_error_context_callback(void *arg)
/* /*
* RestoreGUCState: * RestoreGUCState:
* Reads the GUC state at the specified address and updates the GUCs with the * Reads the GUC state at the specified address and sets this process's
* values read from the GUC state. * GUCs to match.
*
* Note that this provides the worker with only a very shallow view of the
* leader's GUC state: we'll know about the currently active values, but not
* about stacked or reset values. That's fine since the worker is just
* executing one part of a query, within which the active values won't change
* and the stacked values are invisible.
*/ */
void void
RestoreGUCState(void *gucstate) RestoreGUCState(void *gucstate)
...@@ -10687,10 +10706,100 @@ RestoreGUCState(void *gucstate) ...@@ -10687,10 +10706,100 @@ RestoreGUCState(void *gucstate)
int i; int i;
ErrorContextCallback error_context_callback; ErrorContextCallback error_context_callback;
/* See comment at can_skip_gucvar(). */ /*
* First, ensure that all potentially-shippable GUCs are reset to their
* default values. We must not touch those GUCs that the leader will
* never ship, while there is no need to touch those that are shippable
* but already have their default values. Thus, this ends up being the
* same test that SerializeGUCState uses, even though the sets of
* variables involved may well be different since the leader's set of
* variables-not-at-default-values can differ from the set that are
* not-default in this freshly started worker.
*
* Once we have set all the potentially-shippable GUCs to default values,
* restoring the GUCs that the leader sent (because they had non-default
* values over there) leads us to exactly the set of GUC values that the
* leader has. This is true even though the worker may have initially
* absorbed postgresql.conf settings that the leader hasn't yet seen, or
* ALTER USER/DATABASE SET settings that were established after the leader
* started.
*
* Note that ensuring all the potential target GUCs are at PGC_S_DEFAULT
* also ensures that set_config_option won't refuse to set them because of
* source-priority comparisons.
*/
for (i = 0; i < num_guc_variables; i++) for (i = 0; i < num_guc_variables; i++)
if (!can_skip_gucvar(guc_variables[i])) {
InitializeOneGUCOption(guc_variables[i]); struct config_generic *gconf = guc_variables[i];
/* Do nothing if non-shippable or if already at PGC_S_DEFAULT. */
if (can_skip_gucvar(gconf))
continue;
/*
* We can use InitializeOneGUCOption to reset the GUC to default, but
* 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.
*/
Assert(gconf->stack == NULL);
if (gconf->extra)
free(gconf->extra);
if (gconf->last_reported) /* probably can't happen */
free(gconf->last_reported);
if (gconf->sourcefile)
free(gconf->sourcefile);
switch (gconf->vartype)
{
case PGC_BOOL:
{
struct config_bool *conf = (struct config_bool *) gconf;
if (conf->reset_extra && conf->reset_extra != gconf->extra)
free(conf->reset_extra);
break;
}
case PGC_INT:
{
struct config_int *conf = (struct config_int *) gconf;
if (conf->reset_extra && conf->reset_extra != gconf->extra)
free(conf->reset_extra);
break;
}
case PGC_REAL:
{
struct config_real *conf = (struct config_real *) gconf;
if (conf->reset_extra && conf->reset_extra != gconf->extra)
free(conf->reset_extra);
break;
}
case PGC_STRING:
{
struct config_string *conf = (struct config_string *) gconf;
if (*conf->variable)
free(*conf->variable);
if (conf->reset_val && conf->reset_val != *conf->variable)
free(conf->reset_val);
if (conf->reset_extra && conf->reset_extra != gconf->extra)
free(conf->reset_extra);
break;
}
case PGC_ENUM:
{
struct config_enum *conf = (struct config_enum *) gconf;
if (conf->reset_extra && conf->reset_extra != gconf->extra)
free(conf->reset_extra);
break;
}
}
/* Now we can reset the struct to PGS_S_DEFAULT state. */
InitializeOneGUCOption(gconf);
}
/* First item is the length of the subsequent data */ /* First item is the length of the subsequent data */
memcpy(&len, gucstate, sizeof(len)); memcpy(&len, gucstate, sizeof(len));
...@@ -10704,6 +10813,7 @@ RestoreGUCState(void *gucstate) ...@@ -10704,6 +10813,7 @@ RestoreGUCState(void *gucstate)
error_context_callback.arg = NULL; error_context_callback.arg = NULL;
error_context_stack = &error_context_callback; error_context_stack = &error_context_callback;
/* Restore all the listed GUCs. */
while (srcptr < srcend) while (srcptr < srcend)
{ {
int result; int result;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment