]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Fix assorted pretty-trivial memory leaks in the backend.
authorTom Lane <tgl@sss.pgh.pa.us>
Sat, 2 Aug 2025 23:07:53 +0000 (19:07 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Sun, 3 Aug 2025 01:59:46 +0000 (21:59 -0400)
In the current system architecture, none of these are worth obsessing
over; most are once-per-process leaks.  However, Valgrind complains
about all of them, and if we get to using threads rather than
processes for backend sessions, it will become more interesting to
avoid per-session leaks.

* Fix leaks in StartupXLOG() and ShutdownWalRecovery().

* Fix leakage of pq_mq_handle in a parallel worker.
While at it, move mq_putmessage's "Assert(pq_mq_handle != NULL)"
to someplace where it's not trivially useless.

* Fix leak in logicalrep_worker_detach().

* Don't leak the startup-packet buffer in ProcessStartupPacket().

* Fix leak in evtcache.c's DecodeTextArrayToBitmapset().
If the presented array is toasted, this neglected to free the
detoasted copy, which was then leaked into EventTriggerCacheContext.

* I'm distressed by the amount of code that BuildEventTriggerCache
is willing to run while switched into a long-lived cache context.
Although the detoasted array is the only leak that Valgrind reports,
let's tighten things up while we're here.  (DecodeTextArrayToBitmapset
is still run in the cache context, so doing this doesn't remove the
need for the detoast fix.  But it reduces the surface area for other
leaks.)

* load_domaintype_info() intentionally leaked some intermediate cruft
into the long-lived DomainConstraintCache's memory context, reasoning
that the amount of leakage will typically not be much so it's not
worth doing a copyObject() of the final tree to avoid that.  But
Valgrind knows nothing of engineering tradeoffs and complains anyway.
On the whole, the copyObject doesn't cost that much and this is surely
not a performance-critical code path, so let's do it the clean way.

* MarkGUCPrefixReserved didn't bother to clean up removed placeholder
GUCs at all, which shows up as a leak in one regression test.
It seems appropriate for it to do as much cleanup as
define_custom_variable does when replacing placeholders, so factor
that code out into a helper function.  define_custom_variable's logic
was one brick shy of a load too: it forgot to free the separate
allocation for the placeholder's name.

Author: Tom Lane <tgl@sss.pgh.pa.us>
Reviewed-by: Andres Freund <andres@anarazel.de>
Reviewed-by: Richard Guo <guofenglinux@gmail.com>
Discussion: https://postgr.es/m/285483.1746756246@sss.pgh.pa.us

src/backend/access/transam/xlog.c
src/backend/access/transam/xlogrecovery.c
src/backend/libpq/pqmq.c
src/backend/replication/logical/launcher.c
src/backend/tcop/backend_startup.c
src/backend/utils/cache/evtcache.c
src/backend/utils/cache/typcache.c
src/backend/utils/misc/guc.c

index b0891998b243f4524f63fabbcc45835a04cae639..5553c20fee8cee70536fff90dcebc1ba2a186cff 100644 (file)
@@ -703,7 +703,7 @@ static void InitControlFile(uint64 sysidentifier, uint32 data_checksum_version);
 static void WriteControlFile(void);
 static void ReadControlFile(void);
 static void UpdateControlFile(void);
-static char *str_time(pg_time_t tnow);
+static char *str_time(pg_time_t tnow, char *buf, size_t bufsize);
 
 static int     get_sync_bit(int method);
 
@@ -5371,11 +5371,9 @@ BootStrapXLOG(uint32 data_checksum_version)
 }
 
 static char *
