]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Avoid leaking memory in RestoreGUCState(), and improve comments.
authorTom Lane <tgl@sss.pgh.pa.us>
Sat, 20 Mar 2021 03:03:17 +0000 (23:03 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Sat, 20 Mar 2021 03:03:17 +0000 (23:03 -0400)
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 f5d9698a8 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

src/backend/utils/misc/guc.c

index 2964efda96793d1feca61b074aec4743bbf78baa..3b36a31a47507177dacc942be272f7002f794448 100644 (file)
@@ -7121,6 +7121,10 @@ parse_and_validate_value(struct config_generic *record,
  * 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.
  *
+ * 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:
  *     +1: the value is valid and was successfully applied.
  *     0:      the name or value is invalid (but see below).
@@ -10258,12 +10262,6 @@ read_nondefault_variables(void)
        GucSource       varsource;
        GucContext      varscontext;
 
-       /*
-        * Assert that PGC_BACKEND/PGC_SU_BACKEND case in set_config_option() will
-        * do the right thing.
-        */
-       Assert(IsInitProcessingMode());
-
        /*
         * Open file
         */
@@ -10317,30 +10315,43 @@ read_nondefault_variables(void)
 
 /*
  * can_skip_gucvar:
- * When serializing, determine whether to skip this GUC.  When restoring, the
- * negation of this test determines whether to restore the compiled-in default
- * value before processing serialized values.
+ * Decide whether SerializeGUCState can skip sending this GUC variable,
+ * or whether RestoreGUCState can skip resetting this GUC to default.
  *
- * A PGC_S_DEFAULT setting on the serialize side will typically match new
- * postmaster children, but that can be false when got_SIGHUP == true and the
- * pending configuration change modifies this setting.  Nonetheless, we omit
- * PGC_S_DEFAULT settings from serialization and make up for that by restoring
- * 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.
+ * It is somewhat magical and fragile that the same test works for both cases.
+ * Realize in particular that we are very likely selecting different sets of
+ * GUCs on the leader and worker sides!  Be sure you've understood the
+ * comments here and in RestoreGUCState thoroughly before changing this.
  */
 static bool
 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 ||
                gconf->context == PGC_INTERNAL || gconf->source == PGC_S_DEFAULT ||
                strcmp(gconf->name, "role") == 0;
@@ -10358,6 +10369,7 @@ estimate_variable_size(struct config_generic *gconf)
        Size            size;
        Size            valsize = 0;
 
+       /* Skippable GUCs consume zero space. */
        if (can_skip_gucvar(gconf))
                return 0;
 
@@ -10522,6 +10534,7 @@ static void
 serialize_variable(char **destptr, Size *maxbytes,
                                   struct config_generic *gconf)
 {
+       /* Ignore skippable GUCs. */
        if (can_skip_gucvar(gconf))
                return;
 
@@ -10669,8 +10682,14 @@ guc_restore_error_context_callback(void *arg)
 
 /*
  * RestoreGUCState:
- * Reads the GUC state at the specified address and updates the GUCs with the
- * values read from the GUC state.
+ * Reads the GUC state at the specified address and sets this process's
+ * 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
 RestoreGUCState(void *gucstate)
@@ -10687,10 +10706,100 @@ RestoreGUCState(void *gucstate)
        int                     i;
        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++)
-               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 */
        memcpy(&len, gucstate, sizeof(len));
@@ -10704,6 +10813,7 @@ RestoreGUCState(void *gucstate)
        error_context_callback.arg = NULL;
        error_context_stack = &error_context_callback;
 
+       /* Restore all the listed GUCs. */
        while (srcptr < srcend)
        {
                int                     result;