LCOV - code coverage report
Current view: top level - src/backend/access/transam - subtrans.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 94.9 % 99 94
Test Date: 2026-02-28 05:14:31 Functions: 100.0 % 14 14
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*-------------------------------------------------------------------------
       2              :  *
       3              :  * subtrans.c
       4              :  *      PostgreSQL subtransaction-log manager
       5              :  *
       6              :  * The pg_subtrans manager is a pg_xact-like manager that stores the parent
       7              :  * transaction Id for each transaction.  It is a fundamental part of the
       8              :  * nested transactions implementation.  A main transaction has a parent
       9              :  * of InvalidTransactionId, and each subtransaction has its immediate parent.
      10              :  * The tree can easily be walked from child to parent, but not in the
      11              :  * opposite direction.
      12              :  *
      13              :  * This code is based on xact.c, but the robustness requirements
      14              :  * are completely different from pg_xact, because we only need to remember
      15              :  * pg_subtrans information for currently-open transactions.  Thus, there is
      16              :  * no need to preserve data over a crash and restart.
      17              :  *
      18              :  * There are no XLOG interactions since we do not care about preserving
      19              :  * data across crashes.  During database startup, we simply force the
      20              :  * currently-active page of SUBTRANS to zeroes.
      21              :  *
      22              :  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
      23              :  * Portions Copyright (c) 1994, Regents of the University of California
      24              :  *
      25              :  * src/backend/access/transam/subtrans.c
      26              :  *
      27              :  *-------------------------------------------------------------------------
      28              :  */
      29              : #include "postgres.h"
      30              : 
      31              : #include "access/slru.h"
      32              : #include "access/subtrans.h"
      33              : #include "access/transam.h"
      34              : #include "miscadmin.h"
      35              : #include "pg_trace.h"
      36              : #include "utils/guc_hooks.h"
      37              : #include "utils/snapmgr.h"
      38              : 
      39              : 
      40              : /*
      41              :  * Defines for SubTrans page sizes.  A page is the same BLCKSZ as is used
      42              :  * everywhere else in Postgres.
      43              :  *
      44              :  * Note: because TransactionIds are 32 bits and wrap around at 0xFFFFFFFF,
      45              :  * SubTrans page numbering also wraps around at
      46              :  * 0xFFFFFFFF/SUBTRANS_XACTS_PER_PAGE, and segment numbering at
      47              :  * 0xFFFFFFFF/SUBTRANS_XACTS_PER_PAGE/SLRU_PAGES_PER_SEGMENT.  We need take no
      48              :  * explicit notice of that fact in this module, except when comparing segment
      49              :  * and page numbers in TruncateSUBTRANS (see SubTransPagePrecedes) and zeroing
      50              :  * them in StartupSUBTRANS.
      51              :  */
      52              : 
      53              : /* We need four bytes per xact */
      54              : #define SUBTRANS_XACTS_PER_PAGE (BLCKSZ / sizeof(TransactionId))
      55              : 
      56              : /*
      57              :  * Although we return an int64 the actual value can't currently exceed
      58              :  * 0xFFFFFFFF/SUBTRANS_XACTS_PER_PAGE.
      59              :  */
      60              : static inline int64
      61      6924897 : TransactionIdToPage(TransactionId xid)
      62              : {
      63      6924897 :     return xid / (int64) SUBTRANS_XACTS_PER_PAGE;
      64              : }
      65              : 
      66              : #define TransactionIdToEntry(xid) ((xid) % (TransactionId) SUBTRANS_XACTS_PER_PAGE)
      67              : 
      68              : 
      69              : /*
      70              :  * Link to shared-memory data structures for SUBTRANS control
      71              :  */
      72              : static SlruCtlData SubTransCtlData;
      73              : 
      74              : #define SubTransCtl  (&SubTransCtlData)
      75              : 
      76              : 
      77              : static bool SubTransPagePrecedes(int64 page1, int64 page2);
      78              : 
      79              : 
      80              : /*
      81              :  * Record the parent of a subtransaction in the subtrans log.
      82              :  */
      83              : void
      84         6516 : SubTransSetParent(TransactionId xid, TransactionId parent)
      85              : {
      86         6516 :     int64       pageno = TransactionIdToPage(xid);
      87         6516 :     int         entryno = TransactionIdToEntry(xid);
      88              :     int         slotno;
      89              :     LWLock     *lock;
      90              :     TransactionId *ptr;
      91              : 
      92              :     Assert(TransactionIdIsValid(parent));
      93              :     Assert(TransactionIdFollows(xid, parent));
      94              : 
      95         6516 :     lock = SimpleLruGetBankLock(SubTransCtl, pageno);
      96         6516 :     LWLockAcquire(lock, LW_EXCLUSIVE);
      97              : 
      98         6516 :     slotno = SimpleLruReadPage(SubTransCtl, pageno, true, xid);
      99         6516 :     ptr = (TransactionId *) SubTransCtl->shared->page_buffer[slotno];
     100         6516 :     ptr += entryno;
     101              : 
     102              :     /*
     103              :      * It's possible we'll try to set the parent xid multiple times but we
     104              :      * shouldn't ever be changing the xid from one valid xid to another valid
     105              :      * xid, which would corrupt the data structure.
     106              :      */
     107         6516 :     if (*ptr != parent)
     108              :     {
     109              :         Assert(*ptr == InvalidTransactionId);
     110         5930 :         *ptr = parent;
     111         5930 :         SubTransCtl->shared->page_dirty[slotno] = true;
     112              :     }
     113              : 
     114         6516 :     LWLockRelease(lock);
     115         6516 : }
     116              : 
     117              : /*
     118              :  * Interrogate the parent of a transaction in the subtrans log.
     119              :  */
     120              : TransactionId
     121         3142 : SubTransGetParent(TransactionId xid)
     122              : {
     123         3142 :     int64       pageno = TransactionIdToPage(xid);
     124         3142 :     int         entryno = TransactionIdToEntry(xid);
     125              :     int         slotno;
     126              :     TransactionId *ptr;
     127              :     TransactionId parent;
     128              : 
     129              :     /* Can't ask about stuff that might not be around anymore */
     130              :     Assert(TransactionIdFollowsOrEquals(xid, TransactionXmin));
     131              : 
     132              :     /* Bootstrap and frozen XIDs have no parent */
     133         3142 :     if (!TransactionIdIsNormal(xid))
     134            0 :         return InvalidTransactionId;
     135              : 
     136              :     /* lock is acquired by SimpleLruReadPage_ReadOnly */
     137              : 
     138         3142 :     slotno = SimpleLruReadPage_ReadOnly(SubTransCtl, pageno, xid);
     139         3142 :     ptr = (TransactionId *) SubTransCtl->shared->page_buffer[slotno];
     140         3142 :     ptr += entryno;
     141              : 
     142         3142 :     parent = *ptr;
     143              : 
     144         3142 :     LWLockRelease(SimpleLruGetBankLock(SubTransCtl, pageno));
     145              : 
     146         3142 :     return parent;
     147              : }
     148              : 
     149              : /*
     150              :  * SubTransGetTopmostTransaction
     151              :  *
     152              :  * Returns the topmost transaction of the given transaction id.
     153              :  *
     154              :  * Because we cannot look back further than TransactionXmin, it is possible
     155              :  * that this function will lie and return an intermediate subtransaction ID
     156              :  * instead of the true topmost parent ID.  This is OK, because in practice
     157              :  * we only care about detecting whether the topmost parent is still running
     158              :  * or is part of a current snapshot's list of still-running transactions.
     159              :  * Therefore, any XID before TransactionXmin is as good as any other.
     160              :  */
     161              : TransactionId
     162         1137 : SubTransGetTopmostTransaction(TransactionId xid)
     163              : {
     164         1137 :     TransactionId parentXid = xid,
     165         1137 :                 previousXid = xid;
     166              : 
     167              :     /* Can't ask about stuff that might not be around anymore */
     168              :     Assert(TransactionIdFollowsOrEquals(xid, TransactionXmin));
     169              : 
     170         4279 :     while (TransactionIdIsValid(parentXid))
     171              :     {
     172         3142 :         previousXid = parentXid;
     173         3142 :         if (TransactionIdPrecedes(parentXid, TransactionXmin))
     174            0 :             break;
     175         3142 :         parentXid = SubTransGetParent(parentXid);
     176              : 
     177              :         /*
     178              :          * By convention the parent xid gets allocated first, so should always
     179              :          * precede the child xid. Anything else points to a corrupted data
     180              :          * structure that could lead to an infinite loop, so exit.
     181              :          */
     182         3142 :         if (!TransactionIdPrecedes(parentXid, previousXid))
     183            0 :             elog(ERROR, "pg_subtrans contains invalid entry: xid %u points to parent xid %u",
     184              :                  previousXid, parentXid);
     185              :     }
     186              : 
     187              :     Assert(TransactionIdIsValid(previousXid));
     188              : 
     189         1137 :     return previousXid;
     190              : }
     191              : 
     192              : /*
     193              :  * Number of shared SUBTRANS buffers.
     194              :  *
     195              :  * If asked to autotune, use 2MB for every 1GB of shared buffers, up to 8MB.
     196              :  * Otherwise just cap the configured amount to be between 16 and the maximum
     197              :  * allowed.
     198              :  */
     199              : static int
     200         4435 : SUBTRANSShmemBuffers(void)
     201              : {
     202              :     /* auto-tune based on shared buffers */
     203         4435 :     if (subtransaction_buffers == 0)
     204         3266 :         return SimpleLruAutotuneBuffers(512, 1024);
     205              : 
     206         1169 :     return Min(Max(16, subtransaction_buffers), SLRU_MAX_ALLOWED_BUFFERS);
     207              : }
     208              : 
     209              : /*
     210              :  * Initialization of shared memory for SUBTRANS
     211              :  */
     212              : Size
     213         2147 : SUBTRANSShmemSize(void)
     214              : {
     215         2147 :     return SimpleLruShmemSize(SUBTRANSShmemBuffers(), 0);
     216              : }
     217              : 
     218              : void
     219         1150 : SUBTRANSShmemInit(void)
     220              : {
     221              :     /* If auto-tuning is requested, now is the time to do it */
     222         1150 :     if (subtransaction_buffers == 0)
     223              :     {
     224              :         char        buf[32];
     225              : 
     226         1138 :         snprintf(buf, sizeof(buf), "%d", SUBTRANSShmemBuffers());
     227         1138 :         SetConfigOption("subtransaction_buffers", buf, PGC_POSTMASTER,
     228              :                         PGC_S_DYNAMIC_DEFAULT);
     229              : 
     230              :         /*
     231              :          * We prefer to report this value's source as PGC_S_DYNAMIC_DEFAULT.
     232              :          * However, if the DBA explicitly set subtransaction_buffers = 0 in
     233              :          * the config file, then PGC_S_DYNAMIC_DEFAULT will fail to override
     234              :          * that and we must force the matter with PGC_S_OVERRIDE.
     235              :          */
     236         1138 :         if (subtransaction_buffers == 0)    /* failed to apply it? */
     237            0 :             SetConfigOption("subtransaction_buffers", buf, PGC_POSTMASTER,
     238              :                             PGC_S_OVERRIDE);
     239              :     }
     240              :     Assert(subtransaction_buffers != 0);
     241              : 
     242         1150 :     SubTransCtl->PagePrecedes = SubTransPagePrecedes;
     243         1150 :     SimpleLruInit(SubTransCtl, "subtransaction", SUBTRANSShmemBuffers(), 0,
     244              :                   "pg_subtrans", LWTRANCHE_SUBTRANS_BUFFER,
     245              :                   LWTRANCHE_SUBTRANS_SLRU, SYNC_HANDLER_NONE, false);
     246              :     SlruPagePrecedesUnitTests(SubTransCtl, SUBTRANS_XACTS_PER_PAGE);
     247         1150 : }
     248              : 
     249              : /*
     250              :  * GUC check_hook for subtransaction_buffers
     251              :  */
     252              : bool
     253         2351 : check_subtrans_buffers(int *newval, void **extra, GucSource source)
     254              : {
     255         2351 :     return check_slru_buffers("subtransaction_buffers", newval);
     256              : }
     257              : 
     258              : /*
     259              :  * This func must be called ONCE on system install.  It creates
     260              :  * the initial SUBTRANS segment.  (The SUBTRANS directory is assumed to
     261              :  * have been created by the initdb shell script, and SUBTRANSShmemInit
     262              :  * must have been called already.)
     263              :  *
     264              :  * Note: it's not really necessary to create the initial segment now,
     265              :  * since slru.c would create it on first write anyway.  But we may as well
     266              :  * do it to be sure the directory is set up correctly.
     267              :  */
     268              : void
     269           51 : BootStrapSUBTRANS(void)
     270              : {
     271              :     /* Zero the initial page and flush it to disk */
     272           51 :     SimpleLruZeroAndWritePage(SubTransCtl, 0);
     273           51 : }
     274              : 
     275              : /*
     276              :  * This must be called ONCE during postmaster or standalone-backend startup,
     277              :  * after StartupXLOG has initialized TransamVariables->nextXid.
     278              :  *
     279              :  * oldestActiveXID is the oldest XID of any prepared transaction, or nextXid
     280              :  * if there are none.
     281              :  */
     282              : void
     283         1000 : StartupSUBTRANS(TransactionId oldestActiveXID)
     284              : {
     285              :     FullTransactionId nextXid;
     286              :     int64       startPage;
     287              :     int64       endPage;
     288         1000 :     LWLock     *prevlock = NULL;
     289              :     LWLock     *lock;
     290              : 
     291              :     /*
     292              :      * Since we don't expect pg_subtrans to be valid across crashes, we
     293              :      * initialize the currently-active page(s) to zeroes during startup.
     294              :      * Whenever we advance into a new page, ExtendSUBTRANS will likewise zero
     295              :      * the new page without regard to whatever was previously on disk.
     296              :      */
     297         1000 :     startPage = TransactionIdToPage(oldestActiveXID);
     298         1000 :     nextXid = TransamVariables->nextXid;
     299         1000 :     endPage = TransactionIdToPage(XidFromFullTransactionId(nextXid));
     300              : 
     301              :     for (;;)
     302              :     {
     303         1002 :         lock = SimpleLruGetBankLock(SubTransCtl, startPage);
     304         1002 :         if (prevlock != lock)
     305              :         {
     306         1002 :             if (prevlock)
     307            2 :                 LWLockRelease(prevlock);
     308         1002 :             LWLockAcquire(lock, LW_EXCLUSIVE);
     309         1002 :             prevlock = lock;
     310              :         }
     311              : 
     312         1002 :         (void) SimpleLruZeroPage(SubTransCtl, startPage);
     313         1002 :         if (startPage == endPage)
     314         1000 :             break;
     315              : 
     316            2 :         startPage++;
     317              :         /* must account for wraparound */
     318            2 :         if (startPage > TransactionIdToPage(MaxTransactionId))
     319            0 :             startPage = 0;
     320              :     }
     321              : 
     322         1000 :     LWLockRelease(lock);
     323         1000 : }
     324              : 
     325              : /*
     326              :  * Perform a checkpoint --- either during shutdown, or on-the-fly
     327              :  */
     328              : void
     329         1797 : CheckPointSUBTRANS(void)
     330              : {
     331              :     /*
     332              :      * Write dirty SUBTRANS pages to disk
     333              :      *
     334              :      * This is not actually necessary from a correctness point of view. We do
     335              :      * it merely to improve the odds that writing of dirty pages is done by
     336              :      * the checkpoint process and not by backends.
     337              :      */
     338              :     TRACE_POSTGRESQL_SUBTRANS_CHECKPOINT_START(true);
     339         1797 :     SimpleLruWriteAll(SubTransCtl, true);
     340              :     TRACE_POSTGRESQL_SUBTRANS_CHECKPOINT_DONE(true);
     341         1797 : }
     342              : 
     343              : 
     344              : /*
     345              :  * Make sure that SUBTRANS has room for a newly-allocated XID.
     346              :  *
     347              :  * NB: this is called while holding XidGenLock.  We want it to be very fast
     348              :  * most of the time; even when it's not so fast, no actual I/O need happen
     349              :  * unless we're forced to write out a dirty subtrans page to make room
     350              :  * in shared memory.
     351              :  */
     352              : void
     353     24546960 : ExtendSUBTRANS(TransactionId newestXact)
     354              : {
     355              :     int64       pageno;
     356              :     LWLock     *lock;
     357              : 
     358              :     /*
     359              :      * No work except at first XID of a page.  But beware: just after
     360              :      * wraparound, the first XID of page zero is FirstNormalTransactionId.
     361              :      */
     362     24546960 :     if (TransactionIdToEntry(newestXact) != 0 &&
     363              :         !TransactionIdEquals(newestXact, FirstNormalTransactionId))
     364     17635491 :         return;
     365              : 
     366      6911469 :     pageno = TransactionIdToPage(newestXact);
     367              : 
     368      6911469 :     lock = SimpleLruGetBankLock(SubTransCtl, pageno);
     369      6911469 :     LWLockAcquire(lock, LW_EXCLUSIVE);
     370              : 
     371              :     /* Zero the page */
     372      6911469 :     SimpleLruZeroPage(SubTransCtl, pageno);
     373              : 
     374      6911469 :     LWLockRelease(lock);
     375              : }
     376              : 
     377              : 
     378              : /*
     379              :  * Remove all SUBTRANS segments before the one holding the passed transaction ID
     380              :  *
     381              :  * oldestXact is the oldest TransactionXmin of any running transaction.  This
     382              :  * is called only during checkpoint.
     383              :  */
     384              : void
     385         1768 : TruncateSUBTRANS(TransactionId oldestXact)
     386              : {
     387              :     int64       cutoffPage;
     388              : 
     389              :     /*
     390              :      * The cutoff point is the start of the segment containing oldestXact. We
     391              :      * pass the *page* containing oldestXact to SimpleLruTruncate.  We step
     392              :      * back one transaction to avoid passing a cutoff page that hasn't been
     393              :      * created yet in the rare case that oldestXact would be the first item on
     394              :      * a page and oldestXact == next XID.  In that case, if we didn't subtract
     395              :      * one, we'd trigger SimpleLruTruncate's wraparound detection.
     396              :      */
     397         1927 :     TransactionIdRetreat(oldestXact);
     398         1768 :     cutoffPage = TransactionIdToPage(oldestXact);
     399              : 
     400         1768 :     SimpleLruTruncate(SubTransCtl, cutoffPage);
     401         1768 : }
     402              : 
     403              : 
     404              : /*
     405              :  * Decide whether a SUBTRANS page number is "older" for truncation purposes.
     406              :  * Analogous to CLOGPagePrecedes().
     407              :  */
     408              : static bool
     409       329009 : SubTransPagePrecedes(int64 page1, int64 page2)
     410              : {
     411              :     TransactionId xid1;
     412              :     TransactionId xid2;
     413              : 
     414       329009 :     xid1 = ((TransactionId) page1) * SUBTRANS_XACTS_PER_PAGE;
     415       329009 :     xid1 += FirstNormalTransactionId + 1;
     416       329009 :     xid2 = ((TransactionId) page2) * SUBTRANS_XACTS_PER_PAGE;
     417       329009 :     xid2 += FirstNormalTransactionId + 1;
     418              : 
     419       587088 :     return (TransactionIdPrecedes(xid1, xid2) &&
     420       258079 :             TransactionIdPrecedes(xid1, xid2 + SUBTRANS_XACTS_PER_PAGE - 1));
     421              : }
        

Generated by: LCOV version 2.0-1