-str_time(pg_time_t tnow)
+str_time(pg_time_t tnow, char *buf, size_t bufsize)
 {
-       char       *buf = palloc(128);
-
-       pg_strftime(buf, 128,
+       pg_strftime(buf, bufsize,
                                "%Y-%m-%d %H:%M:%S %Z",
                                pg_localtime(&tnow, log_timezone));
 
@@ -5618,6 +5616,7 @@ StartupXLOG(void)
        XLogRecPtr      missingContrecPtr;
        TransactionId oldestActiveXID;
        bool            promoted = false;
+       char            timebuf[128];
 
        /*
         * We should have an aux process resource owner to use, and we should not
@@ -5646,25 +5645,29 @@ StartupXLOG(void)
                         */
                        ereport(IsPostmasterEnvironment ? LOG : NOTICE,
                                        (errmsg("database system was shut down at %s",
-                                                       str_time(ControlFile->time))));
+                                                       str_time(ControlFile->time,
+                                                                        timebuf, sizeof(timebuf)))));
                        break;
 
                case DB_SHUTDOWNED_IN_RECOVERY:
                        ereport(LOG,
                                        (errmsg("database system was shut down in recovery at %s",
-                                                       str_time(ControlFile->time))));
+                                                       str_time(ControlFile->time,
+                                                                        timebuf, sizeof(timebuf)))));
                        break;
 
                case DB_SHUTDOWNING:
                        ereport(LOG,
                                        (errmsg("database system shutdown was interrupted; last known up at %s",
-                                                       str_time(ControlFile->time))));
+                                                       str_time(ControlFile->time,
+                                                                        timebuf, sizeof(timebuf)))));
                        break;
 
                case DB_IN_CRASH_RECOVERY:
                        ereport(LOG,
                                        (errmsg("database system was interrupted while in recovery at %s",
-                                                       str_time(ControlFile->time)),
+                                                       str_time(ControlFile->time,
+                                                                        timebuf, sizeof(timebuf))),
                                         errhint("This probably means that some data is corrupted and"
                                                         " you will have to use the last backup for recovery.")));
                        break;
@@ -5672,7 +5675,8 @@ StartupXLOG(void)
                case DB_IN_ARCHIVE_RECOVERY:
                        ereport(LOG,
                                        (errmsg("database system was interrupted while in recovery at log time %s",
-                                                       str_time(ControlFile->checkPointCopy.time)),
+                                                       str_time(ControlFile->checkPointCopy.time,
+                                                                        timebuf, sizeof(timebuf))),
                                         errhint("If this has occurred more than once some data might be corrupted"
                                                         " and you might need to choose an earlier recovery target.")));
                        break;
@@ -5680,7 +5684,8 @@ StartupXLOG(void)
                case DB_IN_PRODUCTION:
                        ereport(LOG,
                                        (errmsg("database system was interrupted; last known up at %s",
-                                                       str_time(ControlFile->time))));
+                                                       str_time(ControlFile->time,
+                                                                        timebuf, sizeof(timebuf)))));
                        break;
 
                default:
@@ -6325,6 +6330,12 @@ StartupXLOG(void)
         */
        CompleteCommitTsInitialization();
 
+       /* Clean up EndOfWalRecoveryInfo data to appease Valgrind leak checking */
+       if (endOfRecoveryInfo->lastPage)
+               pfree(endOfRecoveryInfo->lastPage);
+       pfree(endOfRecoveryInfo->recoveryStopReason);
+       pfree(endOfRecoveryInfo);
+
        /*
         * All done with end-of-recovery actions.
         *
index e8f3ba00caae70d2f29225f7c961f435f8200087..f23ec8969c27dd7cbfd7a3a088fe71170fbef7cd 100644 (file)
@@ -1626,6 +1626,7 @@ ShutdownWalRecovery(void)
                close(readFile);
                readFile = -1;
        }
+       pfree(xlogreader->private_data);
        XLogReaderFree(xlogreader);
        XLogPrefetcherFree(xlogprefetcher);
 
index f1a08bc32ca17db15f89fa19b2ac6624c74c2448..5f39949a3677390a3b80199a1e2659fb4a4d2c36 100644 (file)
@@ -23,7 +23,7 @@
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
 
-static shm_mq_handle *pq_mq_handle;
+static shm_mq_handle *pq_mq_handle = NULL;
 static bool pq_mq_busy = false;
 static pid_t pq_mq_parallel_leader_pid = 0;
 static ProcNumber pq_mq_parallel_leader_proc_number = INVALID_PROC_NUMBER;
@@ -66,7 +66,11 @@ pq_redirect_to_shm_mq(dsm_segment *seg, shm_mq_handle *mqh)
 static void
 pq_cleanup_redirect_to_shm_mq(dsm_segment *seg, Datum arg)
 {
-       pq_mq_handle = NULL;
+       if (pq_mq_handle != NULL)
+       {
+               pfree(pq_mq_handle);
+               pq_mq_handle = NULL;
+       }
        whereToSendOutput = DestNone;
 }
 
@@ -131,8 +135,11 @@ mq_putmessage(char msgtype, const char *s, size_t len)
        if (pq_mq_busy)
        {
                if (pq_mq_handle != NULL)
+               {
                        shm_mq_detach(pq_mq_handle);
-               pq_mq_handle = NULL;
+                       pfree(pq_mq_handle);
+                       pq_mq_handle = NULL;
+               }
                return EOF;
        }
 
@@ -152,8 +159,6 @@ mq_putmessage(char msgtype, const char *s, size_t len)
        iov[1].data = s;
        iov[1].len = len;
 
-       Assert(pq_mq_handle != NULL);
-
        for (;;)
        {
                /*
@@ -161,6 +166,7 @@ mq_putmessage(char msgtype, const char *s, size_t len)
                 * that the shared memory value is updated before we send the parallel
                 * message signal right after this.
                 */
