Age Owner TLA 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-2023, 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 "pg_trace.h"
35 : #include "utils/snapmgr.h"
36 :
37 :
38 : /*
39 : * Defines for SubTrans page sizes. A page is the same BLCKSZ as is used
40 : * everywhere else in Postgres.
41 : *
42 : * Note: because TransactionIds are 32 bits and wrap around at 0xFFFFFFFF,
43 : * SubTrans page numbering also wraps around at
44 : * 0xFFFFFFFF/SUBTRANS_XACTS_PER_PAGE, and segment numbering at
45 : * 0xFFFFFFFF/SUBTRANS_XACTS_PER_PAGE/SLRU_PAGES_PER_SEGMENT. We need take no
46 : * explicit notice of that fact in this module, except when comparing segment
47 : * and page numbers in TruncateSUBTRANS (see SubTransPagePrecedes) and zeroing
48 : * them in StartupSUBTRANS.
49 : */
50 :
51 : /* We need four bytes per xact */
52 : #define SUBTRANS_XACTS_PER_PAGE (BLCKSZ / sizeof(TransactionId))
53 :
54 : #define TransactionIdToPage(xid) ((xid) / (TransactionId) SUBTRANS_XACTS_PER_PAGE)
55 : #define TransactionIdToEntry(xid) ((xid) % (TransactionId) SUBTRANS_XACTS_PER_PAGE)
56 :
57 :
58 : /*
59 : * Link to shared-memory data structures for SUBTRANS control
60 : */
61 : static SlruCtlData SubTransCtlData;
62 :
63 : #define SubTransCtl (&SubTransCtlData)
64 :
65 :
66 : static int ZeroSUBTRANSPage(int pageno);
67 : static bool SubTransPagePrecedes(int page1, int page2);
68 :
69 :
70 : /*
71 : * Record the parent of a subtransaction in the subtrans log.
72 : */
73 : void
2173 simon 74 CBC 5103 : SubTransSetParent(TransactionId xid, TransactionId parent)
75 : {
6856 tgl 76 5103 : int pageno = TransactionIdToPage(xid);
77 5103 : int entryno = TransactionIdToEntry(xid);
78 : int slotno;
79 : TransactionId *ptr;
80 :
4859 simon 81 5103 : Assert(TransactionIdIsValid(parent));
2173 82 5103 : Assert(TransactionIdFollows(xid, parent));
83 :
1059 tgl 84 5103 : LWLockAcquire(SubtransSLRULock, LW_EXCLUSIVE);
85 :
5730 86 5103 : slotno = SimpleLruReadPage(SubTransCtl, pageno, true, xid);
6803 87 5103 : ptr = (TransactionId *) SubTransCtl->shared->page_buffer[slotno];
6856 88 5103 : ptr += entryno;
89 :
90 : /*
91 : * It's possible we'll try to set the parent xid multiple times but we
92 : * shouldn't ever be changing the xid from one valid xid to another valid
93 : * xid, which would corrupt the data structure.
94 : */
2173 simon 95 5103 : if (*ptr != parent)
96 : {
97 4847 : Assert(*ptr == InvalidTransactionId);
98 4847 : *ptr = parent;
99 4847 : SubTransCtl->shared->page_dirty[slotno] = true;
100 : }
101 :
1059 tgl 102 5103 : LWLockRelease(SubtransSLRULock);
6856 103 5103 : }
104 :
105 : /*
106 : * Interrogate the parent of a transaction in the subtrans log.
107 : */
108 : TransactionId
109 3079 : SubTransGetParent(TransactionId xid)
110 : {
111 3079 : int pageno = TransactionIdToPage(xid);
112 3079 : int entryno = TransactionIdToEntry(xid);
113 : int slotno;
114 : TransactionId *ptr;
115 : TransactionId parent;
116 :
117 : /* Can't ask about stuff that might not be around anymore */
6779 118 3079 : Assert(TransactionIdFollowsOrEquals(xid, TransactionXmin));
119 :
120 : /* Bootstrap and frozen XIDs have no parent */
6856 121 3079 : if (!TransactionIdIsNormal(xid))
6856 tgl 122 UBC 0 : return InvalidTransactionId;
123 :
124 : /* lock is acquired by SimpleLruReadPage_ReadOnly */
125 :
6333 tgl 126 CBC 3079 : slotno = SimpleLruReadPage_ReadOnly(SubTransCtl, pageno, xid);
6803 127 3079 : ptr = (TransactionId *) SubTransCtl->shared->page_buffer[slotno];
6856 128 3079 : ptr += entryno;
129 :
130 3079 : parent = *ptr;
131 :
1059 132 3079 : LWLockRelease(SubtransSLRULock);
133 :
6856 134 3079 : return parent;
135 : }
136 :
137 : /*
138 : * SubTransGetTopmostTransaction
139 : *
140 : * Returns the topmost transaction of the given transaction id.
141 : *
142 : * Because we cannot look back further than TransactionXmin, it is possible
143 : * that this function will lie and return an intermediate subtransaction ID
144 : * instead of the true topmost parent ID. This is OK, because in practice
145 : * we only care about detecting whether the topmost parent is still running
146 : * or is part of a current snapshot's list of still-running transactions.
147 : * Therefore, any XID before TransactionXmin is as good as any other.
148 : */
149 : TransactionId
150 1076 : SubTransGetTopmostTransaction(TransactionId xid)
151 : {
152 1076 : TransactionId parentXid = xid,
6797 bruce 153 1076 : previousXid = xid;
154 :
155 : /* Can't ask about stuff that might not be around anymore */
6779 tgl 156 1076 : Assert(TransactionIdFollowsOrEquals(xid, TransactionXmin));
157 :
6856 158 4155 : while (TransactionIdIsValid(parentXid))
159 : {
160 3079 : previousXid = parentXid;
6779 161 3079 : if (TransactionIdPrecedes(parentXid, TransactionXmin))
6804 tgl 162 UBC 0 : break;
6856 tgl 163 CBC 3079 : parentXid = SubTransGetParent(parentXid);
164 :
165 : /*
166 : * By convention the parent xid gets allocated first, so should always
167 : * precede the child xid. Anything else points to a corrupted data
168 : * structure that could lead to an infinite loop, so exit.
169 : */
2173 simon 170 3079 : if (!TransactionIdPrecedes(parentXid, previousXid))
2173 simon 171 UBC 0 : elog(ERROR, "pg_subtrans contains invalid entry: xid %u points to parent xid %u",
172 : previousXid, parentXid);
173 : }
174 :
6856 tgl 175 CBC 1076 : Assert(TransactionIdIsValid(previousXid));
176 :
177 1076 : return previousXid;
178 : }
179 :
180 :
181 : /*
182 : * Initialization of shared memory for SUBTRANS
183 : */
184 : Size
185 2738 : SUBTRANSShmemSize(void)
186 : {
5730 187 2738 : return SimpleLruShmemSize(NUM_SUBTRANS_BUFFERS, 0);
188 : }
189 :
190 : void
6856 191 1826 : SUBTRANSShmemInit(void)
192 : {
193 1826 : SubTransCtl->PagePrecedes = SubTransPagePrecedes;
1059 194 1826 : SimpleLruInit(SubTransCtl, "Subtrans", NUM_SUBTRANS_BUFFERS, 0,
195 1826 : SubtransSLRULock, "pg_subtrans",
196 : LWTRANCHE_SUBTRANS_BUFFER, SYNC_HANDLER_NONE);
813 noah 197 1826 : SlruPagePrecedesUnitTests(SubTransCtl, SUBTRANS_XACTS_PER_PAGE);
6856 tgl 198 1826 : }
199 :
200 : /*
201 : * This func must be called ONCE on system install. It creates
202 : * the initial SUBTRANS segment. (The SUBTRANS directory is assumed to
203 : * have been created by the initdb shell script, and SUBTRANSShmemInit
204 : * must have been called already.)
205 : *
206 : * Note: it's not really necessary to create the initial segment now,
207 : * since slru.c would create it on first write anyway. But we may as well
208 : * do it to be sure the directory is set up correctly.
209 : */
210 : void
211 305 : BootStrapSUBTRANS(void)
212 : {
213 : int slotno;
214 :
1059 215 305 : LWLockAcquire(SubtransSLRULock, LW_EXCLUSIVE);
216 :
217 : /* Create and zero the first page of the subtrans log */
6803 218 305 : slotno = ZeroSUBTRANSPage(0);
219 :
220 : /* Make sure it's written out */
4483 alvherre 221 305 : SimpleLruWritePage(SubTransCtl, slotno);
6364 tgl 222 305 : Assert(!SubTransCtl->shared->page_dirty[slotno]);
223 :
1059 224 305 : LWLockRelease(SubtransSLRULock);
6856 225 305 : }
226 :
227 : /*
228 : * Initialize (or reinitialize) a page of SUBTRANS to zeroes.
229 : *
230 : * The page is not actually written, just set up in shared memory.
231 : * The slot number of the new page is returned.
232 : *
233 : * Control lock must be held at entry, and will be held at exit.
234 : */
235 : static int
6803 236 1822 : ZeroSUBTRANSPage(int pageno)
237 : {
238 1822 : return SimpleLruZeroPage(SubTransCtl, pageno);
239 : }
240 :
241 : /*
242 : * This must be called ONCE during postmaster or standalone-backend startup,
243 : * after StartupXLOG has initialized ShmemVariableCache->nextXid.
244 : *
245 : * oldestActiveXID is the oldest XID of any prepared transaction, or nextXid
246 : * if there are none.
247 : */
248 : void
6505 249 1174 : StartupSUBTRANS(TransactionId oldestActiveXID)
250 : {
251 : FullTransactionId nextXid;
252 : int startPage;
253 : int endPage;
254 :
255 : /*
256 : * Since we don't expect pg_subtrans to be valid across crashes, we
257 : * initialize the currently-active page(s) to zeroes during startup.
258 : * Whenever we advance into a new page, ExtendSUBTRANS will likewise zero
259 : * the new page without regard to whatever was previously on disk.
260 : */
1059 261 1174 : LWLockAcquire(SubtransSLRULock, LW_EXCLUSIVE);
262 :
6505 263 1174 : startPage = TransactionIdToPage(oldestActiveXID);
971 andres 264 1174 : nextXid = ShmemVariableCache->nextXid;
265 1174 : endPage = TransactionIdToPage(XidFromFullTransactionId(nextXid));
266 :
6505 tgl 267 1174 : while (startPage != endPage)
268 : {
6505 tgl 269 UBC 0 : (void) ZeroSUBTRANSPage(startPage);
270 0 : startPage++;
271 : /* must account for wraparound */
2606 simon 272 0 : if (startPage > TransactionIdToPage(MaxTransactionId))
2495 rhaas 273 0 : startPage = 0;
274 : }
6803 tgl 275 CBC 1174 : (void) ZeroSUBTRANSPage(startPage);
276 :
1059 277 1174 : LWLockRelease(SubtransSLRULock);
6856 278 1174 : }
279 :
280 : /*
281 : * Perform a checkpoint --- either during shutdown, or on-the-fly
282 : */
283 : void
284 2363 : CheckPointSUBTRANS(void)
285 : {
286 : /*
287 : * Write dirty SUBTRANS pages to disk
288 : *
289 : * This is not actually necessary from a correctness point of view. We do
290 : * it merely to improve the odds that writing of dirty pages is done by
291 : * the checkpoint process and not by backends.
292 : */
293 : TRACE_POSTGRESQL_SUBTRANS_CHECKPOINT_START(true);
926 tmunro 294 2363 : SimpleLruWriteAll(SubTransCtl, true);
295 : TRACE_POSTGRESQL_SUBTRANS_CHECKPOINT_DONE(true);
6856 tgl 296 2363 : }
297 :
298 :
299 : /*
300 : * Make sure that SUBTRANS has room for a newly-allocated XID.
301 : *
302 : * NB: this is called while holding XidGenLock. We want it to be very fast
303 : * most of the time; even when it's not so fast, no actual I/O need happen
304 : * unless we're forced to write out a dirty subtrans page to make room
305 : * in shared memory.
306 : */
307 : void
308 320834 : ExtendSUBTRANS(TransactionId newestXact)
309 : {
310 : int pageno;
311 :
312 : /*
313 : * No work except at first XID of a page. But beware: just after
314 : * wraparound, the first XID of page zero is FirstNormalTransactionId.
315 : */
316 320834 : if (TransactionIdToEntry(newestXact) != 0 &&
317 : !TransactionIdEquals(newestXact, FirstNormalTransactionId))
318 320491 : return;
319 :
320 343 : pageno = TransactionIdToPage(newestXact);
321 :
1059 322 343 : LWLockAcquire(SubtransSLRULock, LW_EXCLUSIVE);
323 :
324 : /* Zero the page */
6803 325 343 : ZeroSUBTRANSPage(pageno);
326 :
1059 327 343 : LWLockRelease(SubtransSLRULock);
328 : }
329 :
330 :
331 : /*
332 : * Remove all SUBTRANS segments before the one holding the passed transaction ID
333 : *
334 : * oldestXact is the oldest TransactionXmin of any running transaction. This
335 : * is called only during checkpoint.
336 : */
337 : void
6856 338 2336 : TruncateSUBTRANS(TransactionId oldestXact)
339 : {
340 : int cutoffPage;
341 :
342 : /*
343 : * The cutoff point is the start of the segment containing oldestXact. We
344 : * pass the *page* containing oldestXact to SimpleLruTruncate. We step
345 : * back one transaction to avoid passing a cutoff page that hasn't been
346 : * created yet in the rare case that oldestXact would be the first item on
347 : * a page and oldestXact == next XID. In that case, if we didn't subtract
348 : * one, we'd trigger SimpleLruTruncate's wraparound detection.
349 : */
2817 heikki.linnakangas 350 3257 : TransactionIdRetreat(oldestXact);
6856 tgl 351 2336 : cutoffPage = TransactionIdToPage(oldestXact);
352 :
353 2336 : SimpleLruTruncate(SubTransCtl, cutoffPage);
354 2336 : }
355 :
356 :
357 : /*
358 : * Decide whether a SUBTRANS page number is "older" for truncation purposes.
359 : * Analogous to CLOGPagePrecedes().
360 : */
361 : static bool
362 78322 : SubTransPagePrecedes(int page1, int page2)
363 : {
364 : TransactionId xid1;
365 : TransactionId xid2;
366 :
367 78322 : xid1 = ((TransactionId) page1) * SUBTRANS_XACTS_PER_PAGE;
813 noah 368 78322 : xid1 += FirstNormalTransactionId + 1;
6856 tgl 369 78322 : xid2 = ((TransactionId) page2) * SUBTRANS_XACTS_PER_PAGE;
813 noah 370 78322 : xid2 += FirstNormalTransactionId + 1;
371 :
372 125889 : return (TransactionIdPrecedes(xid1, xid2) &&
373 47567 : TransactionIdPrecedes(xid1, xid2 + SUBTRANS_XACTS_PER_PAGE - 1));
374 : }
|