/*------------------------------------------------------------------------- * * subtrans.c * PostgreSQL subtrans-log manager * * FIXME -- describir * * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * $PostgreSQL$ * *------------------------------------------------------------------------- */ #include "postgres.h" #include #include #include #include #include #include "access/subtrans.h" #include "access/slru.h" #include "storage/lwlock.h" #include "miscadmin.h" /* * Defines for SubTrans page and segment sizes. A page is the same BLCKSZ * as is used everywhere else in Postgres. * * Note: because TransactionIds are 32 bits and wrap around at 0xFFFFFFFF, * SubTrans page numbering also wraps around at 0xFFFFFFFF/SUBTRANS_XACTS_PER_PAGE, * and segment numbering at 0xFFFFFFFF/SUBTRANS_XACTS_PER_PAGE/SLRU_SEGMENTS_PER_PAGE. * We need take no explicit notice of that fact in this module, except when * comparing segment and page numbers in TruncateSubTrans (see SubTransPagePrecedes). */ /* We need four bytes per xact */ #define SUBTRANS_XACTS_PER_PAGE (BLCKSZ / sizeof(TransactionId)) #define TransactionIdToPage(xid) ((xid) / (TransactionId) SUBTRANS_XACTS_PER_PAGE) #define TransactionIdToByte(xid) (sizeof(TransactionId) * ((xid) % (TransactionId) SUBTRANS_XACTS_PER_PAGE)) /*---------- * Shared-memory data structures for SUBTRANS control * * XLOG interactions: this module generates an XLOG record whenever a new * SUBTRANS page is initialized to zeroes. Other writes of SUBTRANS come from * recording of transaction commit or abort in xact.c, which generates its * own XLOG records for these events and will re-perform the status update * on redo; so we need make no additional XLOG entry here. Also, the XLOG * is guaranteed flushed through the XLOG commit record before we are called * to log a commit, so the WAL rule "write xlog before data" is satisfied * automatically for commits, and we don't really care for aborts. Therefore, * we don't need to mark XLOG pages with LSN information; we have enough * synchronization already. *---------- */ static SlruCtlData SubTransCtlData; static SlruCtl SubTransCtl = &SubTransCtlData; static int ZeroSubTransPage(int pageno, bool writeXlog); static bool SubTransPagePrecedes(int page1, int page2); static void WriteZeroPageXlogRec(int pageno); /* * Record the parent of a subtransaction in the subtrans log. */ void SubTransSetParent(TransactionId xid, TransactionId parent) { int pageno = TransactionIdToPage(xid); int byteno = TransactionIdToByte(xid); char *byteptr; LWLockAcquire(SubTransCtl->locks->ControlLock, LW_EXCLUSIVE); byteptr = SimpleLruReadPage(SubTransCtl, pageno, xid, true); byteptr += byteno; /* XXX We should Assert() the previous parent Xid is zero */ memcpy(byteptr, (void *)&parent, sizeof(TransactionId)); /* ...->page_status[slotno] = SLRU_PAGE_DIRTY; already done */ LWLockRelease(SubTransCtl->locks->ControlLock); } /* * Interrogate the parent of a transaction in the subtrans log. */ TransactionId SubTransGetParent(TransactionId xid) { int pageno = TransactionIdToPage(xid); int byteno = TransactionIdToByte(xid); char *byteptr; TransactionId parent; LWLockAcquire(SubTransCtl->locks->ControlLock, LW_EXCLUSIVE); byteptr = SimpleLruReadPage(SubTransCtl, pageno, xid, false); byteptr += byteno; memcpy((void *)&parent, byteptr, sizeof(TransactionId)); LWLockRelease(SubTransCtl->locks->ControlLock); return parent; } /* * Initialization of shared memory for Subtrans */ int SubTransShmemSize(void) { return SimpleLruShmemSize(); } void SubTransShmemInit(void) { SimpleLruInit(SubTransCtl, "SUBTRANS Ctl", "pg_subtrans"); SubTransCtl->PagePrecedes = SubTransPagePrecedes; } /* * This func must be called ONCE on system install. It creates * the initial SubTrans segment. (The SubTrans directory is assumed to * have been created by initdb, and SubTransShmemInit must have been called * already.) */ void BootStrapSubTrans(void) { int slotno; LWLockAcquire(SubTransCtl->locks->ControlLock, LW_EXCLUSIVE); /* Create and zero the first page of the commit log */ slotno = ZeroSubTransPage(0, false); /* Make sure it's written out */ SimpleLruWritePage(SubTransCtl, slotno); LWLockRelease(SubTransCtl->locks->ControlLock); } /* * Initialize (or reinitialize) a page of SubTrans to zeroes. * If writeXlog is TRUE, also emit an XLOG record saying we did this. * * The page is not actually written, just set up in shared memory. * The slot number of the new page is returned. * * Control lock must be held at entry, and will be held at exit. */ static int ZeroSubTransPage(int pageno, bool writeXlog) { int slotno = SimpleLruZeroPage(SubTransCtl, pageno); if (writeXlog) WriteZeroPageXlogRec(pageno); return slotno; } /* * This must be called ONCE during postmaster or standalone-backend startup, * after StartupXLOG has initialized ShmemVariableCache->nextXid. */ void StartupSubTrans(void) { /* * Initialize our idea of the latest page number. */ SimpleLruSetLatestPage(SubTransCtl, TransactionIdToPage(ShmemVariableCache->nextXid)); } /* * This must be called ONCE during postmaster or standalone-backend shutdown */ void ShutdownSubTrans(void) { SimpleLruFlush(SubTransCtl, false); } /* * Perform a checkpoint --- either during shutdown, or on-the-fly */ void CheckPointSubTrans(void) { SimpleLruFlush(SubTransCtl, true); } /* * Make sure that SubTrans has room for a newly-allocated XID. * * NB: this is called while holding XidGenLock. We want it to be very fast * most of the time; even when it's not so fast, no actual I/O need happen * unless we're forced to write out a dirty subtrans or xlog page to make room * in shared memory. */ void ExtendSubTrans(TransactionId newestXact) { int pageno; /* * No work except at first XID of a page. But beware: just after * wraparound, the first XID of page zero is FirstNormalTransactionId. */ if (TransactionIdToByte(newestXact) != 0 && !TransactionIdEquals(newestXact, FirstNormalTransactionId)) return; pageno = TransactionIdToPage(newestXact); LWLockAcquire(SubTransCtl->locks->ControlLock, LW_EXCLUSIVE); /* Zero the page and make an XLOG entry about it */ ZeroSubTransPage(pageno, true); LWLockRelease(SubTransCtl->locks->ControlLock); } /* * Remove all SubTrans segments before the one holding the passed transaction ID * * When this is called, we know that the database logically contains no * reference to transaction IDs older than oldestXact. However, we must * not truncate the SubTrans until we have performed a checkpoint, to ensure * that no such references remain on disk either; else a crash just after * the truncation might leave us with a problem. Since SubTrans segments hold * a large number of transactions, the opportunity to actually remove a * segment is fairly rare, and so it seems best not to do the checkpoint * unless we have confirmed that there is a removable segment. Therefore * we issue the checkpoint command here, not in higher-level code as might * seem cleaner. */ void TruncateSubTrans(TransactionId oldestXact) { int cutoffPage; /* * The cutoff point is the start of the segment containing oldestXact. * We pass the *page* containing oldestXact to SimpleLruTruncate. */ cutoffPage = TransactionIdToPage(oldestXact); SimpleLruTruncate(SubTransCtl, cutoffPage); } /* * Decide which of two SubTrans page numbers is "older" for truncation purposes. * * We need to use comparison of TransactionIds here in order to do the right * thing with wraparound XID arithmetic. However, if we are asked about * page number zero, we don't want to hand InvalidTransactionId to * TransactionIdPrecedes: it'll get weird about permanent xact IDs. So, * offset both xids by FirstNormalTransactionId to avoid that. */ static bool SubTransPagePrecedes(int page1, int page2) { TransactionId xid1; TransactionId xid2; xid1 = ((TransactionId) page1) * SUBTRANS_XACTS_PER_PAGE; xid1 += FirstNormalTransactionId; xid2 = ((TransactionId) page2) * SUBTRANS_XACTS_PER_PAGE; xid2 += FirstNormalTransactionId; return TransactionIdPrecedes(xid1, xid2); } /* * Write a ZEROPAGE xlog record * * Note: xlog record is marked as outside transaction control, since we * want it to be redone whether the invoking transaction commits or not. * (Besides which, this is normally done just before entering a transaction.) */ static void WriteZeroPageXlogRec(int pageno) { XLogRecData rdata; rdata.buffer = InvalidBuffer; rdata.data = (char *) (&pageno); rdata.len = sizeof(int); rdata.next = NULL; (void) XLogInsert(RM_SUBTRANS_ID, SUBTRANS_ZEROPAGE | XLOG_NO_TRAN, &rdata); } /* * SUBTRANS resource manager's routines */ void subtrans_redo(XLogRecPtr lsn, XLogRecord *record) { uint8 info = record->xl_info & ~XLR_INFO_MASK; if (info == SUBTRANS_ZEROPAGE) { int pageno; int slotno; memcpy(&pageno, XLogRecGetData(record), sizeof(int)); LWLockAcquire(SubTransCtl->locks->ControlLock, LW_EXCLUSIVE); slotno = ZeroSubTransPage(pageno, false); SimpleLruWritePage(SubTransCtl, slotno); LWLockRelease(SubTransCtl->locks->ControlLock); } } void subtrans_undo(XLogRecPtr lsn, XLogRecord *record) { } void subtrans_desc(char *buf, uint8 xl_info, char *rec) { uint8 info = xl_info & ~XLR_INFO_MASK; if (info == SUBTRANS_ZEROPAGE) { int pageno; memcpy(&pageno, rec, sizeof(int)); sprintf(buf + strlen(buf), "zeropage: %d", pageno); } else strcat(buf, "UNKNOWN"); }