LCOV - code coverage report
Current view: top level - contrib/pg_stash_advice - stashpersist.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 77.5 % 267 207
Test Date: 2026-04-28 05:16:27 Functions: 91.7 % 12 11
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*-------------------------------------------------------------------------
       2              :  *
       3              :  * stashpersist.c
       4              :  *    Persistence support for pg_stash_advice.
       5              :  *
       6              :  * Copyright (c) 2016-2026, PostgreSQL Global Development Group
       7              :  *
       8              :  *    contrib/pg_stash_advice/stashpersist.c
       9              :  *
      10              :  *-------------------------------------------------------------------------
      11              :  */
      12              : #include "postgres.h"
      13              : 
      14              : #include <sys/stat.h>
      15              : 
      16              : #include "common/hashfn.h"
      17              : #include "miscadmin.h"
      18              : #include "pg_stash_advice.h"
      19              : #include "postmaster/bgworker.h"
      20              : #include "postmaster/interrupt.h"
      21              : #include "storage/fd.h"
      22              : #include "storage/ipc.h"
      23              : #include "storage/latch.h"
      24              : #include "storage/proc.h"
      25              : #include "storage/procsignal.h"
      26              : #include "utils/backend_status.h"
      27              : #include "utils/guc.h"
      28              : #include "utils/memutils.h"
      29              : #include "utils/timestamp.h"
      30              : 
      31              : typedef struct pgsa_writer_context
      32              : {
      33              :     char        pathname[MAXPGPATH];
      34              :     FILE       *file;
      35              :     pgsa_stash_name_table_hash *nhash;
      36              :     StringInfoData buf;
      37              :     int         entries_written;
      38              : } pgsa_writer_context;
      39              : 
      40              : /*
      41              :  * A parsed entry line, with pointers into the slurp buffer.
      42              :  */
      43              : typedef struct pgsa_saved_entry
      44              : {
      45              :     char       *stash_name;
      46              :     int64       queryId;
      47              :     char       *advice_string;
      48              : } pgsa_saved_entry;
      49              : 
      50              : /*
      51              :  * simplehash for detecting duplicate stash names during parsing.
      52              :  * Keyed by stash name (char *), pointing into the slurp buffer.
      53              :  */
      54              : typedef struct pgsa_saved_stash
      55              : {
      56              :     uint32      status;
      57              :     char       *name;
      58              : } pgsa_saved_stash;
      59              : 
      60              : #define SH_PREFIX pgsa_saved_stash_table
      61              : #define SH_ELEMENT_TYPE pgsa_saved_stash
      62              : #define SH_KEY_TYPE char *
      63              : #define SH_KEY name
      64              : #define SH_HASH_KEY(tb, key) hash_bytes((const unsigned char *) (key), strlen(key))
      65              : #define SH_EQUAL(tb, a, b) (strcmp(a, b) == 0)
      66              : #define SH_SCOPE static inline
      67              : #define SH_DEFINE
      68              : #define SH_DECLARE
      69              : #include "lib/simplehash.h"
      70              : 
      71              : extern PGDLLEXPORT void pg_stash_advice_worker_main(Datum main_arg);
      72              : static void pgsa_append_tsv_escaped_string(StringInfo buf, const char *str);
      73              : static void pgsa_detach_shmem(int code, Datum arg);
      74              : static char *pgsa_next_tsv_field(char **cursor);
      75              : static void pgsa_read_from_disk(void);
      76              : static void pgsa_restore_entries(pgsa_saved_entry *entries, int num_entries);
      77              : static void pgsa_restore_stashes(pgsa_saved_stash_table_hash *saved_stashes);
      78              : static void pgsa_unescape_tsv_field(char *str, const char *filename,
      79              :                                     unsigned lineno);
      80              : static void pgsa_write_entries(pgsa_writer_context *wctx);
      81              : pg_noreturn static void pgsa_write_error(pgsa_writer_context *wctx);
      82              : static void pgsa_write_stashes(pgsa_writer_context *wctx);
      83              : static void pgsa_write_to_disk(void);
      84              : 
      85              : /*
      86              :  * Background worker entry point for pg_stash_advice persistence.
      87              :  *
      88              :  * On startup, if stashes_ready is set, we load previously saved
      89              :  * stash data from disk.  Then we enter a loop, periodically checking whether
      90              :  * any changes have been made (via the change_count atomic counter) and
      91              :  * writing them to disk.  On shutdown, we perform a final write.
      92              :  */
      93              : PGDLLEXPORT void
      94            4 : pg_stash_advice_worker_main(Datum main_arg)
      95              : {
      96              :     uint64      last_change_count;
      97            4 :     TimestampTz last_write_time = 0;
      98              : 
      99              :     /* Establish signal handlers; once that's done, unblock signals. */
     100            4 :     pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
     101            4 :     pqsignal(SIGHUP, SignalHandlerForConfigReload);
     102            4 :     pqsignal(SIGUSR1, procsignal_sigusr1_handler);
     103            4 :     BackgroundWorkerUnblockSignals();
     104              : 
     105              :     /* Log a debug message */
     106            4 :     ereport(DEBUG1,
     107              :             errmsg("pg_stash_advice worker started"));
     108              : 
     109              :     /* Set up session user so pgstat can report it. */
     110            4 :     InitializeSessionUserIdStandalone();
     111              : 
     112              :     /* Report this worker in pg_stat_activity. */
     113            4 :     pgstat_beinit();
     114            4 :     pgstat_bestart_initial();
     115            4 :     pgstat_bestart_final();
     116              : 
     117              :     /* Attach to shared memory structures. */
     118            4 :     pgsa_attach();
     119              : 
     120              :     /* Set on-detach hook so that our PID will be cleared on exit. */
     121            4 :     before_shmem_exit(pgsa_detach_shmem, 0);
     122              : 
     123              :     /*
     124              :      * Store our PID in shared memory, unless there's already another worker
     125              :      * running, in which case just exit.
     126              :      */
     127            4 :     LWLockAcquire(&pgsa_state->lock, LW_EXCLUSIVE);
     128            4 :     if (pgsa_state->bgworker_pid != InvalidPid)
     129              :     {
     130            0 :         LWLockRelease(&pgsa_state->lock);
     131            0 :         ereport(LOG,
     132              :                 (errmsg("pg_stash_advice worker is already running under PID %d",
     133              :                         (int) pgsa_state->bgworker_pid)));
     134            0 :         return;
     135              :     }
     136            4 :     pgsa_state->bgworker_pid = MyProcPid;
     137            4 :     LWLockRelease(&pgsa_state->lock);
     138              : 
     139              :     /*
     140              :      * If pg_stash_advice.persist was set to true during
     141              :      * process_shared_preload_libraries() and the data has not yet been
     142              :      * successfully loaded, load it now.
     143              :      */
     144            4 :     if (pg_atomic_unlocked_test_flag(&pgsa_state->stashes_ready))
     145              :     {
     146            4 :         pgsa_read_from_disk();
     147            4 :         pg_atomic_test_set_flag(&pgsa_state->stashes_ready);
     148              :     }
     149              : 
     150              :     /* Note the current change count so we can detect future changes. */
     151            4 :     last_change_count = pg_atomic_read_u64(&pgsa_state->change_count);
     152              : 
     153              :     /* Periodically write to disk until terminated. */
     154           12 :     while (!ShutdownRequestPending)
     155              :     {
     156              :         /* In case of a SIGHUP, just reload the configuration. */
     157            8 :         if (ConfigReloadPending)
     158              :         {
     159            0 :             ConfigReloadPending = false;
     160            0 :             ProcessConfigFile(PGC_SIGHUP);
     161              :         }
     162              : 
     163            8 :         if (pg_stash_advice_persist_interval <= 0)
     164              :         {
     165              :             /* Only writing at shutdown, so just wait forever. */
     166            8 :             (void) WaitLatch(MyLatch,
     167              :                              WL_LATCH_SET | WL_EXIT_ON_PM_DEATH,
     168              :                              -1L,
     169              :                              PG_WAIT_EXTENSION);
     170              :         }
     171              :         else
     172              :         {
     173              :             TimestampTz next_write_time;
     174              :             long        delay_in_ms;
     175              :             uint64      current_change_count;
     176              : 
     177              :             /* Compute when the next write should happen. */
     178            0 :             next_write_time =
     179            0 :                 TimestampTzPlusMilliseconds(last_write_time,
     180              :                                             pg_stash_advice_persist_interval * 1000);
     181              :             delay_in_ms =
     182            0 :                 TimestampDifferenceMilliseconds(GetCurrentTimestamp(),
     183              :                                                 next_write_time);
     184              : 
     185              :             /*
     186              :              * When we reach next_write_time, we always update last_write_time
     187              :              * (which is really the time at which we last considered writing),
     188              :              * but we only actually write to disk if something has changed.
     189              :              */
     190            0 :             if (delay_in_ms <= 0)
     191              :             {
     192              :                 current_change_count =
     193            0 :                     pg_atomic_read_u64(&pgsa_state->change_count);
     194            0 :                 if (current_change_count != last_change_count)
     195              :                 {
     196            0 :                     pgsa_write_to_disk();
     197            0 :                     last_change_count = current_change_count;
     198              :                 }
     199            0 :                 last_write_time = GetCurrentTimestamp();
     200            0 :                 continue;
     201              :             }
     202              : 
     203              :             /* Sleep until the next write time. */
     204            0 :             (void) WaitLatch(MyLatch,
     205              :                              WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
     206              :                              delay_in_ms,
     207              :                              PG_WAIT_EXTENSION);
     208              :         }
     209              : 
     210            8 :         ResetLatch(MyLatch);
     211              :     }
     212              : 
     213              :     /* Write one last time before exiting. */
     214            4 :     pgsa_write_to_disk();
     215              : }
     216              : 
     217              : /*
     218              :  * Clear our PID from shared memory on exit.
     219              :  */
     220              : static void
     221            4 : pgsa_detach_shmem(int code, Datum arg)
     222              : {
     223            4 :     LWLockAcquire(&pgsa_state->lock, LW_EXCLUSIVE);
     224            4 :     if (pgsa_state->bgworker_pid == MyProcPid)
     225            4 :         pgsa_state->bgworker_pid = InvalidPid;
     226            4 :     LWLockRelease(&pgsa_state->lock);
     227            4 : }
     228              : 
     229              : /*
     230              :  * Load advice stash data from a dump file on disk, if there is one.
     231              :  */
     232              : static void
     233            4 : pgsa_read_from_disk(void)
     234              : {
     235              :     struct stat statbuf;
     236              :     FILE       *file;
     237              :     char       *filebuf;
     238              :     size_t      nread;
     239              :     char       *p;
     240              :     unsigned    lineno;
     241              :     pgsa_saved_stash_table_hash *saved_stashes;
     242            4 :     int         num_stashes = 0;
     243              :     pgsa_saved_entry *entries;
     244            4 :     int         num_entries = 0;
     245            4 :     int         max_entries = 64;
     246              :     MemoryContext tmpcxt;
     247              :     MemoryContext oldcxt;
     248              : 
     249              :     Assert(pgsa_entry_dshash != NULL);
     250              : 
     251              :     /*
     252              :      * Clear any existing shared memory state.
     253              :      *
     254              :      * Normally, there won't be any, but if this function was called before
     255              :      * and failed after beginning to apply changes to shared memory, then we
     256              :      * need to get rid of any entries created at that time before trying
     257              :      * again.
     258              :      */
     259            4 :     LWLockAcquire(&pgsa_state->lock, LW_EXCLUSIVE);
     260            4 :     pgsa_reset_all_stashes();
     261            4 :     LWLockRelease(&pgsa_state->lock);
     262              : 
     263              :     /* Open the dump file. If it doesn't exist, we're done. */
     264            4 :     file = AllocateFile(PGSA_DUMP_FILE, "r");
     265            4 :     if (!file)
     266              :     {
     267            2 :         if (errno == ENOENT)
     268            2 :             return;
     269            0 :         ereport(ERROR,
     270              :                 (errcode_for_file_access(),
     271              :                  errmsg("could not open file \"%s\": %m", PGSA_DUMP_FILE)));
     272              :     }
     273              : 
     274              :     /* Use a temporary context for all parse-phase allocations. */
     275            2 :     tmpcxt = AllocSetContextCreate(CurrentMemoryContext,
     276              :                                    "pg_stash_advice load",
     277              :                                    ALLOCSET_DEFAULT_SIZES);
     278            2 :     oldcxt = MemoryContextSwitchTo(tmpcxt);
     279              : 
     280              :     /* Figure out how long the file is. */
     281            2 :     if (fstat(fileno(file), &statbuf) != 0)
     282            0 :         ereport(ERROR,
     283              :                 (errcode_for_file_access(),
     284              :                  errmsg("could not stat file \"%s\": %m", PGSA_DUMP_FILE)));
     285              : 
     286              :     /*
     287              :      * Slurp the entire file into memory all at once.
     288              :      *
     289              :      * We could avoid this by reading the file incrementally and applying
     290              :      * changes to pgsa_stash_dshash and pgsa_entry_dshash as we go. Given the
     291              :      * lockout mechanism implemented by stashes_ready, that shouldn't have any
     292              :      * user-visible behavioral consequences, but it would consume shared
     293              :      * memory to no benefit. It seems better to buffer everything in private
     294              :      * memory first, and then only apply the changes once the file has been
     295              :      * successfully parsed in its entirety.
     296              :      *
     297              :      * That also has the advantage of possibly being more future-proof: if we
     298              :      * decide to remove the stashes_ready mechanism in the future, or say
     299              :      * allow for multiple save files, fully validating the file before
     300              :      * applying any changes will become much more important.
     301              :      *
     302              :      * Of course, this approach does have one major disadvantage, which is
     303              :      * that we'll temporarily use about twice as much memory as we're
     304              :      * ultimately going to need, but that seems like it shouldn't be a problem
     305              :      * in practice. If there's so much stashed advice that parsing the disk
     306              :      * file runs us out of memory, something has gone terribly wrong. In that
     307              :      * situation, there probably also isn't enough free memory for the
     308              :      * workload that the advice is attempting to manipulate to run
     309              :      * successfully.
     310              :      */
     311            2 :     filebuf = palloc_extended(statbuf.st_size + 1, MCXT_ALLOC_HUGE);
     312            2 :     nread = fread(filebuf, 1, statbuf.st_size, file);
     313            2 :     if (ferror(file))
     314            0 :         ereport(ERROR,
     315              :                 (errcode_for_file_access(),
     316              :                  errmsg("could not read file \"%s\": %m", PGSA_DUMP_FILE)));
     317            2 :     FreeFile(file);
     318            2 :     filebuf[nread] = '\0';
     319              : 
     320              :     /* Initial memory allocations. */
     321            2 :     saved_stashes = pgsa_saved_stash_table_create(tmpcxt, 64, NULL);
     322            2 :     entries = palloc(max_entries * sizeof(pgsa_saved_entry));
     323              : 
     324              :     /*
     325              :      * For memory and CPU efficiency, we parse the file in place. The end of
     326              :      * each line gets replaced with a NUL byte, and then the end of each field
     327              :      * within a line gets the same treatment. The advice string is unescaped
     328              :      * in place, and stash names and query IDs can't contain any special
     329              :      * characters. All of the resulting pointers point right back into the
     330              :      * buffer; we only need additional memory to grow the 'entries' array and
     331              :      * the 'saved_stashes' hash table.
     332              :      */
     333           13 :     for (p = filebuf, lineno = 1; *p != '\0'; lineno++)
     334              :     {
     335           11 :         char       *cursor = p;
     336              :         char       *eol;
     337              :         char       *line_type;
     338              : 
     339              :         /* Find end of line and NUL-terminate. */
     340           11 :         eol = strchr(p, '\n');
     341           11 :         if (eol != NULL)
     342              :         {
     343           11 :             *eol = '\0';
     344           11 :             p = eol + 1;
     345           11 :             if (eol > cursor && eol[-1] == '\r')
     346            0 :                 eol[-1] = '\0';
     347              :         }
     348              :         else
     349            0 :             p += strlen(p);
     350              : 
     351              :         /* Skip empty lines. */
     352           11 :         if (*cursor == '\0')
     353            0 :             continue;
     354              : 
     355              :         /* First field is the type of line, either "stash" or "entry". */
     356           11 :         line_type = pgsa_next_tsv_field(&cursor);
     357           11 :         if (strcmp(line_type, "stash") == 0)
     358              :         {
     359              :             char       *name;
     360              :             bool        found;
     361              : 
     362              :             /* Second field should be the stash name. */
     363            5 :             name = pgsa_next_tsv_field(&cursor);
     364            5 :             if (name == NULL || *name == '\0')
     365            0 :                 ereport(ERROR,
     366              :                         (errcode(ERRCODE_DATA_CORRUPTED),
     367              :                          errmsg("syntax error in file \"%s\" line %u: expected stash name",
     368              :                                 PGSA_DUMP_FILE, lineno)));
     369              : 
     370              :             /* No further fields are expected. */
     371            5 :             if (*cursor != '\0')
     372            0 :                 ereport(ERROR,
     373              :                         (errcode(ERRCODE_DATA_CORRUPTED),
     374              :                          errmsg("syntax error in file \"%s\" line %u: expected end of line",
     375              :                                 PGSA_DUMP_FILE, lineno)));
     376              : 
     377              :             /* Duplicate check. */
     378            5 :             (void) pgsa_saved_stash_table_insert(saved_stashes, name, &found);
     379            5 :             if (found)
     380            0 :                 ereport(ERROR,
     381              :                         (errcode(ERRCODE_DATA_CORRUPTED),
     382              :                          errmsg("syntax error in file \"%s\" line %u: duplicate stash name \"%s\"",
     383              :                                 PGSA_DUMP_FILE, lineno, name)));
     384            5 :             num_stashes++;
     385              :         }
     386            6 :         else if (strcmp(line_type, "entry") == 0)
     387              :         {
     388              :             char       *stash_name;
     389              :             char       *queryid_str;
     390              :             char       *advice_str;
     391              :             char       *endptr;
     392              :             int64       queryId;
     393              : 
     394              :             /* Second field should be the stash name. */
     395            6 :             stash_name = pgsa_next_tsv_field(&cursor);
     396            6 :             if (stash_name == NULL)
     397            0 :                 ereport(ERROR,
     398              :                         (errcode(ERRCODE_DATA_CORRUPTED),
     399              :                          errmsg("syntax error in file \"%s\" line %u: expected stash name",
     400              :                                 PGSA_DUMP_FILE, lineno)));
     401              : 
     402              :             /* Third field should be the query ID. */
     403            6 :             queryid_str = pgsa_next_tsv_field(&cursor);
     404            6 :             if (queryid_str == NULL)
     405            0 :                 ereport(ERROR,
     406              :                         (errcode(ERRCODE_DATA_CORRUPTED),
     407              :                          errmsg("syntax error in file \"%s\" line %u: expected query ID",
     408              :                                 PGSA_DUMP_FILE, lineno)));
     409              : 
     410              :             /* Fourth field should be the advice string. */
     411            6 :             advice_str = pgsa_next_tsv_field(&cursor);
     412            6 :             if (advice_str == NULL)
     413            0 :                 ereport(ERROR,
     414              :                         (errcode(ERRCODE_DATA_CORRUPTED),
     415              :                          errmsg("syntax error in file \"%s\" line %u: expected advice string",
     416              :                                 PGSA_DUMP_FILE, lineno)));
     417              : 
     418              :             /* No further fields are expected. */
     419            6 :             if (*cursor != '\0')
     420            0 :                 ereport(ERROR,
     421              :                         (errcode(ERRCODE_DATA_CORRUPTED),
     422              :                          errmsg("syntax error in file \"%s\" line %u: expected end of line",
     423              :                                 PGSA_DUMP_FILE, lineno)));
     424              : 
     425              :             /* Make sure the stash is one we've actually seen. */
     426            6 :             if (pgsa_saved_stash_table_lookup(saved_stashes,
     427              :                                               stash_name) == NULL)
     428            0 :                 ereport(ERROR,
     429              :                         (errcode(ERRCODE_DATA_CORRUPTED),
     430              :                          errmsg("syntax error in file \"%s\" line %u: unknown stash \"%s\"",
     431              :                                 PGSA_DUMP_FILE, lineno, stash_name)));
     432              : 
     433              :             /* Parse the query ID. */
     434            6 :             errno = 0;
     435            6 :             queryId = strtoll(queryid_str, &endptr, 10);
     436            6 :             if (*endptr != '\0' || errno != 0 || queryid_str == endptr ||
     437              :                 queryId == 0)
     438            0 :                 ereport(ERROR,
     439              :                         (errcode(ERRCODE_DATA_CORRUPTED),
     440              :                          errmsg("syntax error in file \"%s\" line %u: invalid query ID \"%s\"",
     441              :                                 PGSA_DUMP_FILE, lineno, queryid_str)));
     442              : 
     443              :             /* Unescape the advice string. */
     444            6 :             pgsa_unescape_tsv_field(advice_str, PGSA_DUMP_FILE, lineno);
     445              : 
     446              :             /* Append to the entry array. */
     447            6 :             if (num_entries >= max_entries)
     448              :             {
     449            0 :                 max_entries *= 2;
     450            0 :                 entries = repalloc(entries,
     451              :                                    max_entries * sizeof(pgsa_saved_entry));
     452              :             }
     453            6 :             entries[num_entries].stash_name = stash_name;
     454            6 :             entries[num_entries].queryId = queryId;
     455            6 :             entries[num_entries].advice_string = advice_str;
     456            6 :             num_entries++;
     457              :         }
     458              :         else
     459              :         {
     460            0 :             ereport(ERROR,
     461              :                     (errcode(ERRCODE_DATA_CORRUPTED),
     462              :                      errmsg("syntax error in file \"%s\" line %u: unrecognized line type",
     463              :                             PGSA_DUMP_FILE, lineno)));
     464              :         }
     465              :     }
     466              : 
     467              :     /*
     468              :      * Parsing succeeded. Apply everything to shared memory.
     469              :      *
     470              :      * At this point, we know that the file we just read is fully valid, but
     471              :      * it's still possible for this to fail if, for example, DSA memory cannot
     472              :      * be allocated. If that happens, the worker will die, the postmaster will
     473              :      * eventually restart it, and we'll try again after clearing any data that
     474              :      * we did manage to put into shared memory. (Note that we call
     475              :      * pgsa_reset_all_stashes() at the top of this function.)
     476              :      */
     477            2 :     pgsa_restore_stashes(saved_stashes);
     478            2 :     pgsa_restore_entries(entries, num_entries);
     479              : 
     480              :     /* Hooray, it worked! Notify the user. */
     481            2 :     ereport(LOG,
     482              :             (errmsg("loaded %d advice stashes and %d entries from \"%s\"",
     483              :                     num_stashes, num_entries, PGSA_DUMP_FILE)));
     484              : 
     485              :     /* Clean up. */
     486            2 :     MemoryContextSwitchTo(oldcxt);
     487            2 :     MemoryContextDelete(tmpcxt);
     488              : }
     489              : 
     490              : /*
     491              :  * Write all advice stash data to disk.
     492              :  *
     493              :  * The file format is a simple TSV with a line-type prefix:
     494              :  *   stash\tstash_name
     495              :  *   entry\tstash_name\tquery_id\tadvice_string
     496              :  */
     497              : static void
     498            4 : pgsa_write_to_disk(void)
     499              : {
     500            4 :     pgsa_writer_context wctx = {0};
     501              :     MemoryContext tmpcxt;
     502              :     MemoryContext oldcxt;
     503              : 
     504              :     Assert(pgsa_entry_dshash != NULL);
     505              : 
     506              :     /* Use a temporary context so all allocations are freed at the end. */
     507            4 :     tmpcxt = AllocSetContextCreate(CurrentMemoryContext,
     508              :                                    "pg_stash_advice dump",
     509              :                                    ALLOCSET_DEFAULT_SIZES);
     510            4 :     oldcxt = MemoryContextSwitchTo(tmpcxt);
     511              : 
     512              :     /* Set up the writer context. */
     513            4 :     snprintf(wctx.pathname, MAXPGPATH, "%s.tmp", PGSA_DUMP_FILE);
     514            4 :     wctx.file = AllocateFile(wctx.pathname, "w");
     515            4 :     if (!wctx.file)
     516            0 :         ereport(ERROR,
     517              :                 (errcode_for_file_access(),
     518              :                  errmsg("could not open file \"%s\": %m", wctx.pathname)));
     519            4 :     wctx.nhash = pgsa_stash_name_table_create(tmpcxt, 64, NULL);
     520            4 :     initStringInfo(&wctx.buf);
     521              : 
     522              :     /* Write stash lines, then entry lines. */
     523            4 :     pgsa_write_stashes(&wctx);
     524            4 :     pgsa_write_entries(&wctx);
     525              : 
     526              :     /*
     527              :      * If nothing was written, remove both the temp file and any existing dump
     528              :      * file rather than installing a zero-length file.
     529              :      */
     530            4 :     if (wctx.nhash->members == 0)
     531              :     {
     532            2 :         ereport(DEBUG1,
     533              :                 errmsg("there are no advice stashes to save"));
     534            2 :         FreeFile(wctx.file);
     535            2 :         unlink(wctx.pathname);
     536            2 :         if (unlink(PGSA_DUMP_FILE) == 0)
     537            1 :             ereport(DEBUG1,
     538              :                     errmsg("removed \"%s\"", PGSA_DUMP_FILE));
     539              :     }
     540              :     else
     541              :     {
     542            2 :         if (FreeFile(wctx.file) != 0)
     543              :         {
     544            0 :             int         save_errno = errno;
     545              : 
     546            0 :             unlink(wctx.pathname);
     547            0 :             errno = save_errno;
     548            0 :             ereport(ERROR,
     549              :                     (errcode_for_file_access(),
     550              :                      errmsg("could not close file \"%s\": %m",
     551              :                             wctx.pathname)));
     552              :         }
     553            2 :         (void) durable_rename(wctx.pathname, PGSA_DUMP_FILE, ERROR);
     554              : 
     555            2 :         ereport(LOG,
     556              :                 errmsg("saved %d advice stashes and %d entries to \"%s\"",
     557              :                        (int) wctx.nhash->members, wctx.entries_written,
     558              :                        PGSA_DUMP_FILE));
     559              :     }
     560              : 
     561            4 :     MemoryContextSwitchTo(oldcxt);
     562            4 :     MemoryContextDelete(tmpcxt);
     563            4 : }
     564              : 
     565              : /*
     566              :  * Append the TSV-escaped form of str to buf.
     567              :  *
     568              :  * Backslash, tab, newline, and carriage return are escaped with backslash
     569              :  * sequences.  All other characters are passed through unchanged.
     570              :  */
     571              : static void
     572            6 : pgsa_append_tsv_escaped_string(StringInfo buf, const char *str)
     573              : {
     574          100 :     for (const char *p = str; *p != '\0'; p++)
     575              :     {
     576           94 :         switch (*p)
     577              :         {
     578            2 :             case '\\':
     579            2 :                 appendStringInfoString(buf, "\\\\");
     580            2 :                 break;
     581            2 :             case '\t':
     582            2 :                 appendStringInfoString(buf, "\\t");
     583            2 :                 break;
     584            2 :             case '\n':
     585            2 :                 appendStringInfoString(buf, "\\n");
     586            2 :                 break;
     587            0 :             case '\r':
     588            0 :                 appendStringInfoString(buf, "\\r");
     589            0 :                 break;
     590           88 :             default:
     591           88 :                 appendStringInfoChar(buf, *p);
     592           88 :                 break;
     593              :         }
     594              :     }
     595            6 : }
     596              : 
     597              : /*
     598              :  * Extract the next tab-delimited field from *cursor.
     599              :  *
     600              :  * The tab delimiter is replaced with '\0' and *cursor is advanced past it.
     601              :  * If *cursor already points to '\0' (no more fields), returns NULL.
     602              :  */
     603              : static char *
     604           34 : pgsa_next_tsv_field(char **cursor)
     605              : {
     606           34 :     char       *start = *cursor;
     607           34 :     char       *p = start;
     608              : 
     609           34 :     if (*p == '\0')
     610            0 :         return NULL;
     611              : 
     612          290 :     while (*p != '\0' && *p != '\t')
     613          256 :         p++;
     614              : 
     615           34 :     if (*p == '\t')
     616           23 :         *p++ = '\0';
     617              : 
     618           34 :     *cursor = p;
     619           34 :     return start;
     620              : }
     621              : 
     622              : /*
     623              :  * Insert entries into shared memory from the parsed entry array.
     624              :  */
     625              : static void
     626            2 : pgsa_restore_entries(pgsa_saved_entry *entries, int num_entries)
     627              : {
     628            2 :     LWLockAcquire(&pgsa_state->lock, LW_SHARED);
     629            8 :     for (int i = 0; i < num_entries; i++)
     630              :     {
     631            6 :         ereport(DEBUG2,
     632              :                 errmsg("restoring advice stash entry for \"%s\", query ID %" PRId64,
     633              :                        entries[i].stash_name, entries[i].queryId));
     634            6 :         pgsa_set_advice_string(entries[i].stash_name,
     635            6 :                                entries[i].queryId,
     636            6 :                                entries[i].advice_string);
     637              :     }
     638            2 :     LWLockRelease(&pgsa_state->lock);
     639            2 : }
     640              : 
     641              : /*
     642              :  * Create stashes in shared memory from the parsed stash hash table.
     643              :  */
     644              : static void
     645            2 : pgsa_restore_stashes(pgsa_saved_stash_table_hash *saved_stashes)
     646              : {
     647              :     pgsa_saved_stash_table_iterator iter;
     648              :     pgsa_saved_stash *s;
     649              : 
     650            2 :     LWLockAcquire(&pgsa_state->lock, LW_EXCLUSIVE);
     651            2 :     pgsa_saved_stash_table_start_iterate(saved_stashes, &iter);
     652            7 :     while ((s = pgsa_saved_stash_table_iterate(saved_stashes,
     653            7 :                                                &iter)) != NULL)
     654              :     {
     655            5 :         ereport(DEBUG2,
     656              :                 errmsg("restoring advice stash \"%s\"", s->name));
     657            5 :         pgsa_create_stash(s->name);
     658              :     }
     659            2 :     LWLockRelease(&pgsa_state->lock);
     660            2 : }
     661              : 
     662              : /*
     663              :  * Unescape a TSV field in place.
     664              :  *
     665              :  * Recognized escape sequences are \\, \t, \n, and \r.  A trailing backslash
     666              :  * or an unrecognized escape sequence is a syntax error.
     667              :  */
     668              : static void
     669            6 : pgsa_unescape_tsv_field(char *str, const char *filename, unsigned lineno)
     670              : {
     671            6 :     char       *src = str;
     672            6 :     char       *dst = str;
     673              : 
     674          100 :     while (*src != '\0')
     675              :     {
     676              :         /* Just pass through anything that's not a backslash-escape. */
     677           94 :         if (likely(*src != '\\'))
     678              :         {
     679           88 :             *dst++ = *src++;
     680           88 :             continue;
     681              :         }
     682              : 
     683              :         /* Check what sort of escape we've got. */
     684            6 :         switch (src[1])
     685              :         {
     686            2 :             case '\\':
     687            2 :                 *dst++ = '\\';
     688            2 :                 break;
     689            2 :             case 't':
     690            2 :                 *dst++ = '\t';
     691            2 :                 break;
     692            2 :             case 'n':
     693            2 :                 *dst++ = '\n';
     694            2 :                 break;
     695            0 :             case 'r':
     696            0 :                 *dst++ = '\r';
     697            0 :                 break;
     698            0 :             case '\0':
     699            0 :                 ereport(ERROR,
     700              :                         (errcode(ERRCODE_DATA_CORRUPTED),
     701              :                          errmsg("syntax error in file \"%s\" line %u: trailing backslash",
     702              :                                 filename, lineno)));
     703              :                 break;
     704            0 :             default:
     705            0 :                 ereport(ERROR,
     706              :                         (errcode(ERRCODE_DATA_CORRUPTED),
     707              :                          errmsg("syntax error in file \"%s\" line %u: unrecognized escape \"\\%c\"",
     708              :                                 filename, lineno, src[1])));
     709              :                 break;
     710              :         }
     711              : 
     712              :         /* We consumed the backslash and the following character. */
     713            6 :         src += 2;
     714              :     }
     715            6 :     *dst = '\0';
     716            6 : }
     717              : 
     718              : /*
     719              :  * Write an entry line for each advice entry.
     720              :  */
     721              : static void
     722            4 : pgsa_write_entries(pgsa_writer_context *wctx)
     723              : {
     724              :     dshash_seq_status iter;
     725              :     pgsa_entry *entry;
     726              : 
     727            4 :     dshash_seq_init(&iter, pgsa_entry_dshash, false);
     728           10 :     while ((entry = dshash_seq_next(&iter)) != NULL)
     729              :     {
     730              :         pgsa_stash_name *n;
     731              :         char       *advice_string;
     732              : 
     733            6 :         if (entry->advice_string == InvalidDsaPointer)
     734            0 :             continue;
     735              : 
     736            6 :         n = pgsa_stash_name_table_lookup(wctx->nhash,
     737              :                                          entry->key.pgsa_stash_id);
     738            6 :         if (n == NULL)
     739            0 :             continue;           /* orphan entry, skip */
     740              : 
     741            6 :         advice_string = dsa_get_address(pgsa_dsa_area, entry->advice_string);
     742              : 
     743            6 :         resetStringInfo(&wctx->buf);
     744            6 :         appendStringInfo(&wctx->buf, "entry\t%s\t%" PRId64 "\t",
     745              :                          n->name, entry->key.queryId);
     746            6 :         pgsa_append_tsv_escaped_string(&wctx->buf, advice_string);
     747            6 :         appendStringInfoChar(&wctx->buf, '\n');
     748            6 :         fwrite(wctx->buf.data, 1, wctx->buf.len, wctx->file);
     749            6 :         if (ferror(wctx->file))
     750            0 :             pgsa_write_error(wctx);
     751            6 :         wctx->entries_written++;
     752              :     }
     753            4 :     dshash_seq_term(&iter);
     754            4 : }
     755              : 
     756              : /*
     757              :  * Clean up and report a write error.  Does not return.
     758              :  */
     759              : static void
     760            0 : pgsa_write_error(pgsa_writer_context *wctx)
     761              : {
     762            0 :     int         save_errno = errno;
     763              : 
     764            0 :     FreeFile(wctx->file);
     765            0 :     unlink(wctx->pathname);
     766            0 :     errno = save_errno;
     767            0 :     ereport(ERROR,
     768              :             (errcode_for_file_access(),
     769              :              errmsg("could not write to file \"%s\": %m", wctx->pathname)));
     770              : }
     771              : 
     772              : /*
     773              :  * Write a stash line for each advice stash, and populate the ID-to-name
     774              :  * hash table for use by pgsa_write_entries.
     775              :  */
     776              : static void
     777            4 : pgsa_write_stashes(pgsa_writer_context *wctx)
     778              : {
     779              :     dshash_seq_status iter;
     780              :     pgsa_stash *stash;
     781              : 
     782            4 :     dshash_seq_init(&iter, pgsa_stash_dshash, false);
     783            9 :     while ((stash = dshash_seq_next(&iter)) != NULL)
     784              :     {
     785              :         pgsa_stash_name *n;
     786              :         bool        found;
     787              : 
     788            5 :         n = pgsa_stash_name_table_insert(wctx->nhash, stash->pgsa_stash_id,
     789              :                                          &found);
     790              :         Assert(!found);
     791            5 :         n->name = pstrdup(stash->name);
     792              : 
     793            5 :         resetStringInfo(&wctx->buf);
     794            5 :         appendStringInfo(&wctx->buf, "stash\t%s\n", n->name);
     795            5 :         fwrite(wctx->buf.data, 1, wctx->buf.len, wctx->file);
     796            5 :         if (ferror(wctx->file))
     797            0 :             pgsa_write_error(wctx);
     798              :     }
     799            4 :     dshash_seq_term(&iter);
     800            4 : }
        

Generated by: LCOV version 2.0-1