+               Assert(pq_mq_handle != NULL);
                result = shm_mq_sendv(pq_mq_handle, iov, 2, true, true);
 
                if (pq_mq_parallel_leader_pid != 0)
index 742d9ba68e900254b0e9d8136a1b58007b5ebc03..37377f7eb636c6c8c60efff0003b3e7680bb9ec2 100644 (file)
@@ -790,6 +790,8 @@ logicalrep_worker_detach(void)
                }
 
                LWLockRelease(LogicalRepWorkerLock);
+
+               list_free(workers);
        }
 
        /* Block concurrent access. */
index ad0af5edc1f2183a9e29d2b43b8e0fec1f243a4c..14d5fc0b1965a28891278294f759c2d43db57702 100644 (file)
@@ -492,7 +492,7 @@ static int
 ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done)
 {
        int32           len;
-       char       *buf;
+       char       *buf = NULL;
        ProtocolVersion proto;
        MemoryContext oldcontext;
 
@@ -516,7 +516,7 @@ ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done)
                 * scanners, which may be less benign, but it's not really our job to
                 * notice those.)
                 */
-               return STATUS_ERROR;
+               goto fail;
        }
 
        if (pq_getbytes(((char *) &len) + 1, 3) == EOF)
@@ -526,7 +526,7 @@ ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done)
                        ereport(COMMERROR,
                                        (errcode(ERRCODE_PROTOCOL_VIOLATION),
                                         errmsg("incomplete startup packet")));
-               return STATUS_ERROR;
+               goto fail;
        }
 
        len = pg_ntoh32(len);
@@ -538,7 +538,7 @@ ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done)
                ereport(COMMERROR,
                                (errcode(ERRCODE_PROTOCOL_VIOLATION),
                                 errmsg("invalid length of startup packet")));
-               return STATUS_ERROR;
+               goto fail;
        }
 
        /*
@@ -554,7 +554,7 @@ ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done)
                ereport(COMMERROR,
                                (errcode(ERRCODE_PROTOCOL_VIOLATION),
                                 errmsg("incomplete startup packet")));
-               return STATUS_ERROR;
+               goto fail;
        }
        pq_endmsgread();
 
@@ -568,7 +568,7 @@ ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done)
        {
                ProcessCancelRequestPacket(port, buf, len);
                /* Not really an error, but we don't want to proceed further */
-               return STATUS_ERROR;
+               goto fail;
        }
 
        if (proto == NEGOTIATE_SSL_CODE && !ssl_done)
@@ -607,14 +607,16 @@ ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done)
                        ereport(COMMERROR,
                                        (errcode_for_socket_access(),
                                         errmsg("failed to send SSL negotiation response: %m")));
-                       return STATUS_ERROR;    /* close the connection */
+                       goto fail;                      /* close the connection */
                }
 
 #ifdef USE_SSL
                if (SSLok == 'S' && secure_open_server(port) == -1)
-                       return STATUS_ERROR;
+                       goto fail;
 #endif
 
