LCOV - differential code coverage report
Current view: top level - contrib/pg_stash_advice - stashpersist.c (source / functions) Coverage Total Hit UNC GNC
Current: bed3ffbf9d952be6c7d739d068cdce44c046dfb7 vs 574581b50ac9c63dd9e4abebb731a3b67e5b50f6 Lines: 77.8 % 270 210 60 210
Current Date: 2026-05-05 10:23:31 +0900 Functions: 91.7 % 12 11 1 11
Baseline: lcov-20260505-025707-baseline Branches: 48.0 % 173 83 90 83
Baseline Date: 2026-05-05 10:27:06 +0900 Line coverage date bins:
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
(7,30] days: 77.8 % 270 210 60 210
Function coverage date bins:
(7,30] days: 91.7 % 12 11 1 11
Branch coverage date bins:
(7,30] days: 48.0 % 173 83 90 83

 Age         Owner                    Branch data    TLA  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
   28 rhaas@postgresql.org       94                 :GNC           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                 :                :     {
   28 rhaas@postgresql.org      130                 :UNC           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                 :                :     }
   28 rhaas@postgresql.org      136                 :GNC           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                 :                :         {
   28 rhaas@postgresql.org      159                 :UNC           0 :             ConfigReloadPending = false;
                                160                 :              0 :             ProcessConfigFile(PGC_SIGHUP);
                                161                 :                :         }
                                162                 :                : 
   28 rhaas@postgresql.org      163         [ +  - ]:GNC           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. */
   28 rhaas@postgresql.org      178                 :UNC           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                 :                : 
   28 rhaas@postgresql.org      210                 :GNC           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         [ -  + ]:              4 :     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;
   28 rhaas@postgresql.org      269         [ #  # ]:UNC           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. */
   28 rhaas@postgresql.org      275                 :GNC           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)
   28 rhaas@postgresql.org      282         [ #  # ]:UNC           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                 :                :      */
   28 rhaas@postgresql.org      311                 :GNC           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))
   28 rhaas@postgresql.org      314         [ #  # ]:UNC           0 :         ereport(ERROR,
                                315                 :                :                 (errcode_for_file_access(),
                                316                 :                :                  errmsg("could not read file \"%s\": %m", PGSA_DUMP_FILE)));
   28 rhaas@postgresql.org      317                 :GNC           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')
   28 rhaas@postgresql.org      346                 :UNC           0 :                 eol[-1] = '\0';
                                347                 :                :         }
                                348                 :                :         else
                                349                 :              0 :             p += strlen(p);
                                350                 :                : 
                                351                 :                :         /* Skip empty lines. */
   28 rhaas@postgresql.org      352         [ -  + ]:GNC          11 :         if (*cursor == '\0')
   28 rhaas@postgresql.org      353                 :UNC           0 :             continue;
                                354                 :                : 
                                355                 :                :         /* First field is the type of line, either "stash" or "entry". */
   28 rhaas@postgresql.org      356                 :GNC          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')
   28 rhaas@postgresql.org      365         [ #  # ]:UNC           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. */
   28 rhaas@postgresql.org      371         [ -  + ]:GNC           5 :             if (*cursor != '\0')
   28 rhaas@postgresql.org      372         [ #  # ]:UNC           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. */
   28 rhaas@postgresql.org      378                 :GNC           5 :             (void) pgsa_saved_stash_table_insert(saved_stashes, name, &found);
                                379         [ -  + ]:              5 :             if (found)
   28 rhaas@postgresql.org      380         [ #  # ]:UNC           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)));
   28 rhaas@postgresql.org      384                 :GNC           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)
   28 rhaas@postgresql.org      397         [ #  # ]:UNC           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. */
   28 rhaas@postgresql.org      403                 :GNC           6 :             queryid_str = pgsa_next_tsv_field(&cursor);
                                404         [ -  + ]:              6 :             if (queryid_str == NULL)
   28 rhaas@postgresql.org      405         [ #  # ]:UNC           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. */
   28 rhaas@postgresql.org      411                 :GNC           6 :             advice_str = pgsa_next_tsv_field(&cursor);
                                412         [ -  + ]:              6 :             if (advice_str == NULL)
   28 rhaas@postgresql.org      413         [ #  # ]:UNC           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. */
   28 rhaas@postgresql.org      419         [ -  + ]:GNC           6 :             if (*cursor != '\0')
   28 rhaas@postgresql.org      420         [ #  # ]:UNC           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. */
   28 rhaas@postgresql.org      426         [ -  + ]:GNC           6 :             if (pgsa_saved_stash_table_lookup(saved_stashes,
                                427                 :                :                                               stash_name) == NULL)
   28 rhaas@postgresql.org      428         [ #  # ]:UNC           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. */
   28 rhaas@postgresql.org      434                 :GNC           6 :             errno = 0;
                                435                 :              6 :             queryId = strtoll(queryid_str, &endptr, 10);
                                436   [ +  -  +  -  :              6 :             if (*endptr != '\0' || errno != 0 || queryid_str == endptr ||
                                        +  -  -  + ]
                                437                 :                :                 queryId == 0)
   28 rhaas@postgresql.org      438         [ #  # ]:UNC           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. */
   28 rhaas@postgresql.org      444                 :GNC           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                 :                :             {
   28 rhaas@postgresql.org      449                 :UNC           0 :                 max_entries *= 2;
                                450                 :              0 :                 entries = repalloc(entries,
                                451                 :                :                                    max_entries * sizeof(pgsa_saved_entry));
                                452                 :                :             }
   28 rhaas@postgresql.org      453                 :GNC           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                 :                :         {
   28 rhaas@postgresql.org      460         [ #  # ]:UNC           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                 :                :      */
   28 rhaas@postgresql.org      477                 :GNC           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         [ -  + ]:              4 :     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)
   28 rhaas@postgresql.org      516         [ #  # ]:UNC           0 :         ereport(ERROR,
                                517                 :                :                 (errcode_for_file_access(),
                                518                 :                :                  errmsg("could not open file \"%s\": %m", wctx.pathname)));
   28 rhaas@postgresql.org      519                 :GNC           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                 :                :         {
   28 rhaas@postgresql.org      544                 :UNC           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                 :                :         }
   28 rhaas@postgresql.org      553                 :GNC           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;
   28 rhaas@postgresql.org      587                 :UNC           0 :             case '\r':
                                588                 :              0 :                 appendStringInfoString(buf, "\\r");
                                589                 :              0 :                 break;
   28 rhaas@postgresql.org      590                 :GNC          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')
   28 rhaas@postgresql.org      610                 :UNC           0 :         return NULL;
                                611                 :                : 
   28 rhaas@postgresql.org      612   [ +  +  +  + ]:GNC         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;
   28 rhaas@postgresql.org      695                 :UNC           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. */
   28 rhaas@postgresql.org      713                 :GNC           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)
   28 rhaas@postgresql.org      734                 :UNC           0 :             continue;
                                735                 :                : 
   28 rhaas@postgresql.org      736                 :GNC           6 :         n = pgsa_stash_name_table_lookup(wctx->nhash,
                                737                 :                :                                          entry->key.pgsa_stash_id);
                                738         [ -  + ]:              6 :         if (n == NULL)
   28 rhaas@postgresql.org      739                 :UNC           0 :             continue;           /* orphan entry, skip */
                                740                 :                : 
   28 rhaas@postgresql.org      741                 :GNC           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))
   28 rhaas@postgresql.org      750                 :UNC           0 :             pgsa_write_error(wctx);
   28 rhaas@postgresql.org      751                 :GNC           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
   28 rhaas@postgresql.org      760                 :UNC           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
   28 rhaas@postgresql.org      777                 :GNC           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         [ -  + ]:              5 :         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))
   28 rhaas@postgresql.org      797                 :UNC           0 :             pgsa_write_error(wctx);
                                798                 :                :     }
   28 rhaas@postgresql.org      799                 :GNC           4 :     dshash_seq_term(&iter);
                                800                 :              4 : }
        

Generated by: LCOV version 2.5.0-beta