Limit memory usage of pg_walinspect functions.
authorJeff Davis <jdavis@postgresql.org>
Mon, 20 Feb 2023 18:29:53 +0000 (10:29 -0800)
committerJeff Davis <jdavis@postgresql.org>
Mon, 20 Feb 2023 19:07:24 +0000 (11:07 -0800)
GetWALRecordsInfo() and pg_get_wal_fpi_info() can leak memory across
WAL record iterations. Fix this by using a temporary memory context
that's reset for each WAL record iteraion.

Also a use temporary context for loops in GetXLogSummaryStats(). The
number of iterations is a small constant, so the previous behavior was
not a leak, but fix for clarity (but no need to backport).

Backport GetWALRecordsInfo() change to version
15. pg_get_wal_fpi_info() didn't exist in version 15.

Reported-by: Peter Geoghegan
Author: Bharath Rupireddy
Discussion: https://www.postgresql.org/message-id/CAH2-WznLEJjn7ghmKOABOEZYuJvkTk%3DGKU3m0%2B-XBAH%2BerPiJQ%40mail.gmail.com
Backpatch-through: 15

contrib/pg_walinspect/pg_walinspect.c

index 91b740ed27771231f7a741db636cbc9030255236..b7b0a805ee8477d7233a75f0a6ff4b309e31c46e 100644 (file)
@@ -304,6 +304,8 @@ pg_get_wal_fpi_info(PG_FUNCTION_ARGS)
        XLogRecPtr      start_lsn;
        XLogRecPtr      end_lsn;
        XLogReaderState *xlogreader;
+       MemoryContext old_cxt;
+       MemoryContext tmp_cxt;
 
        start_lsn = PG_GETARG_LSN(0);
        end_lsn = PG_GETARG_LSN(1);
@@ -314,14 +316,26 @@ pg_get_wal_fpi_info(PG_FUNCTION_ARGS)
 
        xlogreader = InitXLogReaderState(start_lsn);
 
+       tmp_cxt = AllocSetContextCreate(CurrentMemoryContext,
+                                                                       "pg_get_wal_fpi_info temporary cxt",
+                                                                       ALLOCSET_DEFAULT_SIZES);
+
        while (ReadNextXLogRecord(xlogreader) &&
                   xlogreader->EndRecPtr <= end_lsn)
        {
+               /* Use the tmp context so we can clean up after each tuple is done */
+               old_cxt = MemoryContextSwitchTo(tmp_cxt);
+
                GetWALFPIInfo(fcinfo, xlogreader);
 
+               /* clean up and switch back */
+               MemoryContextSwitchTo(old_cxt);
+               MemoryContextReset(tmp_cxt);
+
                CHECK_FOR_INTERRUPTS();
        }
 
+       MemoryContextDelete(tmp_cxt);
        pfree(xlogreader->private_data);
        XLogReaderFree(xlogreader);
 
@@ -440,23 +454,37 @@ GetWALRecordsInfo(FunctionCallInfo fcinfo, XLogRecPtr start_lsn,
        ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
        Datum           values[PG_GET_WAL_RECORDS_INFO_COLS] = {0};
        bool            nulls[PG_GET_WAL_RECORDS_INFO_COLS] = {0};
+       MemoryContext old_cxt;
+       MemoryContext tmp_cxt;
 
        InitMaterializedSRF(fcinfo, 0);
 
        xlogreader = InitXLogReaderState(start_lsn);
 
+       tmp_cxt = AllocSetContextCreate(CurrentMemoryContext,
+                                                                       "GetWALRecordsInfo temporary cxt",
+                                                                       ALLOCSET_DEFAULT_SIZES);
+
        while (ReadNextXLogRecord(xlogreader) &&
                   xlogreader->EndRecPtr <= end_lsn)
        {
+               /* Use the tmp context so we can clean up after each tuple is done */
+               old_cxt = MemoryContextSwitchTo(tmp_cxt);
+
                GetWALRecordInfo(xlogreader, values, nulls,
                                                 PG_GET_WAL_RECORDS_INFO_COLS);
 
                tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
                                                         values, nulls);
 
+               /* clean up and switch back */
+               MemoryContextSwitchTo(old_cxt);
+               MemoryContextReset(tmp_cxt);
+
                CHECK_FOR_INTERRUPTS();
        }
 
+       MemoryContextDelete(tmp_cxt);
        pfree(xlogreader->private_data);
        XLogReaderFree(xlogreader);
 
@@ -560,11 +588,13 @@ GetXLogSummaryStats(XLogStats *stats, ReturnSetInfo *rsinfo,
                                        Datum *values, bool *nulls, uint32 ncols,
                                        bool stats_per_record)
 {
-       uint64          total_count = 0;
-       uint64          total_rec_len = 0;
-       uint64          total_fpi_len = 0;
-       uint64          total_len = 0;
-       int                     ri;
+       MemoryContext   old_cxt;
+       MemoryContext   tmp_cxt;
+       uint64                  total_count       = 0;
+       uint64                  total_rec_len = 0;
+       uint64                  total_fpi_len = 0;
+       uint64                  total_len         = 0;
+       int                             ri;
 
        /*
         * Each row shows its percentages of the total, so make a first pass to
@@ -581,6 +611,10 @@ GetXLogSummaryStats(XLogStats *stats, ReturnSetInfo *rsinfo,
        }
        total_len = total_rec_len + total_fpi_len;
 
+       tmp_cxt = AllocSetContextCreate(CurrentMemoryContext,
+                                                                       "GetXLogSummaryStats temporary cxt",
+                                                                       ALLOCSET_DEFAULT_SIZES);
+
        for (ri = 0; ri <= RM_MAX_ID; ri++)
        {
                uint64          count;
@@ -614,6 +648,8 @@ GetXLogSummaryStats(XLogStats *stats, ReturnSetInfo *rsinfo,
                                if (count == 0)
                                        continue;
 
+                               old_cxt = MemoryContextSwitchTo(tmp_cxt);
+
                                /* the upper four bits in xl_info are the rmgr's */
                                id = desc.rm_identify(rj << 4);
                                if (id == NULL)
@@ -626,6 +662,10 @@ GetXLogSummaryStats(XLogStats *stats, ReturnSetInfo *rsinfo,
 
                                tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
                                                                         values, nulls);
+
+                               /* clean up and switch back */
+                               MemoryContextSwitchTo(old_cxt);
+                               MemoryContextReset(tmp_cxt);
                        }
                }
                else
@@ -635,14 +675,22 @@ GetXLogSummaryStats(XLogStats *stats, ReturnSetInfo *rsinfo,
                        fpi_len = stats->rmgr_stats[ri].fpi_len;
                        tot_len = rec_len + fpi_len;
 
+                       old_cxt = MemoryContextSwitchTo(tmp_cxt);
+
                        FillXLogStatsRow(desc.rm_name, count, total_count, rec_len,
                                                         total_rec_len, fpi_len, total_fpi_len, tot_len,
                                                         total_len, values, nulls, ncols);
 
                        tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
                                                                 values, nulls);
+
+                       /* clean up and switch back */
+                       MemoryContextSwitchTo(old_cxt);
+                       MemoryContextReset(tmp_cxt);
                }
        }
+
+       MemoryContextDelete(tmp_cxt);
 }
 
 /*