+               pfree(buf);
+
                /*
                 * At this point we should have no data already buffered.  If we do,
                 * it was received before we performed the SSL handshake, so it wasn't
@@ -661,14 +663,16 @@ ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done)
                        ereport(COMMERROR,
                                        (errcode_for_socket_access(),
                                         errmsg("failed to send GSSAPI negotiation response: %m")));
-                       return STATUS_ERROR;    /* close the connection */
+                       goto fail;                      /* close the connection */
                }
 
 #ifdef ENABLE_GSS
                if (GSSok == 'G' && secure_open_gssapi(port) == -1)
-                       return STATUS_ERROR;
+                       goto fail;
 #endif
 
+               pfree(buf);
+
                /*
                 * At this point we should have no data already buffered.  If we do,
                 * it was received before we performed the GSS handshake, so it wasn't
@@ -863,7 +867,16 @@ ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done)
         */
        MemoryContextSwitchTo(oldcontext);
 
+       pfree(buf);
+
        return STATUS_OK;
+
+fail:
+       /* be tidy, just to avoid Valgrind complaints */
+       if (buf)
+               pfree(buf);
+
+       return STATUS_ERROR;
 }
 
 /*
index ce596bf563856c95e6af49f5ec7902da8ad845a9..b9d5a5998be506993b27ca172c92e7a610c5a78c 100644 (file)
@@ -78,7 +78,6 @@ BuildEventTriggerCache(void)
 {
        HASHCTL         ctl;
        HTAB       *cache;
-       MemoryContext oldcontext;
        Relation        rel;
        Relation        irel;
        SysScanDesc scan;
@@ -110,9 +109,6 @@ BuildEventTriggerCache(void)
                                                                          (Datum) 0);
        }
 
-       /* Switch to correct memory context. */
-       oldcontext = MemoryContextSwitchTo(EventTriggerCacheContext);
-
        /* Prevent the memory context from being nuked while we're rebuilding. */
        EventTriggerCacheState = ETCS_REBUILD_STARTED;
 
@@ -145,6 +141,7 @@ BuildEventTriggerCache(void)
                bool            evttags_isnull;
                EventTriggerCacheEntry *entry;
                bool            found;
+               MemoryContext oldcontext;
 
                /* Get next tuple. */
                tup = systable_getnext_ordered(scan, ForwardScanDirection);
@@ -171,6 +168,9 @@ BuildEventTriggerCache(void)
                else
                        continue;
 
+               /* Switch to correct memory context. */
+               oldcontext = MemoryContextSwitchTo(EventTriggerCacheContext);
+
                /* Allocate new cache item. */
                item = palloc0(sizeof(EventTriggerCacheItem));
                item->fnoid = form->evtfoid;
@@ -188,6 +188,9 @@ BuildEventTriggerCache(void)
                        entry->triggerlist = lappend(entry->triggerlist, item);
                else
                        entry->triggerlist = list_make1(item);
+
+               /* Restore previous memory context. */
+               MemoryContextSwitchTo(oldcontext);
        }
 
        /* Done with pg_event_trigger scan. */
@@ -195,9 +198,6 @@ BuildEventTriggerCache(void)
        index_close(irel, AccessShareLock);
        relation_close(rel, AccessShareLock);
 
-       /* Restore previous memory context. */
-       MemoryContextSwitchTo(oldcontext);
-
        /* Install new cache. */
        EventTriggerCache = cache;
 
@@ -240,6 +240,8 @@ DecodeTextArrayToBitmapset(Datum array)
        }
 
        pfree(elems);
+       if ((Pointer) arr != DatumGetPointer(array))
+               pfree(arr);
 
        return bms;
 }
index f9aec38a11fb37ce56b0a95eaed233fda22f0567..6a347698edffe2517a3a628992cf487ef6ac7d7d 100644 (file)
@@ -1171,9 +1171,6 @@ load_domaintype_info(TypeCacheEntry *typentry)
                                elog(ERROR, "domain \"%s\" constraint \"%s\" has NULL conbin",
                                         NameStr(typTup->typname), NameStr(c->conname));
 
-                       /* Convert conbin to C string in caller context */
-                       constring = TextDatumGetCString(val);
-
                        /* Create the DomainConstraintCache object and context if needed */
                        if (dcc == NULL)
                        {
@@ -1189,9 +1186,8 @@ load_domaintype_info(TypeCacheEntry *typentry)
                                dcc->dccRefCount = 0;
                        }
 
