LCOV - code coverage report
Current view: top level - src/backend/access/transam - subtrans.c (source / functions) Hit Total Coverage
Test: PostgreSQL 18devel Lines: 100 105 95.2 %
Date: 2024-12-02 20:15:07 Functions: 15 15 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-2024, 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    13838072 : TransactionIdToPage(TransactionId xid)
      62             : {
      63    13838072 :     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 int  ZeroSUBTRANSPage(int64 pageno);
      78             : static bool SubTransPagePrecedes(int64 page1, int64 page2);
      79             : 
      80             : 
      81             : /*
      82             :  * Record the parent of a subtransaction in the subtrans log.
      83             :  */
      84             : void
      85       13034 : SubTransSetParent(TransactionId xid, TransactionId parent)
      86             : {
      87       13034 :     int64       pageno = TransactionIdToPage(xid);
      88       13034 :     int         entryno = TransactionIdToEntry(xid);
      89             :     int         slotno;
      90             :     LWLock     *lock;
      91             :     TransactionId *ptr;
      92             : 
      93             :     Assert(TransactionIdIsValid(parent));
      94             :     Assert(TransactionIdFollows(xid, parent));
      95             : 
      96       13034 :     lock = SimpleLruGetBankLock(SubTransCtl, pageno);
      97       13034 :     LWLockAcquire(lock, LW_EXCLUSIVE);
      98             : 
      99       13034 :     slotno = SimpleLruReadPage(SubTransCtl, pageno, true, xid);
     100       13034 :     ptr = (TransactionId *) SubTransCtl->shared->page_buffer[slotno];
     101       13034 :     ptr += entryno;
     102             : 
     103             :     /*
     104             :      * It's possible we'll try to set the parent xid multiple times but we
     105             :      * shouldn't ever be changing the xid from one valid xid to another valid
     106             :      * xid, which would corrupt the data structure.
     107             :      */
     108       13034 :     if (*ptr != parent)
     109             :     {
     110             :         Assert(*ptr == InvalidTransactionId);
     111       11862 :         *ptr = parent;
     112       11862 :         SubTransCtl->shared->page_dirty[slotno] = true;
     113             :     }
     114             : 
     115       13034 :     LWLockRelease(lock);
     116       13034 : }
     117             : 
     118             : /*
     119             :  * Interrogate the parent of a transaction in the subtrans log.
     120             :  */
     121             : TransactionId
     122        6208 : SubTransGetParent(TransactionId xid)
     123             : {
     124        6208 :     int64       pageno = TransactionIdToPage(xid);
     125        6208 :     int         entryno = TransactionIdToEntry(xid);
     126             :     int         slotno;
     127             :     TransactionId *ptr;
     128             :     TransactionId parent;
     129             : 
     130             :     /* Can't ask about stuff that might not be around anymore */
     131             :     Assert(TransactionIdFollowsOrEquals(xid, TransactionXmin));
     132             : 
     133             :     /* Bootstrap and frozen XIDs have no parent */
     134        6208 :     if (!TransactionIdIsNormal(xid))
     135           0 :         return InvalidTransactionId;
     136             : 
     137             :     /* lock is acquired by SimpleLruReadPage_ReadOnly */
     138             : 
     139        6208 :     slotno = SimpleLruReadPage_ReadOnly(SubTransCtl, pageno, xid);
     140        6208 :     ptr = (TransactionId *) SubTransCtl->shared->page_buffer[slotno];
     141        6208 :     ptr += entryno;
     142             : 
     143        6208 :     parent = *ptr;
     144             : 
     145        6208 :     LWLockRelease(SimpleLruGetBankLock(SubTransCtl, pageno));
     146             : 
     147        6208 :     return parent;
     148             : }
     149             : 
     150             : /*
     151             :  * SubTransGetTopmostTransaction
     152             :  *
     153             :  * Returns the topmost transaction of the given transaction id.
     154             :  *
     155             :  * Because we cannot look back further than TransactionXmin, it is possible
     156             :  * that this function will lie and return an intermediate subtransaction ID
     157             :  * instead of the true topmost parent ID.  This is OK, because in practice
     158             :  * we only care about detecting whether the topmost parent is still running
     159             :  * or is part of a current snapshot's list of still-running transactions.
     160             :  * Therefore, any XID before TransactionXmin is as good as any other.
     161             :  */
     162             : TransactionId
     163        2200 : SubTransGetTopmostTransaction(TransactionId xid)
     164             : {
     165        2200 :     TransactionId parentXid = xid,
     166        2200 :                 previousXid = xid;
     167             : 
     168             :     /* Can't ask about stuff that might not be around anymore */
     169             :     Assert(TransactionIdFollowsOrEquals(xid, TransactionXmin));
     170             : 
     171        8408 :     while (TransactionIdIsValid(parentXid))
     172             :     {
     173        6208 :         previousXid = parentXid;
     174        6208 :         if (TransactionIdPrecedes(parentXid, TransactionXmin))
     175           0 :             break;
     176        6208 :         parentXid = SubTransGetParent(parentXid);
     177             : 
     178             :         /*
     179             :          * By convention the parent xid gets allocated first, so should always
     180             :          * precede the child xid. Anything else points to a corrupted data
     181             :          * structure that could lead to an infinite loop, so exit.
     182             :          */
     183        6208 :         if (!TransactionIdPrecedes(parentXid, previousXid))
     184           0 :             elog(ERROR, "pg_subtrans contains invalid entry: xid %u points to parent xid %u",
     185             :                  previousXid, parentXid);
     186             :     }
     187             : 
     188             :     Assert(TransactionIdIsValid(previousXid));
     189             : 
     190        2200 :     return previousXid;
     191             : }
     192             : 
     193             : /*
     194             :  * Number of shared SUBTRANS buffers.
     195             :  *
     196             :  * If asked to autotune, use 2MB for every 1GB of shared buffers, up to 8MB.
     197             :  * Otherwise just cap the configured amount to be between 16 and the maximum
     198             :  * allowed.
     199             :  */
     200             : static int
     201        7314 : SUBTRANSShmemBuffers(void)
     202             : {
     203             :     /* auto-tune based on shared buffers */
     204        7314 :     if (subtransaction_buffers == 0)
     205        5374 :         return SimpleLruAutotuneBuffers(512, 1024);
     206             : 
     207        1940 :     return Min(Max(16, subtransaction_buffers), SLRU_MAX_ALLOWED_BUFFERS);
     208             : }
     209             : 
     210             : /*
     211             :  * Initialization of shared memory for SUBTRANS
     212             :  */
     213             : Size
     214        3534 : SUBTRANSShmemSize(void)
     215             : {
     216        3534 :     return SimpleLruShmemSize(SUBTRANSShmemBuffers(), 0);
     217             : }
     218             : 
     219             : void
     220        1902 : SUBTRANSShmemInit(void)
     221             : {
     222             :     /* If auto-tuning is requested, now is the time to do it */
     223        1902 :     if (subtransaction_buffers == 0)
     224             :     {
     225             :         char        buf[32];
     226             : 
     227        1878 :         snprintf(buf, sizeof(buf), "%d", SUBTRANSShmemBuffers());
     228        1878 :         SetConfigOption("subtransaction_buffers", buf, PGC_POSTMASTER,
     229             :                         PGC_S_DYNAMIC_DEFAULT);
     230             : 
     231             :         /*
     232             :          * We prefer to report this value's source as PGC_S_DYNAMIC_DEFAULT.
     233             :          * However, if the DBA explicitly set subtransaction_buffers = 0 in
     234             :          * the config file, then PGC_S_DYNAMIC_DEFAULT will fail to override
     235             :          * that and we must force the matter with PGC_S_OVERRIDE.
     236             :          */
     237        1878 :         if (subtransaction_buffers == 0)    /* failed to apply it? */
     238           0 :             SetConfigOption("subtransaction_buffers", buf, PGC_POSTMASTER,
     239             :                             PGC_S_OVERRIDE);
     240             :     }
     241             :     Assert(subtransaction_buffers != 0);
     242             : 
     243        1902 :     SubTransCtl->PagePrecedes = SubTransPagePrecedes;
     244        1902 :     SimpleLruInit(SubTransCtl, "subtransaction", SUBTRANSShmemBuffers(), 0,
     245             :                   "pg_subtrans", LWTRANCHE_SUBTRANS_BUFFER,
     246             :                   LWTRANCHE_SUBTRANS_SLRU, SYNC_HANDLER_NONE, false);
     247             :     SlruPagePrecedesUnitTests(SubTransCtl, SUBTRANS_XACTS_PER_PAGE);
     248        1902 : }
     249             : 
     250             : /*
     251             :  * GUC check_hook for subtransaction_buffers
     252             :  */
     253             : bool
     254        3884 : check_subtrans_buffers(int *newval, void **extra, GucSource source)
     255             : {
     256        3884 :     return check_slru_buffers("subtransaction_buffers", newval);
     257             : }
     258             : 
     259             : /*
     260             :  * This func must be called ONCE on system install.  It creates
     261             :  * the initial SUBTRANS segment.  (The SUBTRANS directory is assumed to
     262             :  * have been created by the initdb shell script, and SUBTRANSShmemInit
     263             :  * must have been called already.)
     264             :  *
     265             :  * Note: it's not really necessary to create the initial segment now,
     266             :  * since slru.c would create it on first write anyway.  But we may as well
     267             :  * do it to be sure the directory is set up correctly.
     268             :  */
     269             : void
     270          90 : BootStrapSUBTRANS(void)
     271             : {
     272             :     int         slotno;
     273          90 :     LWLock     *lock = SimpleLruGetBankLock(SubTransCtl, 0);
     274             : 
     275          90 :     LWLockAcquire(lock, LW_EXCLUSIVE);
     276             : 
     277             :     /* Create and zero the first page of the subtrans log */
     278          90 :     slotno = ZeroSUBTRANSPage(0);
     279             : 
     280             :     /* Make sure it's written out */
     281          90 :     SimpleLruWritePage(SubTransCtl, slotno);
     282             :     Assert(!SubTransCtl->shared->page_dirty[slotno]);
     283             : 
     284          90 :     LWLockRelease(lock);
     285          90 : }
     286             : 
     287             : /*
     288             :  * Initialize (or reinitialize) a page of SUBTRANS to zeroes.
     289             :  *
     290             :  * The page is not actually written, just set up in shared memory.
     291             :  * The slot number of the new page is returned.
     292             :  *
     293             :  * Control lock must be held at entry, and will be held at exit.
     294             :  */
     295             : static int
     296    13814872 : ZeroSUBTRANSPage(int64 pageno)
     297             : {
     298    13814872 :     return SimpleLruZeroPage(SubTransCtl, pageno);
     299             : }
     300             : 
     301             : /*
     302             :  * This must be called ONCE during postmaster or standalone-backend startup,
     303             :  * after StartupXLOG has initialized TransamVariables->nextXid.
     304             :  *
     305             :  * oldestActiveXID is the oldest XID of any prepared transaction, or nextXid
     306             :  * if there are none.
     307             :  */
     308             : void
     309        1630 : StartupSUBTRANS(TransactionId oldestActiveXID)
     310             : {
     311             :     FullTransactionId nextXid;
     312             :     int64       startPage;
     313             :     int64       endPage;
     314        1630 :     LWLock     *prevlock = NULL;
     315             :     LWLock     *lock;
     316             : 
     317             :     /*
     318             :      * Since we don't expect pg_subtrans to be valid across crashes, we
     319             :      * initialize the currently-active page(s) to zeroes during startup.
     320             :      * Whenever we advance into a new page, ExtendSUBTRANS will likewise zero
     321             :      * the new page without regard to whatever was previously on disk.
     322             :      */
     323        1630 :     startPage = TransactionIdToPage(oldestActiveXID);
     324        1630 :     nextXid = TransamVariables->nextXid;
     325        1630 :     endPage = TransactionIdToPage(XidFromFullTransactionId(nextXid));
     326             : 
     327             :     for (;;)
     328             :     {
     329        1634 :         lock = SimpleLruGetBankLock(SubTransCtl, startPage);
     330        1634 :         if (prevlock != lock)
     331             :         {
     332        1634 :             if (prevlock)
     333           4 :                 LWLockRelease(prevlock);
     334        1634 :             LWLockAcquire(lock, LW_EXCLUSIVE);
     335        1634 :             prevlock = lock;
     336             :         }
     337             : 
     338        1634 :         (void) ZeroSUBTRANSPage(startPage);
     339        1634 :         if (startPage == endPage)
     340        1630 :             break;
     341             : 
     342           4 :         startPage++;
     343             :         /* must account for wraparound */
     344           4 :         if (startPage > TransactionIdToPage(MaxTransactionId))
     345           0 :             startPage = 0;
     346             :     }
     347             : 
     348        1630 :     LWLockRelease(lock);
     349        1630 : }
     350             : 
     351             : /*
     352             :  * Perform a checkpoint --- either during shutdown, or on-the-fly
     353             :  */
     354             : void
     355        2476 : CheckPointSUBTRANS(void)
     356             : {
     357             :     /*
     358             :      * Write dirty SUBTRANS pages to disk
     359             :      *
     360             :      * This is not actually necessary from a correctness point of view. We do
     361             :      * it merely to improve the odds that writing of dirty pages is done by
     362             :      * the checkpoint process and not by backends.
     363             :      */
     364             :     TRACE_POSTGRESQL_SUBTRANS_CHECKPOINT_START(true);
     365        2476 :     SimpleLruWriteAll(SubTransCtl, true);
     366             :     TRACE_POSTGRESQL_SUBTRANS_CHECKPOINT_DONE(true);
     367        2476 : }
     368             : 
     369             : 
     370             : /*
     371             :  * Make sure that SUBTRANS has room for a newly-allocated XID.
     372             :  *
     373             :  * NB: this is called while holding XidGenLock.  We want it to be very fast
     374             :  * most of the time; even when it's not so fast, no actual I/O need happen
     375             :  * unless we're forced to write out a dirty subtrans page to make room
     376             :  * in shared memory.
     377             :  */
     378             : void
     379    49019196 : ExtendSUBTRANS(TransactionId newestXact)
     380             : {
     381             :     int64       pageno;
     382             :     LWLock     *lock;
     383             : 
     384             :     /*
     385             :      * No work except at first XID of a page.  But beware: just after
     386             :      * wraparound, the first XID of page zero is FirstNormalTransactionId.
     387             :      */
     388    49019196 :     if (TransactionIdToEntry(newestXact) != 0 &&
     389             :         !TransactionIdEquals(newestXact, FirstNormalTransactionId))
     390    35206048 :         return;
     391             : 
     392    13813148 :     pageno = TransactionIdToPage(newestXact);
     393             : 
     394    13813148 :     lock = SimpleLruGetBankLock(SubTransCtl, pageno);
     395    13813148 :     LWLockAcquire(lock, LW_EXCLUSIVE);
     396             : 
     397             :     /* Zero the page */
     398    13813148 :     ZeroSUBTRANSPage(pageno);
     399             : 
     400    13813148 :     LWLockRelease(lock);
     401             : }
     402             : 
     403             : 
     404             : /*
     405             :  * Remove all SUBTRANS segments before the one holding the passed transaction ID
     406             :  *
     407             :  * oldestXact is the oldest TransactionXmin of any running transaction.  This
     408             :  * is called only during checkpoint.
     409             :  */
     410             : void
     411        2700 : TruncateSUBTRANS(TransactionId oldestXact)
     412             : {
     413             :     int64       cutoffPage;
     414             : 
     415             :     /*
     416             :      * The cutoff point is the start of the segment containing oldestXact. We
     417             :      * pass the *page* containing oldestXact to SimpleLruTruncate.  We step
     418             :      * back one transaction to avoid passing a cutoff page that hasn't been
     419             :      * created yet in the rare case that oldestXact would be the first item on
     420             :      * a page and oldestXact == next XID.  In that case, if we didn't subtract
     421             :      * one, we'd trigger SimpleLruTruncate's wraparound detection.
     422             :      */
     423        2700 :     TransactionIdRetreat(oldestXact);
     424        2418 :     cutoffPage = TransactionIdToPage(oldestXact);
     425             : 
     426        2418 :     SimpleLruTruncate(SubTransCtl, cutoffPage);
     427        2418 : }
     428             : 
     429             : 
     430             : /*
     431             :  * Decide whether a SUBTRANS page number is "older" for truncation purposes.
     432             :  * Analogous to CLOGPagePrecedes().
     433             :  */
     434             : static bool
     435      526588 : SubTransPagePrecedes(int64 page1, int64 page2)
     436             : {
     437             :     TransactionId xid1;
     438             :     TransactionId xid2;
     439             : 
     440      526588 :     xid1 = ((TransactionId) page1) * SUBTRANS_XACTS_PER_PAGE;
     441      526588 :     xid1 += FirstNormalTransactionId + 1;
     442      526588 :     xid2 = ((TransactionId) page2) * SUBTRANS_XACTS_PER_PAGE;
     443      526588 :     xid2 += FirstNormalTransactionId + 1;
     444             : 
     445      917686 :     return (TransactionIdPrecedes(xid1, xid2) &&
     446      391098 :             TransactionIdPrecedes(xid1, xid2 + SUBTRANS_XACTS_PER_PAGE - 1));
     447             : }

Generated by: LCOV version 1.14