LCOV - code coverage report
Current view: top level - contrib/pg_prewarm - autoprewarm.c (source / functions) Hit Total Coverage
Test: PostgreSQL 17devel Lines: 230 300 76.7 %
Date: 2024-04-24 23:13:15 Functions: 14 15 93.3 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * autoprewarm.c
       4             :  *      Periodically dump information about the blocks present in
       5             :  *      shared_buffers, and reload them on server restart.
       6             :  *
       7             :  *      Due to locking considerations, we can't actually begin prewarming
       8             :  *      until the server reaches a consistent state.  We need the catalogs
       9             :  *      to be consistent so that we can figure out which relation to lock,
      10             :  *      and we need to lock the relations so that we don't try to prewarm
      11             :  *      pages from a relation that is in the process of being dropped.
      12             :  *
      13             :  *      While prewarming, autoprewarm will use two workers.  There's a
      14             :  *      leader worker that reads and sorts the list of blocks to be
      15             :  *      prewarmed and then launches a per-database worker for each
      16             :  *      relevant database in turn.  The former keeps running after the
      17             :  *      initial prewarm is complete to update the dump file periodically.
      18             :  *
      19             :  *  Copyright (c) 2016-2024, PostgreSQL Global Development Group
      20             :  *
      21             :  *  IDENTIFICATION
      22             :  *      contrib/pg_prewarm/autoprewarm.c
      23             :  *
      24             :  *-------------------------------------------------------------------------
      25             :  */
      26             : 
      27             : #include "postgres.h"
      28             : 
      29             : #include <unistd.h>
      30             : 
      31             : #include "access/relation.h"
      32             : #include "access/xact.h"
      33             : #include "catalog/pg_class.h"
      34             : #include "catalog/pg_type.h"
      35             : #include "pgstat.h"
      36             : #include "postmaster/bgworker.h"
      37             : #include "postmaster/interrupt.h"
      38             : #include "storage/buf_internals.h"
      39             : #include "storage/dsm.h"
      40             : #include "storage/dsm_registry.h"
      41             : #include "storage/fd.h"
      42             : #include "storage/ipc.h"
      43             : #include "storage/latch.h"
      44             : #include "storage/lwlock.h"
      45             : #include "storage/proc.h"
      46             : #include "storage/procsignal.h"
      47             : #include "storage/shmem.h"
      48             : #include "storage/smgr.h"
      49             : #include "tcop/tcopprot.h"
      50             : #include "utils/acl.h"
      51             : #include "utils/datetime.h"
      52             : #include "utils/guc.h"
      53             : #include "utils/memutils.h"
      54             : #include "utils/rel.h"
      55             : #include "utils/relfilenumbermap.h"
      56             : #include "utils/resowner.h"
      57             : 
      58             : #define AUTOPREWARM_FILE "autoprewarm.blocks"
      59             : 
      60             : /* Metadata for each block we dump. */
      61             : typedef struct BlockInfoRecord
      62             : {
      63             :     Oid         database;
      64             :     Oid         tablespace;
      65             :     RelFileNumber filenumber;
      66             :     ForkNumber  forknum;
      67             :     BlockNumber blocknum;
      68             : } BlockInfoRecord;
      69             : 
      70             : /* Shared state information for autoprewarm bgworker. */
      71             : typedef struct AutoPrewarmSharedState
      72             : {
      73             :     LWLock      lock;           /* mutual exclusion */
      74             :     pid_t       bgworker_pid;   /* for main bgworker */
      75             :     pid_t       pid_using_dumpfile; /* for autoprewarm or block dump */
      76             : 
      77             :     /* Following items are for communication with per-database worker */
      78             :     dsm_handle  block_info_handle;
      79             :     Oid         database;
      80             :     int         prewarm_start_idx;
      81             :     int         prewarm_stop_idx;
      82             :     int         prewarmed_blocks;
      83             : } AutoPrewarmSharedState;
      84             : 
      85             : PGDLLEXPORT void autoprewarm_main(Datum main_arg);
      86             : PGDLLEXPORT void autoprewarm_database_main(Datum main_arg);
      87             : 
      88           4 : PG_FUNCTION_INFO_V1(autoprewarm_start_worker);
      89           6 : PG_FUNCTION_INFO_V1(autoprewarm_dump_now);
      90             : 
      91             : static void apw_load_buffers(void);
      92             : static int  apw_dump_now(bool is_bgworker, bool dump_unlogged);
      93             : static void apw_start_leader_worker(void);
      94             : static void apw_start_database_worker(void);
      95             : static bool apw_init_shmem(void);
      96             : static void apw_detach_shmem(int code, Datum arg);
      97             : static int  apw_compare_blockinfo(const void *p, const void *q);
      98             : 
      99             : /* Pointer to shared-memory state. */
     100             : static AutoPrewarmSharedState *apw_state = NULL;
     101             : 
     102             : /* GUC variables. */
     103             : static bool autoprewarm = true; /* start worker? */
     104             : static int  autoprewarm_interval = 300; /* dump interval */
     105             : 
     106             : /*
     107             :  * Module load callback.
     108             :  */
     109             : void
     110          10 : _PG_init(void)
     111             : {
     112          10 :     DefineCustomIntVariable("pg_prewarm.autoprewarm_interval",
     113             :                             "Sets the interval between dumps of shared buffers",
     114             :                             "If set to zero, time-based dumping is disabled.",
     115             :                             &autoprewarm_interval,
     116             :                             300,
     117             :                             0, INT_MAX / 1000,
     118             :                             PGC_SIGHUP,
     119             :                             GUC_UNIT_S,
     120             :                             NULL,
     121             :                             NULL,
     122             :                             NULL);
     123             : 
     124          10 :     if (!process_shared_preload_libraries_in_progress)
     125           6 :         return;
     126             : 
     127             :     /* can't define PGC_POSTMASTER variable after startup */
     128           4 :     DefineCustomBoolVariable("pg_prewarm.autoprewarm",
     129             :                              "Starts the autoprewarm worker.",
     130             :                              NULL,
     131             :                              &autoprewarm,
     132             :                              true,
     133             :                              PGC_POSTMASTER,
     134             :                              0,
     135             :                              NULL,
     136             :                              NULL,
     137             :                              NULL);
     138             : 
     139           4 :     MarkGUCPrefixReserved("pg_prewarm");
     140             : 
     141             :     /* Register autoprewarm worker, if enabled. */
     142           4 :     if (autoprewarm)
     143           4 :         apw_start_leader_worker();
     144             : }
     145             : 
     146             : /*
     147             :  * Main entry point for the leader autoprewarm process.  Per-database workers
     148             :  * have a separate entry point.
     149             :  */
     150             : void
     151           4 : autoprewarm_main(Datum main_arg)
     152             : {
     153           4 :     bool        first_time = true;
     154           4 :     bool        final_dump_allowed = true;
     155           4 :     TimestampTz last_dump_time = 0;
     156             : 
     157             :     /* Establish signal handlers; once that's done, unblock signals. */
     158           4 :     pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
     159           4 :     pqsignal(SIGHUP, SignalHandlerForConfigReload);
     160           4 :     pqsignal(SIGUSR1, procsignal_sigusr1_handler);
     161           4 :     BackgroundWorkerUnblockSignals();
     162             : 
     163             :     /* Create (if necessary) and attach to our shared memory area. */
     164           4 :     if (apw_init_shmem())
     165           0 :         first_time = false;
     166             : 
     167             :     /*
     168             :      * Set on-detach hook so that our PID will be cleared on exit.
     169             :      *
     170             :      * NB: Autoprewarm's state is stored in a DSM segment, and DSM segments
     171             :      * are detached before calling the on_shmem_exit callbacks, so we must put
     172             :      * apw_detach_shmem in the before_shmem_exit callback list.
     173             :      */
     174           4 :     before_shmem_exit(apw_detach_shmem, 0);
     175             : 
     176             :     /*
     177             :      * Store our PID in the shared memory area --- unless there's already
     178             :      * another worker running, in which case just exit.
     179             :      */
     180           4 :     LWLockAcquire(&apw_state->lock, LW_EXCLUSIVE);
     181           4 :     if (apw_state->bgworker_pid != InvalidPid)
     182             :     {
     183           0 :         LWLockRelease(&apw_state->lock);
     184           0 :         ereport(LOG,
     185             :                 (errmsg("autoprewarm worker is already running under PID %d",
     186             :                         (int) apw_state->bgworker_pid)));
     187           0 :         return;
     188             :     }
     189           4 :     apw_state->bgworker_pid = MyProcPid;
     190           4 :     LWLockRelease(&apw_state->lock);
     191             : 
     192             :     /*
     193             :      * Preload buffers from the dump file only if we just created the shared
     194             :      * memory region.  Otherwise, it's either already been done or shouldn't
     195             :      * be done - e.g. because the old dump file has been overwritten since the
     196             :      * server was started.
     197             :      *
     198             :      * There's not much point in performing a dump immediately after we finish
     199             :      * preloading; so, if we do end up preloading, consider the last dump time
     200             :      * to be equal to the current time.
     201             :      *
     202             :      * If apw_load_buffers() is terminated early by a shutdown request,
     203             :      * prevent dumping out our state below the loop, because we'd effectively
     204             :      * just truncate the saved state to however much we'd managed to preload.
     205             :      */
     206           4 :     if (first_time)
     207             :     {
     208           4 :         apw_load_buffers();
     209           4 :         final_dump_allowed = !ShutdownRequestPending;
     210           4 :         last_dump_time = GetCurrentTimestamp();
     211             :     }
     212             : 
     213             :     /* Periodically dump buffers until terminated. */
     214          10 :     while (!ShutdownRequestPending)
     215             :     {
     216             :         /* In case of a SIGHUP, just reload the configuration. */
     217           6 :         if (ConfigReloadPending)
     218             :         {
     219           0 :             ConfigReloadPending = false;
     220           0 :             ProcessConfigFile(PGC_SIGHUP);
     221             :         }
     222             : 
     223           6 :         if (autoprewarm_interval <= 0)
     224             :         {
     225             :             /* We're only dumping at shutdown, so just wait forever. */
     226           6 :             (void) WaitLatch(MyLatch,
     227             :                              WL_LATCH_SET | WL_EXIT_ON_PM_DEATH,
     228             :                              -1L,
     229             :                              WAIT_EVENT_EXTENSION);
     230             :         }
     231             :         else
     232             :         {
     233             :             TimestampTz next_dump_time;
     234             :             long        delay_in_ms;
     235             : 
     236             :             /* Compute the next dump time. */
     237           0 :             next_dump_time =
     238           0 :                 TimestampTzPlusMilliseconds(last_dump_time,
     239             :                                             autoprewarm_interval * 1000);
     240             :             delay_in_ms =
     241           0 :                 TimestampDifferenceMilliseconds(GetCurrentTimestamp(),
     242             :                                                 next_dump_time);
     243             : 
     244             :             /* Perform a dump if it's time. */
     245           0 :             if (delay_in_ms <= 0)
     246             :             {
     247           0 :                 last_dump_time = GetCurrentTimestamp();
     248           0 :                 apw_dump_now(true, false);
     249           0 :                 continue;
     250             :             }
     251             : 
     252             :             /* Sleep until the next dump time. */
     253           0 :             (void) WaitLatch(MyLatch,
     254             :                              WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
     255             :                              delay_in_ms,
     256             :                              WAIT_EVENT_EXTENSION);
     257             :         }
     258             : 
     259             :         /* Reset the latch, loop. */
     260           6 :         ResetLatch(MyLatch);
     261             :     }
     262             : 
     263             :     /*
     264             :      * Dump one last time.  We assume this is probably the result of a system
     265             :      * shutdown, although it's possible that we've merely been terminated.
     266             :      */
     267           4 :     if (final_dump_allowed)
     268           4 :         apw_dump_now(true, true);
     269             : }
     270             : 
     271             : /*
     272             :  * Read the dump file and launch per-database workers one at a time to
     273             :  * prewarm the buffers found there.
     274             :  */
     275             : static void
     276           4 : apw_load_buffers(void)
     277             : {
     278           4 :     FILE       *file = NULL;
     279             :     int         num_elements,
     280             :                 i;
     281             :     BlockInfoRecord *blkinfo;
     282             :     dsm_segment *seg;
     283             : 
     284             :     /*
     285             :      * Skip the prewarm if the dump file is in use; otherwise, prevent any
     286             :      * other process from writing it while we're using it.
     287             :      */
     288           4 :     LWLockAcquire(&apw_state->lock, LW_EXCLUSIVE);
     289           4 :     if (apw_state->pid_using_dumpfile == InvalidPid)
     290           4 :         apw_state->pid_using_dumpfile = MyProcPid;
     291             :     else
     292             :     {
     293           0 :         LWLockRelease(&apw_state->lock);
     294           0 :         ereport(LOG,
     295             :                 (errmsg("skipping prewarm because block dump file is being written by PID %d",
     296             :                         (int) apw_state->pid_using_dumpfile)));
     297           2 :         return;
     298             :     }
     299           4 :     LWLockRelease(&apw_state->lock);
     300             : 
     301             :     /*
     302             :      * Open the block dump file.  Exit quietly if it doesn't exist, but report
     303             :      * any other error.
     304             :      */
     305           4 :     file = AllocateFile(AUTOPREWARM_FILE, "r");
     306           4 :     if (!file)
     307             :     {
     308           2 :         if (errno == ENOENT)
     309             :         {
     310           2 :             LWLockAcquire(&apw_state->lock, LW_EXCLUSIVE);
     311           2 :             apw_state->pid_using_dumpfile = InvalidPid;
     312           2 :             LWLockRelease(&apw_state->lock);
     313           2 :             return;             /* No file to load. */
     314             :         }
     315           0 :         ereport(ERROR,
     316             :                 (errcode_for_file_access(),
     317             :                  errmsg("could not read file \"%s\": %m",
     318             :                         AUTOPREWARM_FILE)));
     319             :     }
     320             : 
     321             :     /* First line of the file is a record count. */
     322           2 :     if (fscanf(file, "<<%d>>\n", &num_elements) != 1)
     323           0 :         ereport(ERROR,
     324             :                 (errcode_for_file_access(),
     325             :                  errmsg("could not read from file \"%s\": %m",
     326             :                         AUTOPREWARM_FILE)));
     327             : 
     328             :     /* Allocate a dynamic shared memory segment to store the record data. */
     329           2 :     seg = dsm_create(sizeof(BlockInfoRecord) * num_elements, 0);
     330           2 :     blkinfo = (BlockInfoRecord *) dsm_segment_address(seg);
     331             : 
     332             :     /* Read records, one per line. */
     333         392 :     for (i = 0; i < num_elements; i++)
     334             :     {
     335             :         unsigned    forknum;
     336             : 
     337         390 :         if (fscanf(file, "%u,%u,%u,%u,%u\n", &blkinfo[i].database,
     338         390 :                    &blkinfo[i].tablespace, &blkinfo[i].filenumber,
     339         390 :                    &forknum, &blkinfo[i].blocknum) != 5)
     340           0 :             ereport(ERROR,
     341             :                     (errmsg("autoprewarm block dump file is corrupted at line %d",
     342             :                             i + 1)));
     343         390 :         blkinfo[i].forknum = forknum;
     344             :     }
     345             : 
     346           2 :     FreeFile(file);
     347             : 
     348             :     /* Sort the blocks to be loaded. */
     349           2 :     qsort(blkinfo, num_elements, sizeof(BlockInfoRecord),
     350             :           apw_compare_blockinfo);
     351             : 
     352             :     /* Populate shared memory state. */
     353           2 :     apw_state->block_info_handle = dsm_segment_handle(seg);
     354           2 :     apw_state->prewarm_start_idx = apw_state->prewarm_stop_idx = 0;
     355           2 :     apw_state->prewarmed_blocks = 0;
     356             : 
     357             :     /* Get the info position of the first block of the next database. */
     358           4 :     while (apw_state->prewarm_start_idx < num_elements)
     359             :     {
     360           2 :         int         j = apw_state->prewarm_start_idx;
     361           2 :         Oid         current_db = blkinfo[j].database;
     362             : 
     363             :         /*
     364             :          * Advance the prewarm_stop_idx to the first BlockInfoRecord that does
     365             :          * not belong to this database.
     366             :          */
     367           2 :         j++;
     368         390 :         while (j < num_elements)
     369             :         {
     370         388 :             if (current_db != blkinfo[j].database)
     371             :             {
     372             :                 /*
     373             :                  * Combine BlockInfoRecords for global objects with those of
     374             :                  * the database.
     375             :                  */
     376           2 :                 if (current_db != InvalidOid)
     377           0 :                     break;
     378           2 :                 current_db = blkinfo[j].database;
     379             :             }
     380             : 
     381         388 :             j++;
     382             :         }
     383             : 
     384             :         /*
     385             :          * If we reach this point with current_db == InvalidOid, then only
     386             :          * BlockInfoRecords belonging to global objects exist.  We can't
     387             :          * prewarm without a database connection, so just bail out.
     388             :          */
     389           2 :         if (current_db == InvalidOid)
     390           0 :             break;
     391             : 
     392             :         /* Configure stop point and database for next per-database worker. */
     393           2 :         apw_state->prewarm_stop_idx = j;
     394           2 :         apw_state->database = current_db;
     395             :         Assert(apw_state->prewarm_start_idx < apw_state->prewarm_stop_idx);
     396             : 
     397             :         /* If we've run out of free buffers, don't launch another worker. */
     398           2 :         if (!have_free_buffer())
     399           0 :             break;
     400             : 
     401             :         /*
     402             :          * Likewise, don't launch if we've already been told to shut down.
     403             :          * (The launch would fail anyway, but we might as well skip it.)
     404             :          */
     405           2 :         if (ShutdownRequestPending)
     406           0 :             break;
     407             : 
     408             :         /*
     409             :          * Start a per-database worker to load blocks for this database; this
     410             :          * function will return once the per-database worker exits.
     411             :          */
     412           2 :         apw_start_database_worker();
     413             : 
     414             :         /* Prepare for next database. */
     415           2 :         apw_state->prewarm_start_idx = apw_state->prewarm_stop_idx;
     416             :     }
     417             : 
     418             :     /* Clean up. */
     419           2 :     dsm_detach(seg);
     420           2 :     LWLockAcquire(&apw_state->lock, LW_EXCLUSIVE);
     421           2 :     apw_state->block_info_handle = DSM_HANDLE_INVALID;
     422           2 :     apw_state->pid_using_dumpfile = InvalidPid;
     423           2 :     LWLockRelease(&apw_state->lock);
     424             : 
     425             :     /* Report our success, if we were able to finish. */
     426           2 :     if (!ShutdownRequestPending)
     427           2 :         ereport(LOG,
     428             :                 (errmsg("autoprewarm successfully prewarmed %d of %d previously-loaded blocks",
     429             :                         apw_state->prewarmed_blocks, num_elements)));
     430             : }
     431             : 
     432             : /*
     433             :  * Prewarm all blocks for one database (and possibly also global objects, if
     434             :  * those got grouped with this database).
     435             :  */
     436             : void
     437           2 : autoprewarm_database_main(Datum main_arg)
     438             : {
     439             :     int         pos;
     440             :     BlockInfoRecord *block_info;
     441           2 :     Relation    rel = NULL;
     442           2 :     BlockNumber nblocks = 0;
     443           2 :     BlockInfoRecord *old_blk = NULL;
     444             :     dsm_segment *seg;
     445             : 
     446             :     /* Establish signal handlers; once that's done, unblock signals. */
     447           2 :     pqsignal(SIGTERM, die);
     448           2 :     BackgroundWorkerUnblockSignals();
     449             : 
     450             :     /* Connect to correct database and get block information. */
     451           2 :     apw_init_shmem();
     452           2 :     seg = dsm_attach(apw_state->block_info_handle);
     453           2 :     if (seg == NULL)
     454           0 :         ereport(ERROR,
     455             :                 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
     456             :                  errmsg("could not map dynamic shared memory segment")));
     457           2 :     BackgroundWorkerInitializeConnectionByOid(apw_state->database, InvalidOid, 0);
     458           2 :     block_info = (BlockInfoRecord *) dsm_segment_address(seg);
     459           2 :     pos = apw_state->prewarm_start_idx;
     460             : 
     461             :     /*
     462             :      * Loop until we run out of blocks to prewarm or until we run out of free
     463             :      * buffers.
     464             :      */
     465         392 :     while (pos < apw_state->prewarm_stop_idx && have_free_buffer())
     466             :     {
     467         390 :         BlockInfoRecord *blk = &block_info[pos++];
     468             :         Buffer      buf;
     469             : 
     470         390 :         CHECK_FOR_INTERRUPTS();
     471             : 
     472             :         /*
     473             :          * Quit if we've reached records for another database. If previous
     474             :          * blocks are of some global objects, then continue pre-warming.
     475             :          */
     476         390 :         if (old_blk != NULL && old_blk->database != blk->database &&
     477           2 :             old_blk->database != 0)
     478           0 :             break;
     479             : 
     480             :         /*
     481             :          * As soon as we encounter a block of a new relation, close the old
     482             :          * relation. Note that rel will be NULL if try_relation_open failed
     483             :          * previously; in that case, there is nothing to close.
     484             :          */
     485         390 :         if (old_blk != NULL && old_blk->filenumber != blk->filenumber &&
     486             :             rel != NULL)
     487             :         {
     488          94 :             relation_close(rel, AccessShareLock);
     489          94 :             rel = NULL;
     490          94 :             CommitTransactionCommand();
     491             :         }
     492             : 
     493             :         /*
     494             :          * Try to open each new relation, but only once, when we first
     495             :          * encounter it. If it's been dropped, skip the associated blocks.
     496             :          */
     497         390 :         if (old_blk == NULL || old_blk->filenumber != blk->filenumber)
     498             :         {
     499             :             Oid         reloid;
     500             : 
     501             :             Assert(rel == NULL);
     502          96 :             StartTransactionCommand();
     503          96 :             reloid = RelidByRelfilenumber(blk->tablespace, blk->filenumber);
     504          96 :             if (OidIsValid(reloid))
     505          96 :                 rel = try_relation_open(reloid, AccessShareLock);
     506             : 
     507          96 :             if (!rel)
     508           0 :                 CommitTransactionCommand();
     509             :         }
     510         390 :         if (!rel)
     511             :         {
     512           0 :             old_blk = blk;
     513           0 :             continue;
     514             :         }
     515             : 
     516             :         /* Once per fork, check for fork existence and size. */
     517         390 :         if (old_blk == NULL ||
     518         388 :             old_blk->filenumber != blk->filenumber ||
     519         294 :             old_blk->forknum != blk->forknum)
     520             :         {
     521             :             /*
     522             :              * smgrexists is not safe for illegal forknum, hence check whether
     523             :              * the passed forknum is valid before using it in smgrexists.
     524             :              */
     525         124 :             if (blk->forknum > InvalidForkNumber &&
     526         248 :                 blk->forknum <= MAX_FORKNUM &&
     527         124 :                 smgrexists(RelationGetSmgr(rel), blk->forknum))
     528         124 :                 nblocks = RelationGetNumberOfBlocksInFork(rel, blk->forknum);
     529             :             else
     530           0 :                 nblocks = 0;
     531             :         }
     532             : 
     533             :         /* Check whether blocknum is valid and within fork file size. */
     534         390 :         if (blk->blocknum >= nblocks)
     535             :         {
     536             :             /* Move to next forknum. */
     537           0 :             old_blk = blk;
     538           0 :             continue;
     539             :         }
     540             : 
     541             :         /* Prewarm buffer. */
     542         390 :         buf = ReadBufferExtended(rel, blk->forknum, blk->blocknum, RBM_NORMAL,
     543             :                                  NULL);
     544         390 :         if (BufferIsValid(buf))
     545             :         {
     546         390 :             apw_state->prewarmed_blocks++;
     547         390 :             ReleaseBuffer(buf);
     548             :         }
     549             : 
     550         390 :         old_blk = blk;
     551             :     }
     552             : 
     553           2 :     dsm_detach(seg);
     554             : 
     555             :     /* Release lock on previous relation. */
     556           2 :     if (rel)
     557             :     {
     558           2 :         relation_close(rel, AccessShareLock);
     559           2 :         CommitTransactionCommand();
     560             :     }
     561           2 : }
     562             : 
     563             : /*
     564             :  * Dump information on blocks in shared buffers.  We use a text format here
     565             :  * so that it's easy to understand and even change the file contents if
     566             :  * necessary.
     567             :  * Returns the number of blocks dumped.
     568             :  */
     569             : static int
     570           6 : apw_dump_now(bool is_bgworker, bool dump_unlogged)
     571             : {
     572             :     int         num_blocks;
     573             :     int         i;
     574             :     int         ret;
     575             :     BlockInfoRecord *block_info_array;
     576             :     BufferDesc *bufHdr;
     577             :     FILE       *file;
     578             :     char        transient_dump_file_path[MAXPGPATH];
     579             :     pid_t       pid;
     580             : 
     581           6 :     LWLockAcquire(&apw_state->lock, LW_EXCLUSIVE);
     582           6 :     pid = apw_state->pid_using_dumpfile;
     583           6 :     if (apw_state->pid_using_dumpfile == InvalidPid)
     584           6 :         apw_state->pid_using_dumpfile = MyProcPid;
     585           6 :     LWLockRelease(&apw_state->lock);
     586             : 
     587           6 :     if (pid != InvalidPid)
     588             :     {
     589           0 :         if (!is_bgworker)
     590           0 :             ereport(ERROR,
     591             :                     (errmsg("could not perform block dump because dump file is being used by PID %d",
     592             :                             (int) apw_state->pid_using_dumpfile)));
     593             : 
     594           0 :         ereport(LOG,
     595             :                 (errmsg("skipping block dump because it is already being performed by PID %d",
     596             :                         (int) apw_state->pid_using_dumpfile)));
     597           0 :         return 0;
     598             :     }
     599             : 
     600             :     block_info_array =
     601           6 :         (BlockInfoRecord *) palloc(sizeof(BlockInfoRecord) * NBuffers);
     602             : 
     603       98310 :     for (num_blocks = 0, i = 0; i < NBuffers; i++)
     604             :     {
     605             :         uint32      buf_state;
     606             : 
     607       98304 :         CHECK_FOR_INTERRUPTS();
     608             : 
     609       98304 :         bufHdr = GetBufferDescriptor(i);
     610             : 
     611             :         /* Lock each buffer header before inspecting. */
     612       98304 :         buf_state = LockBufHdr(bufHdr);
     613             : 
     614             :         /*
     615             :          * Unlogged tables will be automatically truncated after a crash or
     616             :          * unclean shutdown. In such cases we need not prewarm them. Dump them
     617             :          * only if requested by caller.
     618             :          */
     619       98304 :         if (buf_state & BM_TAG_VALID &&
     620        1170 :             ((buf_state & BM_PERMANENT) || dump_unlogged))
     621             :         {
     622        1170 :             block_info_array[num_blocks].database = bufHdr->tag.dbOid;
     623        1170 :             block_info_array[num_blocks].tablespace = bufHdr->tag.spcOid;
     624        2340 :             block_info_array[num_blocks].filenumber =
     625        1170 :                 BufTagGetRelNumber(&bufHdr->tag);
     626        2340 :             block_info_array[num_blocks].forknum =
     627        1170 :                 BufTagGetForkNum(&bufHdr->tag);
     628        1170 :             block_info_array[num_blocks].blocknum = bufHdr->tag.blockNum;
     629        1170 :             ++num_blocks;
     630             :         }
     631             : 
     632       98304 :         UnlockBufHdr(bufHdr, buf_state);
     633             :     }
     634             : 
     635           6 :     snprintf(transient_dump_file_path, MAXPGPATH, "%s.tmp", AUTOPREWARM_FILE);
     636           6 :     file = AllocateFile(transient_dump_file_path, "w");
     637           6 :     if (!file)
     638           0 :         ereport(ERROR,
     639             :                 (errcode_for_file_access(),
     640             :                  errmsg("could not open file \"%s\": %m",
     641             :                         transient_dump_file_path)));
     642             : 
     643           6 :     ret = fprintf(file, "<<%d>>\n", num_blocks);
     644           6 :     if (ret < 0)
     645             :     {
     646           0 :         int         save_errno = errno;
     647             : 
     648           0 :         FreeFile(file);
     649           0 :         unlink(transient_dump_file_path);
     650           0 :         errno = save_errno;
     651           0 :         ereport(ERROR,
     652             :                 (errcode_for_file_access(),
     653             :                  errmsg("could not write to file \"%s\": %m",
     654             :                         transient_dump_file_path)));
     655             :     }
     656             : 
     657        1176 :     for (i = 0; i < num_blocks; i++)
     658             :     {
     659        1170 :         CHECK_FOR_INTERRUPTS();
     660             : 
     661        1170 :         ret = fprintf(file, "%u,%u,%u,%u,%u\n",
     662        1170 :                       block_info_array[i].database,
     663        1170 :                       block_info_array[i].tablespace,
     664        1170 :                       block_info_array[i].filenumber,
     665        1170 :                       (uint32) block_info_array[i].forknum,
     666        1170 :                       block_info_array[i].blocknum);
     667        1170 :         if (ret < 0)
     668             :         {
     669           0 :             int         save_errno = errno;
     670             : 
     671           0 :             FreeFile(file);
     672           0 :             unlink(transient_dump_file_path);
     673           0 :             errno = save_errno;
     674           0 :             ereport(ERROR,
     675             :                     (errcode_for_file_access(),
     676             :                      errmsg("could not write to file \"%s\": %m",
     677             :                             transient_dump_file_path)));
     678             :         }
     679             :     }
     680             : 
     681           6 :     pfree(block_info_array);
     682             : 
     683             :     /*
     684             :      * Rename transient_dump_file_path to AUTOPREWARM_FILE to make things
     685             :      * permanent.
     686             :      */
     687           6 :     ret = FreeFile(file);
     688           6 :     if (ret != 0)
     689             :     {
     690           0 :         int         save_errno = errno;
     691             : 
     692           0 :         unlink(transient_dump_file_path);
     693           0 :         errno = save_errno;
     694           0 :         ereport(ERROR,
     695             :                 (errcode_for_file_access(),
     696             :                  errmsg("could not close file \"%s\": %m",
     697             :                         transient_dump_file_path)));
     698             :     }
     699             : 
     700           6 :     (void) durable_rename(transient_dump_file_path, AUTOPREWARM_FILE, ERROR);
     701           6 :     apw_state->pid_using_dumpfile = InvalidPid;
     702             : 
     703           6 :     ereport(DEBUG1,
     704             :             (errmsg_internal("wrote block details for %d blocks", num_blocks)));
     705           6 :     return num_blocks;
     706             : }
     707             : 
     708             : /*
     709             :  * SQL-callable function to launch autoprewarm.
     710             :  */
     711             : Datum
     712           0 : autoprewarm_start_worker(PG_FUNCTION_ARGS)
     713             : {
     714             :     pid_t       pid;
     715             : 
     716           0 :     if (!autoprewarm)
     717           0 :         ereport(ERROR,
     718             :                 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
     719             :                  errmsg("autoprewarm is disabled")));
     720             : 
     721           0 :     apw_init_shmem();
     722           0 :     LWLockAcquire(&apw_state->lock, LW_EXCLUSIVE);
     723           0 :     pid = apw_state->bgworker_pid;
     724           0 :     LWLockRelease(&apw_state->lock);
     725             : 
     726           0 :     if (pid != InvalidPid)
     727           0 :         ereport(ERROR,
     728             :                 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
     729             :                  errmsg("autoprewarm worker is already running under PID %d",
     730             :                         (int) pid)));
     731             : 
     732           0 :     apw_start_leader_worker();
     733             : 
     734           0 :     PG_RETURN_VOID();
     735             : }
     736             : 
     737             : /*
     738             :  * SQL-callable function to perform an immediate block dump.
     739             :  *
     740             :  * Note: this is declared to return int8, as insurance against some
     741             :  * very distant day when we might make NBuffers wider than int.
     742             :  */
     743             : Datum
     744           2 : autoprewarm_dump_now(PG_FUNCTION_ARGS)
     745             : {
     746             :     int         num_blocks;
     747             : 
     748           2 :     apw_init_shmem();
     749             : 
     750           2 :     PG_ENSURE_ERROR_CLEANUP(apw_detach_shmem, 0);
     751             :     {
     752           2 :         num_blocks = apw_dump_now(false, true);
     753             :     }
     754           2 :     PG_END_ENSURE_ERROR_CLEANUP(apw_detach_shmem, 0);
     755             : 
     756           2 :     PG_RETURN_INT64((int64) num_blocks);
     757             : }
     758             : 
     759             : static void
     760           4 : apw_init_state(void *ptr)
     761             : {
     762           4 :     AutoPrewarmSharedState *state = (AutoPrewarmSharedState *) ptr;
     763             : 
     764           4 :     LWLockInitialize(&state->lock, LWLockNewTrancheId());
     765           4 :     state->bgworker_pid = InvalidPid;
     766           4 :     state->pid_using_dumpfile = InvalidPid;
     767           4 : }
     768             : 
     769             : /*
     770             :  * Allocate and initialize autoprewarm related shared memory, if not already
     771             :  * done, and set up backend-local pointer to that state.  Returns true if an
     772             :  * existing shared memory segment was found.
     773             :  */
     774             : static bool
     775           8 : apw_init_shmem(void)
     776             : {
     777             :     bool        found;
     778             : 
     779           8 :     apw_state = GetNamedDSMSegment("autoprewarm",
     780             :                                    sizeof(AutoPrewarmSharedState),
     781             :                                    apw_init_state,
     782             :                                    &found);
     783           8 :     LWLockRegisterTranche(apw_state->lock.tranche, "autoprewarm");
     784             : 
     785           8 :     return found;
     786             : }
     787             : 
     788             : /*
     789             :  * Clear our PID from autoprewarm shared state.
     790             :  */
     791             : static void
     792           4 : apw_detach_shmem(int code, Datum arg)
     793             : {
     794           4 :     LWLockAcquire(&apw_state->lock, LW_EXCLUSIVE);
     795           4 :     if (apw_state->pid_using_dumpfile == MyProcPid)
     796           0 :         apw_state->pid_using_dumpfile = InvalidPid;
     797           4 :     if (apw_state->bgworker_pid == MyProcPid)
     798           4 :         apw_state->bgworker_pid = InvalidPid;
     799           4 :     LWLockRelease(&apw_state->lock);
     800           4 : }
     801             : 
     802             : /*
     803             :  * Start autoprewarm leader worker process.
     804             :  */
     805             : static void
     806           4 : apw_start_leader_worker(void)
     807             : {
     808             :     BackgroundWorker worker;
     809             :     BackgroundWorkerHandle *handle;
     810             :     BgwHandleStatus status;
     811             :     pid_t       pid;
     812             : 
     813           4 :     MemSet(&worker, 0, sizeof(BackgroundWorker));
     814           4 :     worker.bgw_flags = BGWORKER_SHMEM_ACCESS;
     815           4 :     worker.bgw_start_time = BgWorkerStart_ConsistentState;
     816           4 :     strcpy(worker.bgw_library_name, "pg_prewarm");
     817           4 :     strcpy(worker.bgw_function_name, "autoprewarm_main");
     818           4 :     strcpy(worker.bgw_name, "autoprewarm leader");
     819           4 :     strcpy(worker.bgw_type, "autoprewarm leader");
     820             : 
     821           4 :     if (process_shared_preload_libraries_in_progress)
     822             :     {
     823           4 :         RegisterBackgroundWorker(&worker);
     824           4 :         return;
     825             :     }
     826             : 
     827             :     /* must set notify PID to wait for startup */
     828           0 :     worker.bgw_notify_pid = MyProcPid;
     829             : 
     830           0 :     if (!RegisterDynamicBackgroundWorker(&worker, &handle))
     831           0 :         ereport(ERROR,
     832             :                 (errcode(ERRCODE_INSUFFICIENT_RESOURCES),
     833             :                  errmsg("could not register background process"),
     834             :                  errhint("You may need to increase max_worker_processes.")));
     835             : 
     836           0 :     status = WaitForBackgroundWorkerStartup(handle, &pid);
     837           0 :     if (status != BGWH_STARTED)
     838           0 :         ereport(ERROR,
     839             :                 (errcode(ERRCODE_INSUFFICIENT_RESOURCES),
     840             :                  errmsg("could not start background process"),
     841             :                  errhint("More details may be available in the server log.")));
     842             : }
     843             : 
     844             : /*
     845             :  * Start autoprewarm per-database worker process.
     846             :  */
     847             : static void
     848           2 : apw_start_database_worker(void)
     849             : {
     850             :     BackgroundWorker worker;
     851             :     BackgroundWorkerHandle *handle;
     852             : 
     853           2 :     MemSet(&worker, 0, sizeof(BackgroundWorker));
     854           2 :     worker.bgw_flags =
     855             :         BGWORKER_SHMEM_ACCESS | BGWORKER_BACKEND_DATABASE_CONNECTION;
     856           2 :     worker.bgw_start_time = BgWorkerStart_ConsistentState;
     857           2 :     worker.bgw_restart_time = BGW_NEVER_RESTART;
     858           2 :     strcpy(worker.bgw_library_name, "pg_prewarm");
     859           2 :     strcpy(worker.bgw_function_name, "autoprewarm_database_main");
     860           2 :     strcpy(worker.bgw_name, "autoprewarm worker");
     861           2 :     strcpy(worker.bgw_type, "autoprewarm worker");
     862             : 
     863             :     /* must set notify PID to wait for shutdown */
     864           2 :     worker.bgw_notify_pid = MyProcPid;
     865             : 
     866           2 :     if (!RegisterDynamicBackgroundWorker(&worker, &handle))
     867           0 :         ereport(ERROR,
     868             :                 (errcode(ERRCODE_INSUFFICIENT_RESOURCES),
     869             :                  errmsg("registering dynamic bgworker autoprewarm failed"),
     870             :                  errhint("Consider increasing configuration parameter max_worker_processes.")));
     871             : 
     872             :     /*
     873             :      * Ignore return value; if it fails, postmaster has died, but we have
     874             :      * checks for that elsewhere.
     875             :      */
     876           2 :     WaitForBackgroundWorkerShutdown(handle);
     877           2 : }
     878             : 
     879             : /* Compare member elements to check whether they are not equal. */
     880             : #define cmp_member_elem(fld)    \
     881             : do { \
     882             :     if (a->fld < b->fld)       \
     883             :         return -1;              \
     884             :     else if (a->fld > b->fld)  \
     885             :         return 1;               \
     886             : } while(0)
     887             : 
     888             : /*
     889             :  * apw_compare_blockinfo
     890             :  *
     891             :  * We depend on all records for a particular database being consecutive
     892             :  * in the dump file; each per-database worker will preload blocks until
     893             :  * it sees a block for some other database.  Sorting by tablespace,
     894             :  * filenumber, forknum, and blocknum isn't critical for correctness, but
     895             :  * helps us get a sequential I/O pattern.
     896             :  */
     897             : static int
     898        3262 : apw_compare_blockinfo(const void *p, const void *q)
     899             : {
     900        3262 :     const BlockInfoRecord *a = (const BlockInfoRecord *) p;
     901        3262 :     const BlockInfoRecord *b = (const BlockInfoRecord *) q;
     902             : 
     903        3262 :     cmp_member_elem(database);
     904        3048 :     cmp_member_elem(tablespace);
     905        3048 :     cmp_member_elem(filenumber);
     906        1178 :     cmp_member_elem(forknum);
     907        1024 :     cmp_member_elem(blocknum);
     908             : 
     909           0 :     return 0;
     910             : }

Generated by: LCOV version 1.14