Age Owner Branch data 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-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
647 akorotkov@postgresql 61 :CBC 12177 : TransactionIdToPage(TransactionId xid)
62 : : {
63 : 12177 : 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
3054 simon@2ndQuadrant.co 84 : 5574 : SubTransSetParent(TransactionId xid, TransactionId parent)
85 : : {
647 akorotkov@postgresql 86 : 5574 : int64 pageno = TransactionIdToPage(xid);
7737 tgl@sss.pgh.pa.us 87 : 5574 : int entryno = TransactionIdToEntry(xid);
88 : : int slotno;
89 : : LWLock *lock;
90 : : TransactionId *ptr;
91 : :
5740 simon@2ndQuadrant.co 92 [ - + ]: 5574 : Assert(TransactionIdIsValid(parent));
3054 93 [ - + ]: 5574 : Assert(TransactionIdFollows(xid, parent));
94 : :
556 alvherre@alvh.no-ip. 95 : 5574 : lock = SimpleLruGetBankLock(SubTransCtl, pageno);
96 : 5574 : LWLockAcquire(lock, LW_EXCLUSIVE);
97 : :
6611 tgl@sss.pgh.pa.us 98 : 5574 : slotno = SimpleLruReadPage(SubTransCtl, pageno, true, xid);
7684 99 : 5574 : ptr = (TransactionId *) SubTransCtl->shared->page_buffer[slotno];
7737 100 : 5574 : 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 : : */
3054 simon@2ndQuadrant.co 107 [ + + ]: 5574 : if (*ptr != parent)
108 : : {
109 [ - + ]: 4988 : Assert(*ptr == InvalidTransactionId);
110 : 4988 : *ptr = parent;
111 : 4988 : SubTransCtl->shared->page_dirty[slotno] = true;
112 : : }
113 : :
556 alvherre@alvh.no-ip. 114 : 5574 : LWLockRelease(lock);
7737 tgl@sss.pgh.pa.us 115 : 5574 : }
116 : :
117 : : /*
118 : : * Interrogate the parent of a transaction in the subtrans log.
119 : : */
120 : : TransactionId
121 : 3087 : SubTransGetParent(TransactionId xid)
122 : : {
647 akorotkov@postgresql 123 : 3087 : int64 pageno = TransactionIdToPage(xid);
7737 tgl@sss.pgh.pa.us 124 : 3087 : 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 */
7660 130 [ - + ]: 3087 : Assert(TransactionIdFollowsOrEquals(xid, TransactionXmin));
131 : :
132 : : /* Bootstrap and frozen XIDs have no parent */
7737 133 [ - + ]: 3087 : if (!TransactionIdIsNormal(xid))
7737 tgl@sss.pgh.pa.us 134 :UBC 0 : return InvalidTransactionId;
135 : :
136 : : /* lock is acquired by SimpleLruReadPage_ReadOnly */
137 : :
7214 tgl@sss.pgh.pa.us 138 :CBC 3087 : slotno = SimpleLruReadPage_ReadOnly(SubTransCtl, pageno, xid);
7684 139 : 3087 : ptr = (TransactionId *) SubTransCtl->shared->page_buffer[slotno];
7737 140 : 3087 : ptr += entryno;
141 : :
142 : 3087 : parent = *ptr;
143 : :
556 alvherre@alvh.no-ip. 144 : 3087 : LWLockRelease(SimpleLruGetBankLock(SubTransCtl, pageno));
145 : :
7737 tgl@sss.pgh.pa.us 146 : 3087 : 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 : 1081 : SubTransGetTopmostTransaction(TransactionId xid)
163 : : {
164 : 1081 : TransactionId parentXid = xid,
7678 bruce@momjian.us 165 : 1081 : previousXid = xid;
166 : :
167 : : /* Can't ask about stuff that might not be around anymore */
7660 tgl@sss.pgh.pa.us 168 [ - + ]: 1081 : Assert(TransactionIdFollowsOrEquals(xid, TransactionXmin));
169 : :
7737 170 [ + + ]: 4168 : while (TransactionIdIsValid(parentXid))
171 : : {
172 : 3087 : previousXid = parentXid;
7660 173 [ - + ]: 3087 : if (TransactionIdPrecedes(parentXid, TransactionXmin))
7685 tgl@sss.pgh.pa.us 174 :UBC 0 : break;
7737 tgl@sss.pgh.pa.us 175 :CBC 3087 : 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 : : */
3054 simon@2ndQuadrant.co 182 [ - + ]: 3087 : if (!TransactionIdPrecedes(parentXid, previousXid))
3054 simon@2ndQuadrant.co 183 [ # # ]:UBC 0 : elog(ERROR, "pg_subtrans contains invalid entry: xid %u points to parent xid %u",
184 : : previousXid, parentXid);
185 : : }
186 : :
7737 tgl@sss.pgh.pa.us 187 [ - + ]:CBC 1081 : Assert(TransactionIdIsValid(previousXid));
188 : :
189 : 1081 : 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
556 alvherre@alvh.no-ip. 200 : 3956 : SUBTRANSShmemBuffers(void)
201 : : {
202 : : /* auto-tune based on shared buffers */
203 [ + + ]: 3956 : if (subtransaction_buffers == 0)
204 : 2909 : return SimpleLruAutotuneBuffers(512, 1024);
205 : :
206 [ + - ]: 1047 : return Min(Max(16, subtransaction_buffers), SLRU_MAX_ALLOWED_BUFFERS);
207 : : }
208 : :
209 : : /*
210 : : * Initialization of shared memory for SUBTRANS
211 : : */
212 : : Size
7737 tgl@sss.pgh.pa.us 213 : 1909 : SUBTRANSShmemSize(void)
214 : : {
556 alvherre@alvh.no-ip. 215 : 1909 : return SimpleLruShmemSize(SUBTRANSShmemBuffers(), 0);
216 : : }
217 : :
218 : : void
7737 tgl@sss.pgh.pa.us 219 : 1029 : SUBTRANSShmemInit(void)
220 : : {
221 : : /* If auto-tuning is requested, now is the time to do it */
556 alvherre@alvh.no-ip. 222 [ + + ]: 1029 : if (subtransaction_buffers == 0)
223 : : {
224 : : char buf[32];
225 : :
226 : 1018 : snprintf(buf, sizeof(buf), "%d", SUBTRANSShmemBuffers());
227 : 1018 : 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 [ - + ]: 1018 : if (subtransaction_buffers == 0) /* failed to apply it? */
556 alvherre@alvh.no-ip. 237 :UBC 0 : SetConfigOption("subtransaction_buffers", buf, PGC_POSTMASTER,
238 : : PGC_S_OVERRIDE);
239 : : }
556 alvherre@alvh.no-ip. 240 [ - + ]:CBC 1029 : Assert(subtransaction_buffers != 0);
241 : :
7737 tgl@sss.pgh.pa.us 242 : 1029 : SubTransCtl->PagePrecedes = SubTransPagePrecedes;
556 alvherre@alvh.no-ip. 243 : 1029 : SimpleLruInit(SubTransCtl, "subtransaction", SUBTRANSShmemBuffers(), 0,
244 : : "pg_subtrans", LWTRANCHE_SUBTRANS_BUFFER,
245 : : LWTRANCHE_SUBTRANS_SLRU, SYNC_HANDLER_NONE, false);
1694 noah@leadboat.com 246 : 1029 : SlruPagePrecedesUnitTests(SubTransCtl, SUBTRANS_XACTS_PER_PAGE);
7737 tgl@sss.pgh.pa.us 247 : 1029 : }
248 : :
249 : : /*
250 : : * GUC check_hook for subtransaction_buffers
251 : : */
252 : : bool
556 alvherre@alvh.no-ip. 253 : 2110 : check_subtrans_buffers(int *newval, void **extra, GucSource source)
254 : : {
255 : 2110 : 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
7737 tgl@sss.pgh.pa.us 269 : 50 : BootStrapSUBTRANS(void)
270 : : {
271 : : /* Zero the initial page and flush it to disk */
61 alvherre@kurilemu.de 272 :GNC 50 : SimpleLruZeroAndWritePage(SubTransCtl, 0);
7737 tgl@sss.pgh.pa.us 273 :GIC 50 : }
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
7386 tgl@sss.pgh.pa.us 283 :CBC 885 : StartupSUBTRANS(TransactionId oldestActiveXID)
284 : : {
285 : : FullTransactionId nextXid;
286 : : int64 startPage;
287 : : int64 endPage;
550 alvherre@alvh.no-ip. 288 : 885 : 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 : : */
7386 tgl@sss.pgh.pa.us 297 : 885 : startPage = TransactionIdToPage(oldestActiveXID);
638 heikki.linnakangas@i 298 : 885 : nextXid = TransamVariables->nextXid;
1852 andres@anarazel.de 299 : 885 : endPage = TransactionIdToPage(XidFromFullTransactionId(nextXid));
300 : :
301 : : for (;;)
302 : : {
556 alvherre@alvh.no-ip. 303 : 887 : lock = SimpleLruGetBankLock(SubTransCtl, startPage);
304 [ + - ]: 887 : if (prevlock != lock)
305 : : {
550 306 [ + + ]: 887 : if (prevlock)
307 : 2 : LWLockRelease(prevlock);
556 308 : 887 : LWLockAcquire(lock, LW_EXCLUSIVE);
309 : 887 : prevlock = lock;
310 : : }
311 : :
61 alvherre@kurilemu.de 312 :GNC 887 : (void) SimpleLruZeroPage(SubTransCtl, startPage);
550 alvherre@alvh.no-ip. 313 [ + + ]:CBC 887 : if (startPage == endPage)
314 : 885 : break;
315 : :
7386 tgl@sss.pgh.pa.us 316 : 2 : startPage++;
317 : : /* must account for wraparound */
3487 simon@2ndQuadrant.co 318 [ - + ]: 2 : if (startPage > TransactionIdToPage(MaxTransactionId))
3376 rhaas@postgresql.org 319 :UBC 0 : startPage = 0;
320 : : }
321 : :
556 alvherre@alvh.no-ip. 322 :CBC 885 : LWLockRelease(lock);
7737 tgl@sss.pgh.pa.us 323 : 885 : }
324 : :
325 : : /*
326 : : * Perform a checkpoint --- either during shutdown, or on-the-fly
327 : : */
328 : : void
329 : 1677 : 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);
1807 tmunro@postgresql.or 339 : 1677 : SimpleLruWriteAll(SubTransCtl, true);
340 : : TRACE_POSTGRESQL_SUBTRANS_CHECKPOINT_DONE(true);
7737 tgl@sss.pgh.pa.us 341 : 1677 : }
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 : 154095 : 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 [ + + + + ]: 154095 : if (TransactionIdToEntry(newestXact) != 0 &&
363 : : !TransactionIdEquals(newestXact, FirstNormalTransactionId))
364 : 154000 : return;
365 : :
366 : 95 : pageno = TransactionIdToPage(newestXact);
367 : :
556 alvherre@alvh.no-ip. 368 : 95 : lock = SimpleLruGetBankLock(SubTransCtl, pageno);
369 : 95 : LWLockAcquire(lock, LW_EXCLUSIVE);
370 : :
371 : : /* Zero the page */
61 alvherre@kurilemu.de 372 :GNC 95 : SimpleLruZeroPage(SubTransCtl, pageno);
373 : :
556 alvherre@alvh.no-ip. 374 :CBC 95 : 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
7737 tgl@sss.pgh.pa.us 385 : 1649 : 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 : : */
3698 heikki.linnakangas@i 397 [ + + ]: 1805 : TransactionIdRetreat(oldestXact);
7737 tgl@sss.pgh.pa.us 398 : 1649 : cutoffPage = TransactionIdToPage(oldestXact);
399 : :
400 : 1649 : SimpleLruTruncate(SubTransCtl, cutoffPage);
401 : 1649 : }
402 : :
403 : :
404 : : /*
405 : : * Decide whether a SUBTRANS page number is "older" for truncation purposes.
406 : : * Analogous to CLOGPagePrecedes().
407 : : */
408 : : static bool
647 akorotkov@postgresql 409 : 45517 : SubTransPagePrecedes(int64 page1, int64 page2)
410 : : {
411 : : TransactionId xid1;
412 : : TransactionId xid2;
413 : :
7737 tgl@sss.pgh.pa.us 414 : 45517 : xid1 = ((TransactionId) page1) * SUBTRANS_XACTS_PER_PAGE;
1694 noah@leadboat.com 415 : 45517 : xid1 += FirstNormalTransactionId + 1;
7737 tgl@sss.pgh.pa.us 416 : 45517 : xid2 = ((TransactionId) page2) * SUBTRANS_XACTS_PER_PAGE;
1694 noah@leadboat.com 417 : 45517 : xid2 += FirstNormalTransactionId + 1;
418 : :
419 [ + + + + ]: 72686 : return (TransactionIdPrecedes(xid1, xid2) &&
420 : 27169 : TransactionIdPrecedes(xid1, xid2 + SUBTRANS_XACTS_PER_PAGE - 1));
421 : : }
|