LCOV - code coverage report
Current view: top level - src/backend/access/transam - subtrans.c (source / functions) Hit Total Coverage
Test: PostgreSQL 19devel Lines: 94 99 94.9 %
Date: 2025-07-25 18:17:26 Functions: 14 14 100.0 %
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-2025, 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    13839570 : TransactionIdToPage(TransactionId xid)
      62             : {
      63    13839570 :     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       12994 : SubTransSetParent(TransactionId xid, TransactionId parent)
      85             : {
      86       12994 :     int64       pageno = TransactionIdToPage(xid);
      87       12994 :     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       12994 :     lock = SimpleLruGetBankLock(SubTransCtl, pageno);
      96       12994 :     LWLockAcquire(lock, LW_EXCLUSIVE);
      97             : 
      98       12994 :     slotno = SimpleLruReadPage(SubTransCtl, pageno, true, xid);
      99       12994 :     ptr = (TransactionId *) SubTransCtl->shared->page_buffer[slotno];
     100       12994 :     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       12994 :     if (*ptr != parent)
     108             :     {
     109             :         Assert(*ptr == InvalidTransactionId);
     110       11822 :         *ptr = parent;
     111       11822 :         SubTransCtl->shared->page_dirty[slotno] = true;
     112             :     }
     113             : 
     114       12994 :     LWLockRelease(lock);
     115       12994 : }
     116             : 
     117             : /*
     118             :  * Interrogate the parent of a transaction in the subtrans log.
     119             :  */
     120             : TransactionId
     121        6370 : SubTransGetParent(TransactionId xid)
     122             : {
     123        6370 :     int64       pageno = TransactionIdToPage(xid);
     124        6370 :     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        6370 :     if (!TransactionIdIsNormal(xid))
     134           0 :         return InvalidTransactionId;
     135             : 
     136             :     /* lock is acquired by SimpleLruReadPage_ReadOnly */
     137             : 
     138        6370 :     slotno = SimpleLruReadPage_ReadOnly(SubTransCtl, pageno, xid);
     139        6370 :     ptr = (TransactionId *) SubTransCtl->shared->page_buffer[slotno];
     140        6370 :     ptr += entryno;
     141             : 
     142        6370 :     parent = *ptr;
     143             : 
     144        6370 :     LWLockRelease(SimpleLruGetBankLock(SubTransCtl, pageno));
     145             : 
     146        6370 :     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        2360 : SubTransGetTopmostTransaction(TransactionId xid)
     163             : {
     164        2360 :     TransactionId parentXid = xid,
     165        2360 :                 previousXid = xid;
     166             : 
     167             :     /* Can't ask about stuff that might not be around anymore */
     168             :     Assert(TransactionIdFollowsOrEquals(xid, TransactionXmin));
     169             : 
     170        8730 :     while (TransactionIdIsValid(parentXid))
     171             :     {
     172        6370 :         previousXid = parentXid;
     173        6370 :         if (TransactionIdPrecedes(parentXid, TransactionXmin))
     174           0 :             break;
     175        6370 :         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        6370 :         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        2360 :     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        8278 : SUBTRANSShmemBuffers(void)
     201             : {
     202             :     /* auto-tune based on shared buffers */
     203        8278 :     if (subtransaction_buffers == 0)
     204        6088 :         return SimpleLruAutotuneBuffers(512, 1024);
     205             : 
     206        2190 :     return Min(Max(16, subtransaction_buffers), SLRU_MAX_ALLOWED_BUFFERS);
     207             : }
     208             : 
     209             : /*
     210             :  * Initialization of shared memory for SUBTRANS
     211             :  */
     212             : Size
     213        3998 : SUBTRANSShmemSize(void)
     214             : {
     215        3998 :     return SimpleLruShmemSize(SUBTRANSShmemBuffers(), 0);
     216             : }
     217             : 
     218             : void
     219        2152 : SUBTRANSShmemInit(void)
     220             : {
     221             :     /* If auto-tuning is requested, now is the time to do it */
     222        2152 :     if (subtransaction_buffers == 0)
     223             :     {
     224             :         char        buf[32];
     225             : 
     226        2128 :         snprintf(buf, sizeof(buf), "%d", SUBTRANSShmemBuffers());
     227        2128 :         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        2128 :         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        2152 :     SubTransCtl->PagePrecedes = SubTransPagePrecedes;
     243        2152 :     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        2152 : }
     248             : 
     249             : /*
     250             :  * GUC check_hook for subtransaction_buffers
     251             :  */
     252             : bool
     253        4408 : check_subtrans_buffers(int *newval, void **extra, GucSource source)
     254             : {
     255        4408 :     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         102 : BootStrapSUBTRANS(void)
     270             : {
     271             :     /* Zero the initial page and flush it to disk */
     272         102 :     SimpleLruZeroAndWritePage(SubTransCtl, 0);
     273         102 : }
     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        1858 : StartupSUBTRANS(TransactionId oldestActiveXID)
     284             : {
     285             :     FullTransactionId nextXid;
     286             :     int64       startPage;
     287             :     int64       endPage;
     288        1858 :     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        1858 :     startPage = TransactionIdToPage(oldestActiveXID);
     298        1858 :     nextXid = TransamVariables->nextXid;
     299        1858 :     endPage = TransactionIdToPage(XidFromFullTransactionId(nextXid));
     300             : 
     301             :     for (;;)
     302             :     {
     303        1862 :         lock = SimpleLruGetBankLock(SubTransCtl, startPage);
     304        1862 :         if (prevlock != lock)
     305             :         {
     306        1862 :             if (prevlock)
     307           4 :                 LWLockRelease(prevlock);
     308        1862 :             LWLockAcquire(lock, LW_EXCLUSIVE);
     309        1862 :             prevlock = lock;
     310             :         }
     311             : 
     312        1862 :         (void) SimpleLruZeroPage(SubTransCtl, startPage);
     313        1862 :         if (startPage == endPage)
     314        1858 :             break;
     315             : 
     316           4 :         startPage++;
     317             :         /* must account for wraparound */
     318           4 :         if (startPage > TransactionIdToPage(MaxTransactionId))
     319           0 :             startPage = 0;
     320             :     }
     321             : 
     322        1858 :     LWLockRelease(lock);
     323        1858 : }
     324             : 
     325             : /*
     326             :  * Perform a checkpoint --- either during shutdown, or on-the-fly
     327             :  */
     328             : void
     329        3380 : 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        3380 :     SimpleLruWriteAll(SubTransCtl, true);
     340             :     TRACE_POSTGRESQL_SUBTRANS_CHECKPOINT_DONE(true);
     341        3380 : }
     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    49045786 : 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    49045786 :     if (TransactionIdToEntry(newestXact) != 0 &&
     363             :         !TransactionIdEquals(newestXact, FirstNormalTransactionId))
     364    35232622 :         return;
     365             : 
     366    13813164 :     pageno = TransactionIdToPage(newestXact);
     367             : 
     368    13813164 :     lock = SimpleLruGetBankLock(SubTransCtl, pageno);
     369    13813164 :     LWLockAcquire(lock, LW_EXCLUSIVE);
     370             : 
     371             :     /* Zero the page */
     372    13813164 :     SimpleLruZeroPage(SubTransCtl, pageno);
     373             : 
     374    13813164 :     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        3322 : 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        3640 :     TransactionIdRetreat(oldestXact);
     398        3322 :     cutoffPage = TransactionIdToPage(oldestXact);
     399             : 
     400        3322 :     SimpleLruTruncate(SubTransCtl, cutoffPage);
     401        3322 : }
     402             : 
     403             : 
     404             : /*
     405             :  * Decide whether a SUBTRANS page number is "older" for truncation purposes.
     406             :  * Analogous to CLOGPagePrecedes().
     407             :  */
     408             : static bool
     409      646520 : SubTransPagePrecedes(int64 page1, int64 page2)
     410             : {
     411             :     TransactionId xid1;
     412             :     TransactionId xid2;
     413             : 
     414      646520 :     xid1 = ((TransactionId) page1) * SUBTRANS_XACTS_PER_PAGE;
     415      646520 :     xid1 += FirstNormalTransactionId + 1;
     416      646520 :     xid2 = ((TransactionId) page2) * SUBTRANS_XACTS_PER_PAGE;
     417      646520 :     xid2 += FirstNormalTransactionId + 1;
     418             : 
     419     1162610 :     return (TransactionIdPrecedes(xid1, xid2) &&
     420      516090 :             TransactionIdPrecedes(xid1, xid2 + SUBTRANS_XACTS_PER_PAGE - 1));
     421             : }

Generated by: LCOV version 1.16