PostgreSQL Source Code git master
Loading...
Searching...
No Matches
stashpersist.c
Go to the documentation of this file.
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"
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"
27#include "utils/guc.h"
28#include "utils/memutils.h"
29#include "utils/timestamp.h"
30
39
40/*
41 * A parsed entry line, with pointers into the slurp buffer.
42 */
49
50/*
51 * simplehash for detecting duplicate stash names during parsing.
52 * Keyed by stash name (char *), pointing into the slurp buffer.
53 */
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
72static void pgsa_append_tsv_escaped_string(StringInfo buf, const char *str);
73static void pgsa_detach_shmem(int code, Datum arg);
74static char *pgsa_next_tsv_field(char **cursor);
75static void pgsa_read_from_disk(void);
76static void pgsa_restore_entries(pgsa_saved_entry *entries, int num_entries);
78static void pgsa_unescape_tsv_field(char *str, const char *filename,
79 unsigned lineno);
83static 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 */
93PGDLLEXPORT void
95{
98
99 /* Establish signal handlers; once that's done, unblock signals. */
104
105 /* Log a debug message */
107 errmsg("pg_stash_advice worker started"));
108
109 /* Set up session user so pgstat can report it. */
111
112 /* Report this worker in pg_stat_activity. */
116
117 /* Attach to shared memory structures. */
118 pgsa_attach();
119
120 /* Set on-detach hook so that our PID will be cleared on exit. */
122
123 /*
124 * Store our PID in shared memory, unless there's already another worker
125 * running, in which case just exit.
126 */
129 {
131 ereport(LOG,
132 (errmsg("pg_stash_advice worker is already running under PID %d",
133 (int) pgsa_state->bgworker_pid)));
134 return;
135 }
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 */
145 {
148 }
149
150 /* Note the current change count so we can detect future changes. */
152
153 /* Periodically write to disk until terminated. */
155 {
156 /* In case of a SIGHUP, just reload the configuration. */
158 {
159 ConfigReloadPending = false;
161 }
162
164 {
165 /* Only writing at shutdown, so just wait forever. */
168 -1L,
170 }
171 else
172 {
174 long delay_in_ms;
176
177 /* Compute when the next write should happen. */
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 if (delay_in_ms <= 0)
191 {
195 {
198 }
200 continue;
201 }
202
203 /* Sleep until the next write time. */
208 }
209
211 }
212
213 /* Write one last time before exiting. */
215}
216
217/*
218 * Clear our PID from shared memory on exit.
219 */
220static void
228
229/*
230 * Load advice stash data from a dump file on disk, if there is one.
231 */
232static void
234{
235 struct stat statbuf;
236 FILE *file;
237 char *filebuf;
238 size_t nread;
239 char *p;
240 unsigned lineno;
242 int num_stashes = 0;
243 pgsa_saved_entry *entries;
244 int num_entries = 0;
245 int max_entries = 64;
248
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 */
262
263 /* Open the dump file. If it doesn't exist, we're done. */
264 file = AllocateFile(PGSA_DUMP_FILE, "r");
265 if (!file)
266 {
267 if (errno == ENOENT)
268 return;
271 errmsg("could not open file \"%s\": %m", PGSA_DUMP_FILE)));
272 }
273
274 /* Use a temporary context for all parse-phase allocations. */
276 "pg_stash_advice load",
279
280 /* Figure out how long the file is. */
281 if (fstat(fileno(file), &statbuf) != 0)
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 */
312 nread = fread(filebuf, 1, statbuf.st_size, file);
313 if (ferror(file))
316 errmsg("could not read file \"%s\": %m", PGSA_DUMP_FILE)));
317 FreeFile(file);
318 filebuf[nread] = '\0';
319
320 /* Initial memory allocations. */
322 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 for (p = filebuf, lineno = 1; *p != '\0'; lineno++)
334 {
335 char *cursor = p;
336 char *eol;
337 char *line_type;
338
339 /* Find end of line and NUL-terminate. */
340 eol = strchr(p, '\n');
341 if (eol != NULL)
342 {
343 *eol = '\0';
344 p = eol + 1;
345 if (eol > cursor && eol[-1] == '\r')
346 eol[-1] = '\0';
347 }
348 else
349 p += strlen(p);
350
351 /* Skip empty lines. */
352 if (*cursor == '\0')
353 continue;
354
355 /* First field is the type of line, either "stash" or "entry". */
357 if (strcmp(line_type, "stash") == 0)
358 {
359 char *name;
360 bool found;
361
362 /* Second field should be the stash name. */
364 if (name == NULL || *name == '\0')
367 errmsg("syntax error in file \"%s\" line %u: expected stash name",
368 PGSA_DUMP_FILE, lineno)));
369
370 /* No further fields are expected. */
371 if (*cursor != '\0')
374 errmsg("syntax error in file \"%s\" line %u: expected end of line",
375 PGSA_DUMP_FILE, lineno)));
376
377 /* Reject overlong stash names. */
378 if (strlen(name) >= NAMEDATALEN)
381 errmsg("syntax error in file \"%s\" line %u: stash name too long",
382 PGSA_DUMP_FILE, lineno)));
383
384 /* Duplicate check. */
386 if (found)
389 errmsg("syntax error in file \"%s\" line %u: duplicate stash name \"%s\"",
390 PGSA_DUMP_FILE, lineno, name)));
391 num_stashes++;
392 }
393 else if (strcmp(line_type, "entry") == 0)
394 {
395 char *stash_name;
396 char *queryid_str;
397 char *advice_str;
398 char *endptr;
399 int64 queryId;
400
401 /* Second field should be the stash name. */
402 stash_name = pgsa_next_tsv_field(&cursor);
403 if (stash_name == NULL)
406 errmsg("syntax error in file \"%s\" line %u: expected stash name",
407 PGSA_DUMP_FILE, lineno)));
408
409 /* Third field should be the query ID. */
411 if (queryid_str == NULL)
414 errmsg("syntax error in file \"%s\" line %u: expected query ID",
415 PGSA_DUMP_FILE, lineno)));
416
417 /* Fourth field should be the advice string. */
419 if (advice_str == NULL)
422 errmsg("syntax error in file \"%s\" line %u: expected advice string",
423 PGSA_DUMP_FILE, lineno)));
424
425 /* No further fields are expected. */
426 if (*cursor != '\0')
429 errmsg("syntax error in file \"%s\" line %u: expected end of line",
430 PGSA_DUMP_FILE, lineno)));
431
432 /* Make sure the stash is one we've actually seen. */
434 stash_name) == NULL)
437 errmsg("syntax error in file \"%s\" line %u: unknown stash \"%s\"",
438 PGSA_DUMP_FILE, lineno, stash_name)));
439
440 /* Parse the query ID. */
441 errno = 0;
442 queryId = strtoi64(queryid_str, &endptr, 10);
443 if (*endptr != '\0' || errno != 0 || queryid_str == endptr ||
444 queryId == 0)
447 errmsg("syntax error in file \"%s\" line %u: invalid query ID \"%s\"",
448 PGSA_DUMP_FILE, lineno, queryid_str)));
449
450 /* Unescape the advice string. */
452
453 /* Append to the entry array. */
454 if (num_entries >= max_entries)
455 {
456 max_entries *= 2;
457 entries = repalloc(entries,
458 max_entries * sizeof(pgsa_saved_entry));
459 }
460 entries[num_entries].stash_name = stash_name;
461 entries[num_entries].queryId = queryId;
462 entries[num_entries].advice_string = advice_str;
463 num_entries++;
464 }
465 else
466 {
469 errmsg("syntax error in file \"%s\" line %u: unrecognized line type",
470 PGSA_DUMP_FILE, lineno)));
471 }
472 }
473
474 /*
475 * Parsing succeeded. Apply everything to shared memory.
476 *
477 * At this point, we know that the file we just read is fully valid, but
478 * it's still possible for this to fail if, for example, DSA memory cannot
479 * be allocated. If that happens, the worker will die, the postmaster will
480 * eventually restart it, and we'll try again after clearing any data that
481 * we did manage to put into shared memory. (Note that we call
482 * pgsa_reset_all_stashes() at the top of this function.)
483 */
485 pgsa_restore_entries(entries, num_entries);
486
487 /* Hooray, it worked! Notify the user. */
488 ereport(LOG,
489 (errmsg("loaded %d advice stashes and %d entries from \"%s\"",
490 num_stashes, num_entries, PGSA_DUMP_FILE)));
491
492 /* Clean up. */
495}
496
497/*
498 * Write all advice stash data to disk.
499 *
500 * The file format is a simple TSV with a line-type prefix:
501 * stash\tstash_name
502 * entry\tstash_name\tquery_id\tadvice_string
503 */
504static void
506{
510
512
513 /* Use a temporary context so all allocations are freed at the end. */
515 "pg_stash_advice dump",
518
519 /* Set up the writer context. */
520 snprintf(wctx.pathname, MAXPGPATH, "%s.tmp", PGSA_DUMP_FILE);
521 wctx.file = AllocateFile(wctx.pathname, "w");
522 if (!wctx.file)
525 errmsg("could not open file \"%s\": %m", wctx.pathname)));
527 initStringInfo(&wctx.buf);
528
529 /* Write stash lines, then entry lines. */
532
533 /*
534 * If nothing was written, remove both the temp file and any existing dump
535 * file rather than installing a zero-length file.
536 */
537 if (wctx.nhash->members == 0)
538 {
540 errmsg("there are no advice stashes to save"));
541 FreeFile(wctx.file);
542 unlink(wctx.pathname);
543 if (unlink(PGSA_DUMP_FILE) == 0)
545 errmsg("removed \"%s\"", PGSA_DUMP_FILE));
546 }
547 else
548 {
549 if (FreeFile(wctx.file) != 0)
550 {
551 int save_errno = errno;
552
553 unlink(wctx.pathname);
557 errmsg("could not close file \"%s\": %m",
558 wctx.pathname)));
559 }
561
562 ereport(LOG,
563 errmsg("saved %d advice stashes and %d entries to \"%s\"",
564 (int) wctx.nhash->members, wctx.entries_written,
566 }
567
570}
571
572/*
573 * Append the TSV-escaped form of str to buf.
574 *
575 * Backslash, tab, newline, and carriage return are escaped with backslash
576 * sequences. All other characters are passed through unchanged.
577 */
578static void
580{
581 for (const char *p = str; *p != '\0'; p++)
582 {
583 switch (*p)
584 {
585 case '\\':
587 break;
588 case '\t':
590 break;
591 case '\n':
593 break;
594 case '\r':
596 break;
597 default:
599 break;
600 }
601 }
602}
603
604/*
605 * Extract the next tab-delimited field from *cursor.
606 *
607 * The tab delimiter is replaced with '\0' and *cursor is advanced past it.
608 * If *cursor already points to '\0' (no more fields), returns NULL.
609 */
610static char *
612{
613 char *start = *cursor;
614 char *p = start;
615
616 if (*p == '\0')
617 return NULL;
618
619 while (*p != '\0' && *p != '\t')
620 p++;
621
622 if (*p == '\t')
623 *p++ = '\0';
624
625 *cursor = p;
626 return start;
627}
628
629/*
630 * Insert entries into shared memory from the parsed entry array.
631 */
632static void
633pgsa_restore_entries(pgsa_saved_entry *entries, int num_entries)
634{
636 for (int i = 0; i < num_entries; i++)
637 {
639 errmsg("restoring advice stash entry for \"%s\", query ID %" PRId64,
640 entries[i].stash_name, entries[i].queryId));
641 pgsa_set_advice_string(entries[i].stash_name,
642 entries[i].queryId,
643 entries[i].advice_string);
644 }
646}
647
648/*
649 * Create stashes in shared memory from the parsed stash hash table.
650 */
651static void
668
669/*
670 * Unescape a TSV field in place.
671 *
672 * Recognized escape sequences are \\, \t, \n, and \r. A trailing backslash
673 * or an unrecognized escape sequence is a syntax error.
674 */
675static void
676pgsa_unescape_tsv_field(char *str, const char *filename, unsigned lineno)
677{
678 char *src = str;
679 char *dst = str;
680
681 while (*src != '\0')
682 {
683 /* Just pass through anything that's not a backslash-escape. */
684 if (likely(*src != '\\'))
685 {
686 *dst++ = *src++;
687 continue;
688 }
689
690 /* Check what sort of escape we've got. */
691 switch (src[1])
692 {
693 case '\\':
694 *dst++ = '\\';
695 break;
696 case 't':
697 *dst++ = '\t';
698 break;
699 case 'n':
700 *dst++ = '\n';
701 break;
702 case 'r':
703 *dst++ = '\r';
704 break;
705 case '\0':
708 errmsg("syntax error in file \"%s\" line %u: trailing backslash",
709 filename, lineno)));
710 break;
711 default:
714 errmsg("syntax error in file \"%s\" line %u: unrecognized escape \"\\%c\"",
715 filename, lineno, src[1])));
716 break;
717 }
718
719 /* We consumed the backslash and the following character. */
720 src += 2;
721 }
722 *dst = '\0';
723}
724
725/*
726 * Write an entry line for each advice entry.
727 */
728static void
730{
732 pgsa_entry *entry;
733
734 dshash_seq_init(&iter, pgsa_entry_dshash, false);
735 while ((entry = dshash_seq_next(&iter)) != NULL)
736 {
738 char *advice_string;
739
740 if (entry->advice_string == InvalidDsaPointer)
741 continue;
742
744 entry->key.pgsa_stash_id);
745 if (n == NULL)
746 continue; /* orphan entry, skip */
747
748 advice_string = dsa_get_address(pgsa_dsa_area, entry->advice_string);
749
750 resetStringInfo(&wctx->buf);
751 appendStringInfo(&wctx->buf, "entry\t%s\t%" PRId64 "\t",
752 n->name, entry->key.queryId);
753 pgsa_append_tsv_escaped_string(&wctx->buf, advice_string);
754 appendStringInfoChar(&wctx->buf, '\n');
755 fwrite(wctx->buf.data, 1, wctx->buf.len, wctx->file);
756 if (ferror(wctx->file))
758 wctx->entries_written++;
759 }
760 dshash_seq_term(&iter);
761}
762
763/*
764 * Clean up and report a write error. Does not return.
765 */
766static void
768{
769 int save_errno = errno;
770
771 FreeFile(wctx->file);
772 unlink(wctx->pathname);
776 errmsg("could not write to file \"%s\": %m", wctx->pathname)));
777}
778
779/*
780 * Write a stash line for each advice stash, and populate the ID-to-name
781 * hash table for use by pgsa_write_entries.
782 */
783static void
785{
788
789 dshash_seq_init(&iter, pgsa_stash_dshash, false);
790 while ((stash = dshash_seq_next(&iter)) != NULL)
791 {
793 bool found;
794
795 n = pgsa_stash_name_table_insert(wctx->nhash, stash->pgsa_stash_id,
796 &found);
797 Assert(!found);
798 n->name = pstrdup(stash->name);
799
800 resetStringInfo(&wctx->buf);
801 appendStringInfo(&wctx->buf, "stash\t%s\n", n->name);
802 fwrite(wctx->buf.data, 1, wctx->buf.len, wctx->file);
803 if (ferror(wctx->file))
805 }
806 dshash_seq_term(&iter);
807}
static bool pg_atomic_test_set_flag(volatile pg_atomic_flag *ptr)
Definition atomics.h:181
static bool pg_atomic_unlocked_test_flag(volatile pg_atomic_flag *ptr)
Definition atomics.h:194
static uint64 pg_atomic_read_u64(volatile pg_atomic_uint64 *ptr)
Definition atomics.h:467
long TimestampDifferenceMilliseconds(TimestampTz start_time, TimestampTz stop_time)
Definition timestamp.c:1765
TimestampTz GetCurrentTimestamp(void)
Definition timestamp.c:1649
void pgstat_bestart_initial(void)
void pgstat_beinit(void)
void pgstat_bestart_final(void)
void BackgroundWorkerUnblockSignals(void)
Definition bgworker.c:949
#define likely(x)
Definition c.h:437
#define pg_noreturn
Definition c.h:190
#define Assert(condition)
Definition c.h:943
int64_t int64
Definition c.h:621
#define PGDLLEXPORT
Definition c.h:1448
uint64_t uint64
Definition c.h:625
uint32_t uint32
Definition c.h:624
int64 TimestampTz
Definition timestamp.h:39
void * dsa_get_address(dsa_area *area, dsa_pointer dp)
Definition dsa.c:957
#define InvalidDsaPointer
Definition dsa.h:78
void dshash_seq_init(dshash_seq_status *status, dshash_table *hash_table, bool exclusive)
Definition dshash.c:659
void dshash_seq_term(dshash_seq_status *status)
Definition dshash.c:768
void * dshash_seq_next(dshash_seq_status *status)
Definition dshash.c:678
Datum arg
Definition elog.c:1323
int errcode_for_file_access(void)
Definition elog.c:898
int errcode(int sqlerrcode)
Definition elog.c:875
#define LOG
Definition elog.h:32
#define DEBUG2
Definition elog.h:30
#define DEBUG1
Definition elog.h:31
#define ERROR
Definition elog.h:40
#define ereport(elevel,...)
Definition elog.h:152
int durable_rename(const char *oldfile, const char *newfile, int elevel)
Definition fd.c:783
int FreeFile(FILE *file)
Definition fd.c:2827
FILE * AllocateFile(const char *name, const char *mode)
Definition fd.c:2628
#define MCXT_ALLOC_HUGE
Definition fe_memutils.h:28
int MyProcPid
Definition globals.c:49
struct Latch * MyLatch
Definition globals.c:65
void ProcessConfigFile(GucContext context)
Definition guc-file.l:120
@ PGC_SIGHUP
Definition guc.h:75
return str start
const char * str
void SignalHandlerForShutdownRequest(SIGNAL_ARGS)
Definition interrupt.c:104
volatile sig_atomic_t ShutdownRequestPending
Definition interrupt.c:28
volatile sig_atomic_t ConfigReloadPending
Definition interrupt.c:27
void SignalHandlerForConfigReload(SIGNAL_ARGS)
Definition interrupt.c:61
void before_shmem_exit(pg_on_exit_callback function, Datum arg)
Definition ipc.c:344
int i
Definition isn.c:77
void ResetLatch(Latch *latch)
Definition latch.c:374
int WaitLatch(Latch *latch, int wakeEvents, long timeout, uint32 wait_event_info)
Definition latch.c:172
bool LWLockAcquire(LWLock *lock, LWLockMode mode)
Definition lwlock.c:1150
void LWLockRelease(LWLock *lock)
Definition lwlock.c:1767
@ LW_SHARED
Definition lwlock.h:105
@ LW_EXCLUSIVE
Definition lwlock.h:104
char * pstrdup(const char *in)
Definition mcxt.c:1910
void * repalloc(void *pointer, Size size)
Definition mcxt.c:1635
void * palloc(Size size)
Definition mcxt.c:1390
MemoryContext CurrentMemoryContext
Definition mcxt.c:161
void * palloc_extended(Size size, int flags)
Definition mcxt.c:1442
void MemoryContextDelete(MemoryContext context)
Definition mcxt.c:475
#define AllocSetContextCreate
Definition memutils.h:129
#define ALLOCSET_DEFAULT_SIZES
Definition memutils.h:160
#define InvalidPid
Definition miscadmin.h:32
void InitializeSessionUserIdStandalone(void)
Definition miscinit.c:841
static char * errmsg
static MemoryContext MemoryContextSwitchTo(MemoryContext context)
Definition palloc.h:138
#define ERRCODE_DATA_CORRUPTED
#define NAMEDATALEN
#define MAXPGPATH
static char * filename
Definition pg_dumpall.c:133
dshash_table * pgsa_entry_dshash
dshash_table * pgsa_stash_dshash
void pgsa_create_stash(char *stash_name)
void pgsa_set_advice_string(char *stash_name, int64 queryId, char *advice_string)
void pgsa_reset_all_stashes(void)
dsa_area * pgsa_dsa_area
void pgsa_attach(void)
pgsa_shared_state * pgsa_state
int pg_stash_advice_persist_interval
#define PGSA_DUMP_FILE
static char buf[DEFAULT_XLOG_SEG_SIZE]
#define pqsignal
Definition port.h:548
#define snprintf
Definition port.h:261
uint64_t Datum
Definition postgres.h:70
static int fb(int x)
void procsignal_sigusr1_handler(SIGNAL_ARGS)
Definition procsignal.c:696
static void pgsa_detach_shmem(int code, Datum arg)
static pg_noreturn void pgsa_write_error(pgsa_writer_context *wctx)
static char * pgsa_next_tsv_field(char **cursor)
static void pgsa_append_tsv_escaped_string(StringInfo buf, const char *str)
static void pgsa_restore_entries(pgsa_saved_entry *entries, int num_entries)
static void pgsa_unescape_tsv_field(char *str, const char *filename, unsigned lineno)
static void pgsa_read_from_disk(void)
PGDLLEXPORT void pg_stash_advice_worker_main(Datum main_arg)
static void pgsa_restore_stashes(pgsa_saved_stash_table_hash *saved_stashes)
static void pgsa_write_to_disk(void)
static void pgsa_write_entries(pgsa_writer_context *wctx)
static void pgsa_write_stashes(pgsa_writer_context *wctx)
void resetStringInfo(StringInfo str)
Definition stringinfo.c:126
void appendStringInfo(StringInfo str, const char *fmt,...)
Definition stringinfo.c:145
void appendStringInfoString(StringInfo str, const char *s)
Definition stringinfo.c:230
void appendStringInfoChar(StringInfo str, char ch)
Definition stringinfo.c:242
void initStringInfo(StringInfo str)
Definition stringinfo.c:97
Definition type.h:139
int64 queryId
uint64 pgsa_stash_id
pgsa_entry_key key
dsa_pointer advice_string
char * stash_name
char * advice_string
int64 queryId
pg_atomic_uint64 change_count
pg_atomic_flag stashes_ready
char pathname[MAXPGPATH]
StringInfoData buf
pgsa_stash_name_table_hash * nhash
#define TimestampTzPlusMilliseconds(tz, ms)
Definition timestamp.h:85
#define PG_WAIT_EXTENSION
const char * name
#define WL_TIMEOUT
#define WL_EXIT_ON_PM_DEATH
#define WL_LATCH_SET
#define SIGHUP
Definition win32_port.h:158
#define SIGUSR1
Definition win32_port.h:170
#define fstat
Definition win32_port.h:73