LCOV - code coverage report
Current view: top level - contrib/pg_stash_advice - stashpersist.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 77.0 % 269 207
Test Date: 2026-11-06 16:15:55 Functions: 91.7 % 12 11
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 46.1 % 167 77

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

Generated by: LCOV version 2.0-1