-                       /* Create node trees in DomainConstraintCache's context */
-                       oldcxt = MemoryContextSwitchTo(dcc->dccContext);
-
+                       /* Convert conbin to a node tree, still in caller's context */
+                       constring = TextDatumGetCString(val);
                        check_expr = (Expr *) stringToNode(constring);
 
                        /*
@@ -1206,10 +1202,13 @@ load_domaintype_info(TypeCacheEntry *typentry)
                         */
                        check_expr = expression_planner(check_expr);
 
+                       /* Create only the minimally needed stuff in dccContext */
+                       oldcxt = MemoryContextSwitchTo(dcc->dccContext);
+
                        r = makeNode(DomainConstraintState);
                        r->constrainttype = DOM_CONSTRAINT_CHECK;
                        r->name = pstrdup(NameStr(c->conname));
-                       r->check_expr = check_expr;
+                       r->check_expr = copyObject(check_expr);
                        r->check_exprstate = NULL;
 
                        MemoryContextSwitchTo(oldcxt);
index ce5449f287853305082c84deaf2975950fdd1c5b..e404c345e6eabd0afd7df16dd158945e4705d255 100644 (file)
@@ -249,6 +249,7 @@ static void reapply_stacked_values(struct config_generic *variable,
                                                                   const char *curvalue,
                                                                   GucContext curscontext, GucSource cursource,
                                                                   Oid cursrole);
+static void free_placeholder(struct config_string *pHolder);
 static bool validate_option_array_item(const char *name, const char *value,
                                                                           bool skipIfNoPermissions);
 static void write_auto_conf_file(int fd, const char *filename, ConfigVariable *head);
@@ -5023,16 +5024,8 @@ define_custom_variable(struct config_generic *variable)
                set_config_sourcefile(name, pHolder->gen.sourcefile,
                                                          pHolder->gen.sourceline);
 
-       /*
-        * Free up as much as we conveniently can of the placeholder structure.
-        * (This neglects any stack items, so it's possible for some memory to be
-        * leaked.  Since this can only happen once per session per variable, it
-        * doesn't seem worth spending much code on.)
-        */
-       set_string_field(pHolder, pHolder->variable, NULL);
-       set_string_field(pHolder, &pHolder->reset_val, NULL);
-
-       guc_free(pHolder);
+       /* Now we can free the no-longer-referenced placeholder variable */
+       free_placeholder(pHolder);
 }
 
 /*
@@ -5131,6 +5124,25 @@ reapply_stacked_values(struct config_generic *variable,
        }
 }
 
+/*
+ * Free up a no-longer-referenced placeholder GUC variable.
+ *
+ * This neglects any stack items, so it's possible for some memory to be
+ * leaked.  Since this can only happen once per session per variable, it
+ * doesn't seem worth spending much code on.
+ */
+static void
+free_placeholder(struct config_string *pHolder)
+{
+       /* Placeholders are always STRING type, so free their values */
+       Assert(pHolder->gen.vartype == PGC_STRING);
+       set_string_field(pHolder, pHolder->variable, NULL);
+       set_string_field(pHolder, &pHolder->reset_val, NULL);
+
+       guc_free(unconstify(char *, pHolder->gen.name));
+       guc_free(pHolder);
+}
+
 /*
  * Functions for extensions to call to define their custom GUC variables.
  */
@@ -5291,9 +5303,7 @@ MarkGUCPrefixReserved(const char *className)
 
        /*
         * Check for existing placeholders.  We must actually remove invalid
-        * placeholders, else future parallel worker startups will fail.  (We
-        * don't bother trying to free associated memory, since this shouldn't
-        * happen often.)
+        * placeholders, else future parallel worker startups will fail.
         */
        hash_seq_init(&status, guc_hashtab);
        while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL)
@@ -5317,6 +5327,8 @@ MarkGUCPrefixReserved(const char *className)
                                                NULL);
                        /* Remove it from any lists it's in, too */
                        RemoveGUCFromLists(var);
+                       /* And free it */
+                       free_placeholder((struct config_string *) var);
                }
        }