Vlasdislav commented on code in PR #1757:
URL: https://github.com/apache/cloudberry/pull/1757#discussion_r3282119558


##########
gpcontrib/gp_relsizes_stats/src/gp_relsizes_stats.c:
##########
@@ -0,0 +1,934 @@
+#include "postgres.h"
+
+/* Required headers for background workers */
+#include "miscadmin.h"
+#include "postmaster/bgworker.h"
+#include "storage/ipc.h"
+#include "storage/latch.h"
+#include "storage/lwlock.h"
+#include "storage/proc.h"
+#include "storage/shmem.h"
+
+/* Additional headers for extension functionality */
+#include "access/xact.h"
+#include "executor/spi.h"
+#include "fmgr.h"
+#include "lib/stringinfo.h"
+#include "pgstat.h"
+#include "tcop/utility.h"
+
+#include "catalog/namespace.h"
+#include "cdb/cdbvars.h"
+#include "commands/defrem.h"
+#include "funcapi.h"
+
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/guc.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/snapmgr.h"
+#include "utils/syscache.h"
+
+#include <assert.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <unistd.h>
+#include <limits.h>
+
+#define FILEINFO_ARGS_CNT 5
+#define HOUR_TIME 3600000    /* milliseconds in hour */
+#define MINUTE_TIME 60000    /* milliseconds in minute */
+#define FILE_NAPTIME 1       /* default naptime between file processing in 
milliseconds */
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(get_stats_for_database);
+PG_FUNCTION_INFO_V1(relsizes_collect_stats_once);
+Datum get_stats_for_database(PG_FUNCTION_ARGS);
+Datum relsizes_collect_stats_once(PG_FUNCTION_ARGS);
+
+static void worker_sigterm(SIGNAL_ARGS);
+static Oid *get_databases_oids(int *databases_cnt, MemoryContext ctx, bool 
create_transaction);
+static int update_segment_file_map_table(void);
+static int update_table_sizes_history(void);
+static void get_stats_for_databases(Oid *databases_oids, int databases_cnt, 
bool fast);
+static void run_database_stats_worker(bool fast, Oid db);
+static int plugin_created(void);
+static BgwHandleStatus 
WaitForBackgroundWorkerShutdownSafely(BackgroundWorkerHandle *handle);
+static int delete_data_in_history(void);
+static int put_data_into_history(void);
+void _PG_init(void);
+
+void relsizes_collect_stats(Datum main_arg);
+void relsizes_database_stats_job(Datum args);
+
+/* Global variables */
+static int worker_restart_naptime = 0;
+static int worker_database_naptime = 0;
+static int worker_file_naptime = 0;
+static bool enabled = false;
+
+static volatile sig_atomic_t got_sigterm = false;
+
+typedef union DbWorkerArg {
+    Datum d;
+    struct {
+        Oid db;
+        bool fast;
+    } s;
+} DbWorkerArg;
+
+static_assert(sizeof(Datum) == sizeof(DbWorkerArg), "Invalid size of structure 
in DbWorkerArg");
+
+/*
+ * Signal handler for SIGTERM in background worker processes.
+ *
+ * This handler is called when the postmaster requests the background worker
+ * to shut down. It sets the got_sigterm flag and wakes up the main worker
+ * loop by setting the process latch.
+ *
+ * The function follows PostgreSQL signal handling conventions:
+ * - Saves and restores errno
+ * - Uses only async-signal-safe operations
+ * - Sets a flag that the main loop can check
+ */
+static void worker_sigterm(SIGNAL_ARGS) {
+    int save_errno = errno;
+    got_sigterm = true;
+    if (MyProc) {
+        SetLatch(&MyProc->procLatch);
+    }
+    errno = save_errno;
+}
+
+/*
+ * Wait for a background worker to stop with timeout and error handling.
+ * 
+ * This is a modified version that adds timeout functionality and improved
+ * error handling to prevent infinite loops in case of hung workers.
+ * Returns BGWH_STOPPED on success, BGWH_POSTMASTER_DIED on error/timeout.
+ */
+static BgwHandleStatus 
WaitForBackgroundWorkerShutdownSafely(BackgroundWorkerHandle *handle) {
+    BgwHandleStatus status = BGWH_NOT_YET_STARTED;
+    int rc;
+    int attempts = 0;
+    const int max_attempts = 5 * HOUR_TIME / 100; /* maximum 5 hours wait time 
*/
+
+    PG_TRY();
+    {
+        while (attempts < max_attempts) {
+            pid_t pid;
+
+            status = GetBackgroundWorkerPid(handle, &pid);
+            if (status == BGWH_STOPPED) {
+                return status;
+            }
+
+            /* Add 100ms timeout instead of infinite wait */
+            rc = WaitLatch(&MyProc->procLatch, WL_LATCH_SET | WL_TIMEOUT | 
WL_POSTMASTER_DEATH, 100L, WAIT_EVENT_BGWORKER_SHUTDOWN);
+
+            ResetLatch(&MyProc->procLatch);
+
+            if (rc & WL_POSTMASTER_DEATH) {
+                status = BGWH_POSTMASTER_DIED;
+                break;
+            }
+
+            /* Check for interrupts but don't let them break the entire 
process */
+            if (QueryCancelPending || ProcDiePending) {
+                ereport(WARNING, 
(errmsg("WaitForBackgroundWorkerShutdownSafely: received interrupt signal, 
stopping wait")));
+                status = BGWH_POSTMASTER_DIED; /* Return status as if 
postmaster died */
+                break;
+            }
+
+            attempts++;
+        }
+
+        /* If maximum attempts reached */
+        if (attempts >= max_attempts) {
+            ereport(WARNING, (errmsg("WaitForBackgroundWorkerShutdownSafely: 
timeout after %d attempts", max_attempts)));
+            status = BGWH_POSTMASTER_DIED; /* Return error status */
+        }
+    }
+    PG_CATCH();
+    {
+        /* Log error but do NOT re-throw exception */
+        ereport(WARNING, (errmsg("WaitForBackgroundWorkerShutdownSafely: 
caught exception, returning error status")));
+        /* Return error status instead of PG_RE_THROW() */
+        return BGWH_POSTMASTER_DIED;
+    }
+    PG_END_TRY();
+    return status;
+}
+
+/*
+ * Retrieve list of database OIDs from the catalog.
+ *
+ * This function queries pg_database to get all user databases (excluding
+ * system databases like template0, template1, diskquota, and gpperfmon).
+ * 
+ * Parameters:
+ *   databases_cnt - Output parameter, set to number of databases found
+ *   ctx - Memory context to allocate result in (for cross-call persistence)
+ *   create_transaction - Whether to create a new transaction for the query
+ *
+ * Returns:
+ *   Array of OIDs allocated in ctx, or NULL on error.
+ *   The array length is databases_cnt.
+ *
+ * Note: Caller is responsible for freeing the returned memory when done.
+ */
+static Oid *get_databases_oids(int *databases_cnt, MemoryContext ctx, bool 
create_transaction) {
+    const char *sql =
+        "SELECT oid"
+        "  FROM pg_database"
+        " WHERE datname NOT IN ('template0', 'template1', 'diskquota', 
'gpperfmon')";
+    const char *error = NULL;
+
+    Oid *databases_oids = NULL;
+    *databases_cnt = 0;
+
+    if (create_transaction) {
+        SetCurrentStatementStartTimestamp();
+        StartTransactionCommand();
+    }
+
+    if (SPI_connect() < 0) {
+        error = "get_databases_oids: SPI_connect failed";
+        goto finish_transaction;
+    }
+    if (create_transaction) {
+        PushActiveSnapshot(GetTransactionSnapshot());
+        pgstat_report_activity(STATE_RUNNING, sql);
+    }
+
+    if (SPI_execute(sql, true, 0) != SPI_OK_SELECT) {
+        error = "get_databases_oids: SPI_execute failed (select datname, oid)";
+        goto finish_spi;
+    }
+
+    /* Prepare tuple processing variables */
+
+    *databases_cnt = SPI_processed;
+    MemoryContext old_context = MemoryContextSwitchTo(ctx);
+    databases_oids = palloc((*databases_cnt) * sizeof(*databases_oids));
+    MemoryContextSwitchTo(old_context);
+
+    for (int i = 0; i < SPI_processed; ++i) {
+        Datum oid_datum;
+        bool oid_nullable;
+
+        heap_deform_tuple(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 
&oid_datum, &oid_nullable);
+
+        databases_oids[i] = DatumGetObjectId(oid_datum);
+    }
+
+finish_spi:
+    SPI_finish();
+finish_transaction:
+    if (create_transaction) {
+        PopActiveSnapshot();
+        CommitTransactionCommand();
+        pgstat_report_stat(false);
+        pgstat_report_activity(STATE_IDLE, NULL);
+    }
+
+    if (error != NULL) {
+        ereport(WARNING, (errmsg("%s: %m", error)));
+        return NULL; /* Return NULL on error */
+    }
+
+    return databases_oids;
+}
+
+/*
+ * Update the segment_file_map table with current relation file mappings.
+ *
+ * This function refreshes the mapping between relation OIDs and their
+ * physical file nodes across all segments. It first deletes the existing
+ * data and then repopulates it by querying pg_class on all segments.
+ *
+ * The mapping is essential for correlating file statistics collected
+ * from the filesystem with actual database relations.
+ *
+ * Returns:
+ *   0 on success, negative value on error
+ *
+ * Note: This function assumes it's running within an active SPI context.
+ */
+static int update_segment_file_map_table() {
+    int retcode = 0;
+    char *sql_delete = "DELETE FROM relsizes_stats_schema.segment_file_map";
+    char *sql_insert = "INSERT INTO relsizes_stats_schema.segment_file_map 
SELECT gp_segment_id, oid, relfilenode FROM "
+                       "gp_dist_random('pg_class')";
+    char *error = NULL;
+    pgstat_report_activity(STATE_RUNNING, sql_delete);
+    retcode = SPI_execute(sql_delete, false, 0);
+    if (retcode != SPI_OK_DELETE) {
+        error = "update_segment_file_map_table: failed to delete from table";
+        goto cleanup;
+    }
+    
+    pgstat_report_activity(STATE_RUNNING, sql_insert);
+    retcode = SPI_execute(sql_insert, false, 0);
+    if (retcode != SPI_OK_INSERT) {
+        error = "update_segment_file_map_table: failed to insert new rows into 
table";
+        goto cleanup;
+    }
+
+cleanup:
+    pgstat_report_activity(STATE_IDLE, NULL);
+    if (error != NULL) {
+        ereport(WARNING, (errmsg("%s: %m", error)));
+    }
+    return retcode;
+}
+
+/*
+ * Check if a character is a digit (0-9).
+ *
+ * Simple utility function used by fill_relfilenode() to parse
+ * numeric portions of filenames.
+ *
+ * Returns:
+ *   true if character is a digit, false otherwise
+ */
+static bool is_number(char symbol) { return '0' <= symbol && symbol <= '9'; }
+
+/*
+ * Extract relfilenode from filename by finding the first sequence of digits
+ * in the filename and converting it to numeric value
+ */
+static unsigned int fill_relfilenode(char *name) {
+    unsigned int result = 0, pos = 0;
+    size_t name_len = strlen(name);
+    
+    while (pos < name_len && !is_number(name[pos])) {
+        ++pos;
+    }
+    while (pos < name_len && is_number(name[pos])) {
+        /* Check for overflow to prevent integer overflow */
+        if (result > (UINT_MAX - (name[pos] - '0')) / 10) {
+            break; /* Stop on potential overflow */
+        }
+        result = (result * 10 + (name[pos] - '0'));
+        ++pos;
+    }
+    return result;
+}
+
+/*
+ * Background worker entry point for database-specific statistics collection.
+ *
+ * This function is executed by dynamically spawned background workers to
+ * collect file size statistics for a specific database. Each worker:
+ * 1. Connects to the target database
+ * 2. Verifies the extension is installed
+ * 3. Updates the segment file mapping
+ * 4. Collects file size statistics from all segments
+ * 5. Updates the historical statistics table
+ *
+ * The function runs within its own transaction and handles errors gracefully
+ * by logging warnings rather than aborting the entire collection process.
+ *
+ * Parameters:
+ *   args - Background worker argument which contains database OID and the flag
+ *          which indicates make pauses or not
+ *
+ * Note: This function is called via the background worker framework and
+ *       should not be called directly.
+ */
+void relsizes_database_stats_job(Datum args) {
+    int retcode = 0;
+    char *error = NULL;
+    DbWorkerArg wa = { .d = args };
+
+    optimizer = false;
+    pqsignal(SIGTERM, worker_sigterm);
+    BackgroundWorkerUnblockSignals();
+
+    BackgroundWorkerInitializeConnectionByOid(wa.s.db, InvalidOid, 0);
+
+    SetCurrentStatementStartTimestamp();
+    StartTransactionCommand();
+
+    retcode = SPI_connect();
+    if (retcode < 0) {
+        error = "relsizes_database_stats_job: SPI_connect failed";
+        goto finish_transaction;
+    }
+    PushActiveSnapshot(GetTransactionSnapshot());
+
+    /* Verify extension is installed */
+    int created = plugin_created();
+    if (created < 0) {
+        error = "relsizes_database_stats_job: SPI execute failed while looking 
for plugin";
+        goto finish_spi;
+    } else if (created == 0) {
+        goto finish_spi;
+    }
+
+    retcode = update_segment_file_map_table();
+    if (retcode < 0) {
+        error = "relsizes_database_stats_job: updating segment_file_map 
failed";
+        goto finish_spi;
+    }
+
+    char *sql_delete = "DELETE FROM relsizes_stats_schema.segment_file_sizes";
+    pgstat_report_activity(STATE_RUNNING, sql_delete);
+    retcode = SPI_execute(sql_delete, false, 0);
+    if (retcode != SPI_OK_DELETE) {
+        error = "relsizes_database_stats_job: SPI_execute failed (delete from 
segment_file_sizes)";
+        goto finish_spi;
+    }
+
+    /* Remove this condition after decision how to upgrade extensions is made. 
*/
+    if (SearchSysCacheExists3(PROCNAMEARGSNSP,
+            CStringGetDatum("get_stats_for_database"),
+            PointerGetDatum(buildoidvector((Oid[]){INT4OID}, 1)),
+            ObjectIdGetDatum(get_namespace_oid("relsizes_stats_schema", 
true))))
+    {
+        const char* sql_get_stats =
+            "INSERT INTO relsizes_stats_schema.segment_file_sizes (segment, 
relfilenode, filepath, size, mtime) "
+            "SELECT * FROM relsizes_stats_schema.get_stats_for_database($1)";
+        pgstat_report_activity(STATE_RUNNING, sql_get_stats);
+        retcode = SPI_execute_with_args(sql_get_stats, 1,
+                              (Oid[]){INT4OID},
+                              (Datum[]){ObjectIdGetDatum(MyDatabaseId)},
+                              NULL, false, 0);
+    } else {
+        const char* sql_get_stats =
+            "INSERT INTO relsizes_stats_schema.segment_file_sizes (segment, 
relfilenode, filepath, size, mtime) "
+            "SELECT * FROM relsizes_stats_schema.get_stats_for_database($1, 
$2)";
+        pgstat_report_activity(STATE_RUNNING, sql_get_stats);
+        retcode = SPI_execute_with_args(sql_get_stats, 2,
+                              (Oid[]){OIDOID, BOOLOID},
+                              (Datum[]){ObjectIdGetDatum(MyDatabaseId), 
BoolGetDatum(wa.s.fast)},
+                              NULL, false, 0);
+    }
+    if (retcode != SPI_OK_INSERT) {
+        error = "relsizes_database_stats_job: SPI_execute failed (insert into 
segment_file_sizes)";
+        goto finish_spi;
+    }
+
+    retcode = update_table_sizes_history();
+    if (retcode < 0) {
+        error = "relsizes_database_stats_job: updating tables sizes history 
table failed";
+        goto finish_spi;
+    }
+
+finish_spi:
+    if (error != NULL) {
+        ereport(WARNING, (errmsg("%s: %m", error)));
+        /* Don't abort execution, continue with cleanup */
+    }
+    SPI_finish();
+finish_transaction:
+    PopActiveSnapshot();
+    CommitTransactionCommand();
+    pgstat_report_stat(false);
+    pgstat_report_activity(STATE_IDLE, NULL);
+}
+
+/*
+ * Spawn and manage a background worker for database statistics collection.
+ *
+ * This function creates a new background worker to collect statistics for
+ * a specific database.
+ *
+ * The function:
+ * 1. Configures a new background worker with appropriate settings
+ * 2. Registers and starts the worker
+ * 3. Waits for the worker to complete
+ * 4. Handles any errors during worker execution
+ *
+ * If the worker fails to start or encounters errors during execution,
+ * warnings are logged but the function returns normally to allow
+ * processing of remaining databases.
+ *
+ * Parameters:
+ *   fast - Don't make pauses
+ *   db - OID of the database which worker will collect statistics from
+ *
+ * Note: This function may take significant time to complete as it waits
+ *       for the background worker to finish processing the entire database.
+ */
+static void run_database_stats_worker(bool fast, Oid db) {
+    bool ret;
+    MemoryContext old_ctx;
+    BackgroundWorkerHandle *handle;
+    BgwHandleStatus status;
+    
+    /* Configure background worker */
+    BackgroundWorker database_worker = {
+        .bgw_flags = BGWORKER_SHMEM_ACCESS | 
BGWORKER_BACKEND_DATABASE_CONNECTION,
+        .bgw_start_time = BgWorkerStart_RecoveryFinished,
+        .bgw_restart_time = BGW_NEVER_RESTART,
+        .bgw_library_name = "gp_relsizes_stats",
+        .bgw_function_name = "relsizes_database_stats_job",
+        .bgw_notify_pid = MyProcPid,
+        .bgw_main_arg = ((DbWorkerArg){ .s.db = db, .s.fast = fast }).d,
+        .bgw_start_rule = NULL,
+    };
+    snprintf(database_worker.bgw_name, BGW_MAXLEN, 
"database_relsizes_collector_worker for %u", db);
+    old_ctx = MemoryContextSwitchTo(TopMemoryContext);
+    ret = RegisterDynamicBackgroundWorker(&database_worker, &handle);
+    MemoryContextSwitchTo(old_ctx);
+    if (!ret) {
+        ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_RESOURCES), errmsg("could 
not register background process"),
+                        errhint("You may need to increase 
max_worker_processes.")));
+    }
+    pid_t pid;
+    status = WaitForBackgroundWorkerStartup(handle, &pid);
+    if (status == BGWH_STOPPED)
+        return;
+    if (status != BGWH_STARTED) {
+        ereport(WARNING, (errmsg("Failed to start background worker [%s], 
skipping", database_worker.bgw_name)));
+        return;
+    }
+    status = WaitForBackgroundWorkerShutdownSafely(handle);
+    if (status != BGWH_STOPPED) {
+        ereport(WARNING, (errmsg("Failure during background worker execution 
[%s], continuing", database_worker.bgw_name)));
+        /* Don't abort execution, just log and continue */
+    }
+}
+
+/*
+ * SQL-callable function to collect file statistics for a database.
+ *
+ * This function scans the filesystem directory corresponding to a database
+ * and returns statistics for all regular files found. It's designed to run
+ * on individual segments to collect local file information.
+ *
+ * The function:
+ * 1. Validates the function call context (must support returning a set)
+ * 2. Sets up a tuplestore for result collection
+ * 3. Scans the database directory (base/<dboid>/)
+ * 4. For each regular file, extracts relfilenode from filename
+ * 5. Collects file size and modification time via lstat()
+ * 6. Returns results as a set of tuples
+ *
+ * Parameters:
+ *   Database OID (oid) - identifies which database directory to scan
+ *   Fast (bool) - When true, don't sleep between each collect-phase for files
+ *
+ * Returns:
+ *   Set of tuples containing:
+ *   - segment: current segment ID
+ *   - relfilenode: extracted from filename
+ *   - filepath: full path to the file
+ *   - size: file size in bytes
+ *   - mtime: modification time as Unix timestamp
+ *
+ * Note: Includes configurable delays between file processing to reduce I/O 
load
+ */
+Datum get_stats_for_database(PG_FUNCTION_ARGS) {
+    int segment_id = GpIdentity.segindex;
+    Oid dboid = PG_GETARG_OID(0);
+    bool fast = (PG_NARGS() < 2) ? false : PG_GETARG_BOOL(1);
+
+    char cwd[PATH_MAX];
+    char *data_dir = NULL;
+    char *error = NULL;
+    char *file_path = NULL;
+
+    if (getcwd(cwd, sizeof(cwd)) == NULL) {
+        error = "get_stats_for_database: failed to get current working 
directory";
+        goto finish_data;
+    }
+    data_dir = psprintf("%s/base/%u", cwd, dboid);
+    ReturnSetInfo *rsinfo = (ReturnSetInfo *)fcinfo->resultinfo;
+    /* Validate function call context */
+    if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) {
+        error = "get_stats_for_database: set-valued function called in context 
that cannot accept a set";
+        goto finish_data;
+    }
+    if (!(rsinfo->allowedModes & SFRM_Materialize)) {
+        error = "get_stats_for_database: materialize mode required, but it is 
not allowed in this context";
+        goto finish_data;
+    }
+
+    /* Setup output tuple store */
+    MemoryContext oldcontext = 
MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
+    TupleDesc tupdesc;
+    if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) {
+        MemoryContextSwitchTo(oldcontext);
+        error = "get_stats_for_database: incorrect return type in fcinfo (must 
be a row type)";
+        goto finish_data;
+    }
+    tupdesc = BlessTupleDesc(tupdesc);
+
+    bool randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
+    Tuplestorestate *tupstore = tuplestore_begin_heap(randomAccess, false, 
work_mem);
+    
+    rsinfo->returnMode = SFRM_Materialize;
+    rsinfo->setResult = tupstore;
+    rsinfo->setDesc = tupdesc;
+
+    Datum outputValues[FILEINFO_ARGS_CNT];
+    bool outputNulls[FILEINFO_ARGS_CNT] = { false };
+
+    MemoryContextSwitchTo(oldcontext);
+
+    /* Scan database directory for files */
+    DIR *current_dir = AllocateDir(data_dir);
+    if (!current_dir) {
+        error = "get_stats_for_database: failed to allocate current directory";
+        goto finish_data;
+    }
+
+    struct dirent *file;
+    while ((file = ReadDir(current_dir, data_dir)) != NULL) {
+        char *filename = file->d_name;
+        if (strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0) {
+            continue;
+        }
+
+        file_path = psprintf("%s/%s", data_dir, filename);
+        struct stat stb;
+        if (lstat(file_path, &stb) < 0) {
+            ereport(WARNING,
+                    (errmsg("get_stats_for_database: lstat failed with %s file 
(unexpected behavior)", file_path)));
+            pfree(file_path);
+            continue;
+        }
+
+        if (S_ISREG(stb.st_mode)) {
+            /* Process regular files and collect size statistics */
+            outputValues[0] = Int32GetDatum(segment_id);
+            outputValues[1] = ObjectIdGetDatum(fill_relfilenode(filename));
+            outputValues[2] = CStringGetTextDatum(file_path);
+            outputValues[3] = Int64GetDatum(stb.st_size);
+            outputValues[4] = Int64GetDatum(stb.st_mtime);
+
+            tuplestore_putvalues(tupstore, tupdesc, outputValues, outputNulls);
+
+            if (fast)
+                CHECK_FOR_INTERRUPTS();
+            else {
+                /* Brief pause between file processing to reduce system load */
+                int retcode = WaitLatch(&MyProc->procLatch,
+                                WL_LATCH_SET | WL_TIMEOUT | 
WL_POSTMASTER_DEATH,
+                                worker_file_naptime, WAIT_EVENT_BUFFER_IO);
+                ResetLatch(&MyProc->procLatch);
+
+                CHECK_FOR_INTERRUPTS();
+
+                if (retcode & WL_POSTMASTER_DEATH) {
+                    proc_exit(1);
+                }
+            }
+        }
+        pfree(file_path);
+    }
+
+    FreeDir(current_dir);
+finish_data:
+    pfree(data_dir);
+    if (error != NULL) {
+        ereport(WARNING, (errmsg("%s: %m", error)));
+        /* Don't abort execution, return result */
+    }

Review Comment:
   ```
   void pfree(void *ptr)
   {
        if (ptr != NULL)
                free(ptr);
   }
   ```



##########
gpcontrib/gp_relsizes_stats/src/gp_relsizes_stats.c:
##########
@@ -0,0 +1,934 @@
+#include "postgres.h"
+
+/* Required headers for background workers */
+#include "miscadmin.h"
+#include "postmaster/bgworker.h"
+#include "storage/ipc.h"
+#include "storage/latch.h"
+#include "storage/lwlock.h"
+#include "storage/proc.h"
+#include "storage/shmem.h"
+
+/* Additional headers for extension functionality */
+#include "access/xact.h"
+#include "executor/spi.h"
+#include "fmgr.h"
+#include "lib/stringinfo.h"
+#include "pgstat.h"
+#include "tcop/utility.h"
+
+#include "catalog/namespace.h"
+#include "cdb/cdbvars.h"
+#include "commands/defrem.h"
+#include "funcapi.h"
+
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/guc.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/snapmgr.h"
+#include "utils/syscache.h"
+
+#include <assert.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <unistd.h>
+#include <limits.h>
+
+#define FILEINFO_ARGS_CNT 5
+#define HOUR_TIME 3600000    /* milliseconds in hour */
+#define MINUTE_TIME 60000    /* milliseconds in minute */
+#define FILE_NAPTIME 1       /* default naptime between file processing in 
milliseconds */
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(get_stats_for_database);
+PG_FUNCTION_INFO_V1(relsizes_collect_stats_once);
+Datum get_stats_for_database(PG_FUNCTION_ARGS);
+Datum relsizes_collect_stats_once(PG_FUNCTION_ARGS);
+
+static void worker_sigterm(SIGNAL_ARGS);
+static Oid *get_databases_oids(int *databases_cnt, MemoryContext ctx, bool 
create_transaction);
+static int update_segment_file_map_table(void);
+static int update_table_sizes_history(void);
+static void get_stats_for_databases(Oid *databases_oids, int databases_cnt, 
bool fast);
+static void run_database_stats_worker(bool fast, Oid db);
+static int plugin_created(void);
+static BgwHandleStatus 
WaitForBackgroundWorkerShutdownSafely(BackgroundWorkerHandle *handle);
+static int delete_data_in_history(void);
+static int put_data_into_history(void);
+void _PG_init(void);
+
+void relsizes_collect_stats(Datum main_arg);
+void relsizes_database_stats_job(Datum args);
+
+/* Global variables */
+static int worker_restart_naptime = 0;
+static int worker_database_naptime = 0;
+static int worker_file_naptime = 0;
+static bool enabled = false;
+
+static volatile sig_atomic_t got_sigterm = false;
+
+typedef union DbWorkerArg {
+    Datum d;
+    struct {
+        Oid db;
+        bool fast;
+    } s;
+} DbWorkerArg;
+
+static_assert(sizeof(Datum) == sizeof(DbWorkerArg), "Invalid size of structure 
in DbWorkerArg");
+
+/*
+ * Signal handler for SIGTERM in background worker processes.
+ *
+ * This handler is called when the postmaster requests the background worker
+ * to shut down. It sets the got_sigterm flag and wakes up the main worker
+ * loop by setting the process latch.
+ *
+ * The function follows PostgreSQL signal handling conventions:
+ * - Saves and restores errno
+ * - Uses only async-signal-safe operations
+ * - Sets a flag that the main loop can check
+ */
+static void worker_sigterm(SIGNAL_ARGS) {
+    int save_errno = errno;
+    got_sigterm = true;
+    if (MyProc) {
+        SetLatch(&MyProc->procLatch);
+    }
+    errno = save_errno;
+}
+
+/*
+ * Wait for a background worker to stop with timeout and error handling.
+ * 
+ * This is a modified version that adds timeout functionality and improved
+ * error handling to prevent infinite loops in case of hung workers.
+ * Returns BGWH_STOPPED on success, BGWH_POSTMASTER_DIED on error/timeout.
+ */
+static BgwHandleStatus 
WaitForBackgroundWorkerShutdownSafely(BackgroundWorkerHandle *handle) {
+    BgwHandleStatus status = BGWH_NOT_YET_STARTED;
+    int rc;
+    int attempts = 0;
+    const int max_attempts = 5 * HOUR_TIME / 100; /* maximum 5 hours wait time 
*/
+
+    PG_TRY();
+    {
+        while (attempts < max_attempts) {
+            pid_t pid;
+
+            status = GetBackgroundWorkerPid(handle, &pid);
+            if (status == BGWH_STOPPED) {
+                return status;
+            }
+
+            /* Add 100ms timeout instead of infinite wait */
+            rc = WaitLatch(&MyProc->procLatch, WL_LATCH_SET | WL_TIMEOUT | 
WL_POSTMASTER_DEATH, 100L, WAIT_EVENT_BGWORKER_SHUTDOWN);
+
+            ResetLatch(&MyProc->procLatch);
+
+            if (rc & WL_POSTMASTER_DEATH) {
+                status = BGWH_POSTMASTER_DIED;
+                break;
+            }
+
+            /* Check for interrupts but don't let them break the entire 
process */
+            if (QueryCancelPending || ProcDiePending) {
+                ereport(WARNING, 
(errmsg("WaitForBackgroundWorkerShutdownSafely: received interrupt signal, 
stopping wait")));
+                status = BGWH_POSTMASTER_DIED; /* Return status as if 
postmaster died */
+                break;
+            }
+
+            attempts++;
+        }
+
+        /* If maximum attempts reached */
+        if (attempts >= max_attempts) {
+            ereport(WARNING, (errmsg("WaitForBackgroundWorkerShutdownSafely: 
timeout after %d attempts", max_attempts)));
+            status = BGWH_POSTMASTER_DIED; /* Return error status */
+        }
+    }
+    PG_CATCH();
+    {
+        /* Log error but do NOT re-throw exception */
+        ereport(WARNING, (errmsg("WaitForBackgroundWorkerShutdownSafely: 
caught exception, returning error status")));
+        /* Return error status instead of PG_RE_THROW() */
+        return BGWH_POSTMASTER_DIED;
+    }
+    PG_END_TRY();
+    return status;
+}
+
+/*
+ * Retrieve list of database OIDs from the catalog.
+ *
+ * This function queries pg_database to get all user databases (excluding
+ * system databases like template0, template1, diskquota, and gpperfmon).
+ * 
+ * Parameters:
+ *   databases_cnt - Output parameter, set to number of databases found
+ *   ctx - Memory context to allocate result in (for cross-call persistence)
+ *   create_transaction - Whether to create a new transaction for the query
+ *
+ * Returns:
+ *   Array of OIDs allocated in ctx, or NULL on error.
+ *   The array length is databases_cnt.
+ *
+ * Note: Caller is responsible for freeing the returned memory when done.
+ */
+static Oid *get_databases_oids(int *databases_cnt, MemoryContext ctx, bool 
create_transaction) {
+    const char *sql =
+        "SELECT oid"
+        "  FROM pg_database"
+        " WHERE datname NOT IN ('template0', 'template1', 'diskquota', 
'gpperfmon')";
+    const char *error = NULL;
+
+    Oid *databases_oids = NULL;
+    *databases_cnt = 0;
+
+    if (create_transaction) {
+        SetCurrentStatementStartTimestamp();
+        StartTransactionCommand();
+    }
+
+    if (SPI_connect() < 0) {
+        error = "get_databases_oids: SPI_connect failed";
+        goto finish_transaction;
+    }
+    if (create_transaction) {
+        PushActiveSnapshot(GetTransactionSnapshot());
+        pgstat_report_activity(STATE_RUNNING, sql);
+    }
+
+    if (SPI_execute(sql, true, 0) != SPI_OK_SELECT) {
+        error = "get_databases_oids: SPI_execute failed (select datname, oid)";
+        goto finish_spi;
+    }
+
+    /* Prepare tuple processing variables */
+
+    *databases_cnt = SPI_processed;
+    MemoryContext old_context = MemoryContextSwitchTo(ctx);
+    databases_oids = palloc((*databases_cnt) * sizeof(*databases_oids));
+    MemoryContextSwitchTo(old_context);
+
+    for (int i = 0; i < SPI_processed; ++i) {
+        Datum oid_datum;
+        bool oid_nullable;
+
+        heap_deform_tuple(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 
&oid_datum, &oid_nullable);
+
+        databases_oids[i] = DatumGetObjectId(oid_datum);
+    }
+
+finish_spi:
+    SPI_finish();
+finish_transaction:
+    if (create_transaction) {
+        PopActiveSnapshot();
+        CommitTransactionCommand();
+        pgstat_report_stat(false);
+        pgstat_report_activity(STATE_IDLE, NULL);
+    }
+
+    if (error != NULL) {
+        ereport(WARNING, (errmsg("%s: %m", error)));
+        return NULL; /* Return NULL on error */
+    }
+
+    return databases_oids;
+}
+
+/*
+ * Update the segment_file_map table with current relation file mappings.
+ *
+ * This function refreshes the mapping between relation OIDs and their
+ * physical file nodes across all segments. It first deletes the existing
+ * data and then repopulates it by querying pg_class on all segments.
+ *
+ * The mapping is essential for correlating file statistics collected
+ * from the filesystem with actual database relations.
+ *
+ * Returns:
+ *   0 on success, negative value on error
+ *
+ * Note: This function assumes it's running within an active SPI context.
+ */
+static int update_segment_file_map_table() {
+    int retcode = 0;
+    char *sql_delete = "DELETE FROM relsizes_stats_schema.segment_file_map";
+    char *sql_insert = "INSERT INTO relsizes_stats_schema.segment_file_map 
SELECT gp_segment_id, oid, relfilenode FROM "
+                       "gp_dist_random('pg_class')";
+    char *error = NULL;
+    pgstat_report_activity(STATE_RUNNING, sql_delete);
+    retcode = SPI_execute(sql_delete, false, 0);
+    if (retcode != SPI_OK_DELETE) {
+        error = "update_segment_file_map_table: failed to delete from table";
+        goto cleanup;
+    }
+    
+    pgstat_report_activity(STATE_RUNNING, sql_insert);
+    retcode = SPI_execute(sql_insert, false, 0);
+    if (retcode != SPI_OK_INSERT) {
+        error = "update_segment_file_map_table: failed to insert new rows into 
table";
+        goto cleanup;
+    }
+
+cleanup:
+    pgstat_report_activity(STATE_IDLE, NULL);
+    if (error != NULL) {
+        ereport(WARNING, (errmsg("%s: %m", error)));
+    }
+    return retcode;
+}
+
+/*
+ * Check if a character is a digit (0-9).
+ *
+ * Simple utility function used by fill_relfilenode() to parse
+ * numeric portions of filenames.
+ *
+ * Returns:
+ *   true if character is a digit, false otherwise
+ */
+static bool is_number(char symbol) { return '0' <= symbol && symbol <= '9'; }
+
+/*
+ * Extract relfilenode from filename by finding the first sequence of digits
+ * in the filename and converting it to numeric value
+ */
+static unsigned int fill_relfilenode(char *name) {
+    unsigned int result = 0, pos = 0;
+    size_t name_len = strlen(name);
+    
+    while (pos < name_len && !is_number(name[pos])) {
+        ++pos;
+    }
+    while (pos < name_len && is_number(name[pos])) {
+        /* Check for overflow to prevent integer overflow */
+        if (result > (UINT_MAX - (name[pos] - '0')) / 10) {
+            break; /* Stop on potential overflow */
+        }
+        result = (result * 10 + (name[pos] - '0'));
+        ++pos;
+    }
+    return result;
+}
+
+/*
+ * Background worker entry point for database-specific statistics collection.
+ *
+ * This function is executed by dynamically spawned background workers to
+ * collect file size statistics for a specific database. Each worker:
+ * 1. Connects to the target database
+ * 2. Verifies the extension is installed
+ * 3. Updates the segment file mapping
+ * 4. Collects file size statistics from all segments
+ * 5. Updates the historical statistics table
+ *
+ * The function runs within its own transaction and handles errors gracefully
+ * by logging warnings rather than aborting the entire collection process.
+ *
+ * Parameters:
+ *   args - Background worker argument which contains database OID and the flag
+ *          which indicates make pauses or not
+ *
+ * Note: This function is called via the background worker framework and
+ *       should not be called directly.
+ */
+void relsizes_database_stats_job(Datum args) {
+    int retcode = 0;
+    char *error = NULL;
+    DbWorkerArg wa = { .d = args };
+
+    optimizer = false;
+    pqsignal(SIGTERM, worker_sigterm);
+    BackgroundWorkerUnblockSignals();
+
+    BackgroundWorkerInitializeConnectionByOid(wa.s.db, InvalidOid, 0);
+
+    SetCurrentStatementStartTimestamp();
+    StartTransactionCommand();
+
+    retcode = SPI_connect();
+    if (retcode < 0) {
+        error = "relsizes_database_stats_job: SPI_connect failed";
+        goto finish_transaction;
+    }
+    PushActiveSnapshot(GetTransactionSnapshot());
+
+    /* Verify extension is installed */
+    int created = plugin_created();
+    if (created < 0) {
+        error = "relsizes_database_stats_job: SPI execute failed while looking 
for plugin";
+        goto finish_spi;
+    } else if (created == 0) {
+        goto finish_spi;
+    }
+
+    retcode = update_segment_file_map_table();
+    if (retcode < 0) {
+        error = "relsizes_database_stats_job: updating segment_file_map 
failed";
+        goto finish_spi;
+    }
+
+    char *sql_delete = "DELETE FROM relsizes_stats_schema.segment_file_sizes";
+    pgstat_report_activity(STATE_RUNNING, sql_delete);
+    retcode = SPI_execute(sql_delete, false, 0);
+    if (retcode != SPI_OK_DELETE) {
+        error = "relsizes_database_stats_job: SPI_execute failed (delete from 
segment_file_sizes)";
+        goto finish_spi;
+    }
+
+    /* Remove this condition after decision how to upgrade extensions is made. 
*/
+    if (SearchSysCacheExists3(PROCNAMEARGSNSP,
+            CStringGetDatum("get_stats_for_database"),
+            PointerGetDatum(buildoidvector((Oid[]){INT4OID}, 1)),
+            ObjectIdGetDatum(get_namespace_oid("relsizes_stats_schema", 
true))))
+    {
+        const char* sql_get_stats =
+            "INSERT INTO relsizes_stats_schema.segment_file_sizes (segment, 
relfilenode, filepath, size, mtime) "
+            "SELECT * FROM relsizes_stats_schema.get_stats_for_database($1)";
+        pgstat_report_activity(STATE_RUNNING, sql_get_stats);
+        retcode = SPI_execute_with_args(sql_get_stats, 1,
+                              (Oid[]){INT4OID},
+                              (Datum[]){ObjectIdGetDatum(MyDatabaseId)},
+                              NULL, false, 0);
+    } else {
+        const char* sql_get_stats =
+            "INSERT INTO relsizes_stats_schema.segment_file_sizes (segment, 
relfilenode, filepath, size, mtime) "
+            "SELECT * FROM relsizes_stats_schema.get_stats_for_database($1, 
$2)";
+        pgstat_report_activity(STATE_RUNNING, sql_get_stats);
+        retcode = SPI_execute_with_args(sql_get_stats, 2,
+                              (Oid[]){OIDOID, BOOLOID},
+                              (Datum[]){ObjectIdGetDatum(MyDatabaseId), 
BoolGetDatum(wa.s.fast)},
+                              NULL, false, 0);
+    }
+    if (retcode != SPI_OK_INSERT) {
+        error = "relsizes_database_stats_job: SPI_execute failed (insert into 
segment_file_sizes)";
+        goto finish_spi;
+    }
+
+    retcode = update_table_sizes_history();
+    if (retcode < 0) {
+        error = "relsizes_database_stats_job: updating tables sizes history 
table failed";
+        goto finish_spi;
+    }
+
+finish_spi:
+    if (error != NULL) {
+        ereport(WARNING, (errmsg("%s: %m", error)));
+        /* Don't abort execution, continue with cleanup */
+    }
+    SPI_finish();
+finish_transaction:
+    PopActiveSnapshot();
+    CommitTransactionCommand();
+    pgstat_report_stat(false);
+    pgstat_report_activity(STATE_IDLE, NULL);
+}
+
+/*
+ * Spawn and manage a background worker for database statistics collection.
+ *
+ * This function creates a new background worker to collect statistics for
+ * a specific database.
+ *
+ * The function:
+ * 1. Configures a new background worker with appropriate settings
+ * 2. Registers and starts the worker
+ * 3. Waits for the worker to complete
+ * 4. Handles any errors during worker execution
+ *
+ * If the worker fails to start or encounters errors during execution,
+ * warnings are logged but the function returns normally to allow
+ * processing of remaining databases.
+ *
+ * Parameters:
+ *   fast - Don't make pauses
+ *   db - OID of the database which worker will collect statistics from
+ *
+ * Note: This function may take significant time to complete as it waits
+ *       for the background worker to finish processing the entire database.
+ */
+static void run_database_stats_worker(bool fast, Oid db) {
+    bool ret;
+    MemoryContext old_ctx;
+    BackgroundWorkerHandle *handle;
+    BgwHandleStatus status;
+    
+    /* Configure background worker */
+    BackgroundWorker database_worker = {
+        .bgw_flags = BGWORKER_SHMEM_ACCESS | 
BGWORKER_BACKEND_DATABASE_CONNECTION,
+        .bgw_start_time = BgWorkerStart_RecoveryFinished,
+        .bgw_restart_time = BGW_NEVER_RESTART,
+        .bgw_library_name = "gp_relsizes_stats",
+        .bgw_function_name = "relsizes_database_stats_job",
+        .bgw_notify_pid = MyProcPid,
+        .bgw_main_arg = ((DbWorkerArg){ .s.db = db, .s.fast = fast }).d,
+        .bgw_start_rule = NULL,
+    };
+    snprintf(database_worker.bgw_name, BGW_MAXLEN, 
"database_relsizes_collector_worker for %u", db);
+    old_ctx = MemoryContextSwitchTo(TopMemoryContext);
+    ret = RegisterDynamicBackgroundWorker(&database_worker, &handle);
+    MemoryContextSwitchTo(old_ctx);
+    if (!ret) {
+        ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_RESOURCES), errmsg("could 
not register background process"),
+                        errhint("You may need to increase 
max_worker_processes.")));
+    }
+    pid_t pid;
+    status = WaitForBackgroundWorkerStartup(handle, &pid);
+    if (status == BGWH_STOPPED)
+        return;
+    if (status != BGWH_STARTED) {
+        ereport(WARNING, (errmsg("Failed to start background worker [%s], 
skipping", database_worker.bgw_name)));
+        return;
+    }
+    status = WaitForBackgroundWorkerShutdownSafely(handle);
+    if (status != BGWH_STOPPED) {
+        ereport(WARNING, (errmsg("Failure during background worker execution 
[%s], continuing", database_worker.bgw_name)));
+        /* Don't abort execution, just log and continue */
+    }
+}
+
+/*
+ * SQL-callable function to collect file statistics for a database.
+ *
+ * This function scans the filesystem directory corresponding to a database
+ * and returns statistics for all regular files found. It's designed to run
+ * on individual segments to collect local file information.
+ *
+ * The function:
+ * 1. Validates the function call context (must support returning a set)
+ * 2. Sets up a tuplestore for result collection
+ * 3. Scans the database directory (base/<dboid>/)
+ * 4. For each regular file, extracts relfilenode from filename
+ * 5. Collects file size and modification time via lstat()
+ * 6. Returns results as a set of tuples
+ *
+ * Parameters:
+ *   Database OID (oid) - identifies which database directory to scan
+ *   Fast (bool) - When true, don't sleep between each collect-phase for files
+ *
+ * Returns:
+ *   Set of tuples containing:
+ *   - segment: current segment ID
+ *   - relfilenode: extracted from filename
+ *   - filepath: full path to the file
+ *   - size: file size in bytes
+ *   - mtime: modification time as Unix timestamp
+ *
+ * Note: Includes configurable delays between file processing to reduce I/O 
load
+ */
+Datum get_stats_for_database(PG_FUNCTION_ARGS) {
+    int segment_id = GpIdentity.segindex;
+    Oid dboid = PG_GETARG_OID(0);
+    bool fast = (PG_NARGS() < 2) ? false : PG_GETARG_BOOL(1);
+
+    char cwd[PATH_MAX];
+    char *data_dir = NULL;
+    char *error = NULL;
+    char *file_path = NULL;
+
+    if (getcwd(cwd, sizeof(cwd)) == NULL) {
+        error = "get_stats_for_database: failed to get current working 
directory";
+        goto finish_data;
+    }
+    data_dir = psprintf("%s/base/%u", cwd, dboid);
+    ReturnSetInfo *rsinfo = (ReturnSetInfo *)fcinfo->resultinfo;
+    /* Validate function call context */
+    if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) {
+        error = "get_stats_for_database: set-valued function called in context 
that cannot accept a set";
+        goto finish_data;
+    }
+    if (!(rsinfo->allowedModes & SFRM_Materialize)) {
+        error = "get_stats_for_database: materialize mode required, but it is 
not allowed in this context";
+        goto finish_data;
+    }
+
+    /* Setup output tuple store */
+    MemoryContext oldcontext = 
MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
+    TupleDesc tupdesc;
+    if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) {
+        MemoryContextSwitchTo(oldcontext);
+        error = "get_stats_for_database: incorrect return type in fcinfo (must 
be a row type)";
+        goto finish_data;
+    }
+    tupdesc = BlessTupleDesc(tupdesc);
+
+    bool randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
+    Tuplestorestate *tupstore = tuplestore_begin_heap(randomAccess, false, 
work_mem);
+    
+    rsinfo->returnMode = SFRM_Materialize;
+    rsinfo->setResult = tupstore;
+    rsinfo->setDesc = tupdesc;
+
+    Datum outputValues[FILEINFO_ARGS_CNT];
+    bool outputNulls[FILEINFO_ARGS_CNT] = { false };
+
+    MemoryContextSwitchTo(oldcontext);
+
+    /* Scan database directory for files */
+    DIR *current_dir = AllocateDir(data_dir);
+    if (!current_dir) {
+        error = "get_stats_for_database: failed to allocate current directory";
+        goto finish_data;
+    }
+
+    struct dirent *file;
+    while ((file = ReadDir(current_dir, data_dir)) != NULL) {
+        char *filename = file->d_name;
+        if (strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0) {
+            continue;
+        }
+
+        file_path = psprintf("%s/%s", data_dir, filename);
+        struct stat stb;
+        if (lstat(file_path, &stb) < 0) {
+            ereport(WARNING,
+                    (errmsg("get_stats_for_database: lstat failed with %s file 
(unexpected behavior)", file_path)));
+            pfree(file_path);
+            continue;
+        }
+
+        if (S_ISREG(stb.st_mode)) {
+            /* Process regular files and collect size statistics */
+            outputValues[0] = Int32GetDatum(segment_id);
+            outputValues[1] = ObjectIdGetDatum(fill_relfilenode(filename));
+            outputValues[2] = CStringGetTextDatum(file_path);
+            outputValues[3] = Int64GetDatum(stb.st_size);
+            outputValues[4] = Int64GetDatum(stb.st_mtime);
+
+            tuplestore_putvalues(tupstore, tupdesc, outputValues, outputNulls);
+
+            if (fast)
+                CHECK_FOR_INTERRUPTS();
+            else {
+                /* Brief pause between file processing to reduce system load */
+                int retcode = WaitLatch(&MyProc->procLatch,
+                                WL_LATCH_SET | WL_TIMEOUT | 
WL_POSTMASTER_DEATH,
+                                worker_file_naptime, WAIT_EVENT_BUFFER_IO);
+                ResetLatch(&MyProc->procLatch);
+
+                CHECK_FOR_INTERRUPTS();
+
+                if (retcode & WL_POSTMASTER_DEATH) {
+                    proc_exit(1);
+                }
+            }
+        }
+        pfree(file_path);
+    }
+
+    FreeDir(current_dir);
+finish_data:
+    pfree(data_dir);
+    if (error != NULL) {
+        ereport(WARNING, (errmsg("%s: %m", error)));
+        /* Don't abort execution, return result */
+    }

Review Comment:
   ```c
   void pfree(void *ptr)
   {
        if (ptr != NULL)
                free(ptr);
   }
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to