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-2026, 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 "storage/subsystems.h"
37 : : #include "utils/guc_hooks.h"
38 : : #include "utils/snapmgr.h"
39 : :
40 : :
41 : : /*
42 : : * Defines for SubTrans page sizes. A page is the same BLCKSZ as is used
43 : : * everywhere else in Postgres.
44 : : *
45 : : * Note: because TransactionIds are 32 bits and wrap around at 0xFFFFFFFF,
46 : : * SubTrans page numbering also wraps around at
47 : : * 0xFFFFFFFF/SUBTRANS_XACTS_PER_PAGE, and segment numbering at
48 : : * 0xFFFFFFFF/SUBTRANS_XACTS_PER_PAGE/SLRU_PAGES_PER_SEGMENT. We need take no
49 : : * explicit notice of that fact in this module, except when comparing segment
50 : : * and page numbers in TruncateSUBTRANS (see SubTransPagePrecedes) and zeroing
51 : : * them in StartupSUBTRANS.
52 : : */
53 : :
54 : : /* We need four bytes per xact */
55 : : #define SUBTRANS_XACTS_PER_PAGE (BLCKSZ / sizeof(TransactionId))
56 : :
57 : : /*
58 : : * Although we return an int64 the actual value can't currently exceed
59 : : * 0xFFFFFFFF/SUBTRANS_XACTS_PER_PAGE.
60 : : */
61 : : static inline int64
888 akorotkov@postgresql 62 :CBC 15657 : TransactionIdToPage(TransactionId xid)
63 : : {
64 : 15657 : return xid / (int64) SUBTRANS_XACTS_PER_PAGE;
65 : : }
66 : :
67 : : #define TransactionIdToEntry(xid) ((xid) % (TransactionId) SUBTRANS_XACTS_PER_PAGE)
68 : :
69 : :
70 : : static void SUBTRANSShmemRequest(void *arg);
71 : : static void SUBTRANSShmemInit(void *arg);
72 : : static bool SubTransPagePrecedes(int64 page1, int64 page2);
73 : : static int subtrans_errdetail_for_io_error(const void *opaque_data);
74 : :
75 : : const ShmemCallbacks SUBTRANSShmemCallbacks = {
76 : : .request_fn = SUBTRANSShmemRequest,
77 : : .init_fn = SUBTRANSShmemInit,
78 : : };
79 : :
80 : : /*
81 : : * Link to shared-memory data structures for SUBTRANS control
82 : : */
83 : : static SlruDesc SubTransSlruDesc;
84 : :
85 : : #define SubTransCtl (&SubTransSlruDesc)
86 : :
87 : :
88 : : /*
89 : : * Record the parent of a subtransaction in the subtrans log.
90 : : */
91 : : void
3295 simon@2ndQuadrant.co 92 : 8382 : SubTransSetParent(TransactionId xid, TransactionId parent)
93 : : {
888 akorotkov@postgresql 94 : 8382 : int64 pageno = TransactionIdToPage(xid);
7978 tgl@sss.pgh.pa.us 95 : 8382 : int entryno = TransactionIdToEntry(xid);
96 : : int slotno;
97 : : LWLock *lock;
98 : : TransactionId *ptr;
99 : :
5981 simon@2ndQuadrant.co 100 [ - + ]: 8382 : Assert(TransactionIdIsValid(parent));
3295 101 [ - + ]: 8382 : Assert(TransactionIdFollows(xid, parent));
102 : :
797 alvherre@alvh.no-ip. 103 : 8382 : lock = SimpleLruGetBankLock(SubTransCtl, pageno);
104 : 8382 : LWLockAcquire(lock, LW_EXCLUSIVE);
105 : :
53 heikki.linnakangas@i 106 :GNC 8382 : slotno = SimpleLruReadPage(SubTransCtl, pageno, true, &xid);
7925 tgl@sss.pgh.pa.us 107 :CBC 8382 : ptr = (TransactionId *) SubTransCtl->shared->page_buffer[slotno];
7978 108 : 8382 : ptr += entryno;
109 : :
110 : : /*
111 : : * It's possible we'll try to set the parent xid multiple times but we
112 : : * shouldn't ever be changing the xid from one valid xid to another valid
113 : : * xid, which would corrupt the data structure.
114 : : */
3295 simon@2ndQuadrant.co 115 [ + + ]: 8382 : if (*ptr != parent)
116 : : {
117 [ - + ]: 7795 : Assert(*ptr == InvalidTransactionId);
118 : 7795 : *ptr = parent;
119 : 7795 : SubTransCtl->shared->page_dirty[slotno] = true;
120 : : }
121 : :
797 alvherre@alvh.no-ip. 122 : 8382 : LWLockRelease(lock);
7978 tgl@sss.pgh.pa.us 123 : 8382 : }
124 : :
125 : : /*
126 : : * Interrogate the parent of a transaction in the subtrans log.
127 : : */
128 : : TransactionId
129 : 3090 : SubTransGetParent(TransactionId xid)
130 : : {
888 akorotkov@postgresql 131 : 3090 : int64 pageno = TransactionIdToPage(xid);
7978 tgl@sss.pgh.pa.us 132 : 3090 : int entryno = TransactionIdToEntry(xid);
133 : : int slotno;
134 : : TransactionId *ptr;
135 : : TransactionId parent;
136 : :
137 : : /* Can't ask about stuff that might not be around anymore */
7901 138 [ - + ]: 3090 : Assert(TransactionIdFollowsOrEquals(xid, TransactionXmin));
139 : :
140 : : /* Bootstrap and frozen XIDs have no parent */
7978 141 [ - + ]: 3090 : if (!TransactionIdIsNormal(xid))
7978 tgl@sss.pgh.pa.us 142 :UBC 0 : return InvalidTransactionId;
143 : :
144 : : /* lock is acquired by SimpleLruReadPage_ReadOnly */
145 : :
53 heikki.linnakangas@i 146 :GNC 3090 : slotno = SimpleLruReadPage_ReadOnly(SubTransCtl, pageno, &xid);
7925 tgl@sss.pgh.pa.us 147 :CBC 3090 : ptr = (TransactionId *) SubTransCtl->shared->page_buffer[slotno];
7978 148 : 3090 : ptr += entryno;
149 : :
150 : 3090 : parent = *ptr;
151 : :
797 alvherre@alvh.no-ip. 152 : 3090 : LWLockRelease(SimpleLruGetBankLock(SubTransCtl, pageno));
153 : :
7978 tgl@sss.pgh.pa.us 154 : 3090 : return parent;
155 : : }
156 : :
157 : : /*
158 : : * SubTransGetTopmostTransaction
159 : : *
160 : : * Returns the topmost transaction of the given transaction id.
161 : : *
162 : : * Because we cannot look back further than TransactionXmin, it is possible
163 : : * that this function will lie and return an intermediate subtransaction ID
164 : : * instead of the true topmost parent ID. This is OK, because in practice
165 : : * we only care about detecting whether the topmost parent is still running
166 : : * or is part of a current snapshot's list of still-running transactions.
167 : : * Therefore, any XID before TransactionXmin is as good as any other.
168 : : */
169 : : TransactionId
170 : 1084 : SubTransGetTopmostTransaction(TransactionId xid)
171 : : {
172 : 1084 : TransactionId parentXid = xid,
7919 bruce@momjian.us 173 : 1084 : previousXid = xid;
174 : :
175 : : /* Can't ask about stuff that might not be around anymore */
7901 tgl@sss.pgh.pa.us 176 [ - + ]: 1084 : Assert(TransactionIdFollowsOrEquals(xid, TransactionXmin));
177 : :
7978 178 [ + + ]: 4174 : while (TransactionIdIsValid(parentXid))
179 : : {
180 : 3090 : previousXid = parentXid;
7901 181 [ - + ]: 3090 : if (TransactionIdPrecedes(parentXid, TransactionXmin))
7926 tgl@sss.pgh.pa.us 182 :UBC 0 : break;
7978 tgl@sss.pgh.pa.us 183 :CBC 3090 : parentXid = SubTransGetParent(parentXid);
184 : :
185 : : /*
186 : : * By convention the parent xid gets allocated first, so should always
187 : : * precede the child xid. Anything else points to a corrupted data
188 : : * structure that could lead to an infinite loop, so exit.
189 : : */
3295 simon@2ndQuadrant.co 190 [ - + ]: 3090 : if (!TransactionIdPrecedes(parentXid, previousXid))
3295 simon@2ndQuadrant.co 191 [ # # ]:UBC 0 : elog(ERROR, "pg_subtrans contains invalid entry: xid %u points to parent xid %u",
192 : : previousXid, parentXid);
193 : : }
194 : :
7978 tgl@sss.pgh.pa.us 195 [ - + ]:CBC 1084 : Assert(TransactionIdIsValid(previousXid));
196 : :
197 : 1084 : return previousXid;
198 : : }
199 : :
200 : : /*
201 : : * Number of shared SUBTRANS buffers.
202 : : *
203 : : * If asked to autotune, use 2MB for every 1GB of shared buffers, up to 8MB.
204 : : * Otherwise just cap the configured amount to be between 16 and the maximum
205 : : * allowed.
206 : : */
207 : : static int
797 alvherre@alvh.no-ip. 208 : 2476 : SUBTRANSShmemBuffers(void)
209 : : {
210 : : /* auto-tune based on shared buffers */
211 [ + + ]: 2476 : if (subtransaction_buffers == 0)
212 : 1232 : return SimpleLruAutotuneBuffers(512, 1024);
213 : :
214 [ + - ]: 1244 : return Min(Max(16, subtransaction_buffers), SLRU_MAX_ALLOWED_BUFFERS);
215 : : }
216 : :
217 : :
218 : :
219 : : /*
220 : : * Register shared memory for SUBTRANS
221 : : */
222 : : static void
29 heikki.linnakangas@i 223 :GNC 1244 : SUBTRANSShmemRequest(void *arg)
224 : : {
225 : : /* If auto-tuning is requested, now is the time to do it */
797 alvherre@alvh.no-ip. 226 [ + + ]:CBC 1244 : if (subtransaction_buffers == 0)
227 : : {
228 : : char buf[32];
229 : :
230 : 1232 : snprintf(buf, sizeof(buf), "%d", SUBTRANSShmemBuffers());
231 : 1232 : SetConfigOption("subtransaction_buffers", buf, PGC_POSTMASTER,
232 : : PGC_S_DYNAMIC_DEFAULT);
233 : :
234 : : /*
235 : : * We prefer to report this value's source as PGC_S_DYNAMIC_DEFAULT.
236 : : * However, if the DBA explicitly set subtransaction_buffers = 0 in
237 : : * the config file, then PGC_S_DYNAMIC_DEFAULT will fail to override
238 : : * that and we must force the matter with PGC_S_OVERRIDE.
239 : : */
240 [ - + ]: 1232 : if (subtransaction_buffers == 0) /* failed to apply it? */
797 alvherre@alvh.no-ip. 241 :UBC 0 : SetConfigOption("subtransaction_buffers", buf, PGC_POSTMASTER,
242 : : PGC_S_OVERRIDE);
243 : : }
797 alvherre@alvh.no-ip. 244 [ - + ]:CBC 1244 : Assert(subtransaction_buffers != 0);
245 : :
29 heikki.linnakangas@i 246 :GNC 1244 : SimpleLruRequest(.desc = &SubTransSlruDesc,
247 : : .name = "subtransaction",
248 : : .Dir = "pg_subtrans",
249 : : .long_segment_names = false,
250 : :
251 : : .nslots = SUBTRANSShmemBuffers(),
252 : :
253 : : .sync_handler = SYNC_HANDLER_NONE,
254 : : .PagePrecedes = SubTransPagePrecedes,
255 : : .errdetail_for_io_error = subtrans_errdetail_for_io_error,
256 : :
257 : : .buffer_tranche_id = LWTRANCHE_SUBTRANS_BUFFER,
258 : : .bank_tranche_id = LWTRANCHE_SUBTRANS_SLRU,
259 : : );
260 : 1244 : }
261 : :
262 : : static void
263 : 1241 : SUBTRANSShmemInit(void *arg)
264 : : {
1935 noah@leadboat.com 265 :CBC 1241 : SlruPagePrecedesUnitTests(SubTransCtl, SUBTRANS_XACTS_PER_PAGE);
7978 tgl@sss.pgh.pa.us 266 : 1241 : }
267 : :
268 : : /*
269 : : * GUC check_hook for subtransaction_buffers
270 : : */
271 : : bool
797 alvherre@alvh.no-ip. 272 : 2542 : check_subtrans_buffers(int *newval, void **extra, GucSource source)
273 : : {
274 : 2542 : return check_slru_buffers("subtransaction_buffers", newval);
275 : : }
276 : :
277 : : /*
278 : : * This func must be called ONCE on system install. It creates
279 : : * the initial SUBTRANS segment. (The SUBTRANS directory is assumed to
280 : : * have been created by the initdb shell script, and SUBTRANSShmemInit
281 : : * must have been called already.)
282 : : *
283 : : * Note: it's not really necessary to create the initial segment now,
284 : : * since slru.c would create it on first write anyway. But we may as well
285 : : * do it to be sure the directory is set up correctly.
286 : : */
287 : : void
7978 tgl@sss.pgh.pa.us 288 : 57 : BootStrapSUBTRANS(void)
289 : : {
290 : : /* Zero the initial page and flush it to disk */
302 alvherre@kurilemu.de 291 :GNC 57 : SimpleLruZeroAndWritePage(SubTransCtl, 0);
7978 tgl@sss.pgh.pa.us 292 :GIC 57 : }
293 : :
294 : : /*
295 : : * This must be called ONCE during postmaster or standalone-backend startup,
296 : : * after StartupXLOG has initialized TransamVariables->nextXid.
297 : : *
298 : : * oldestActiveXID is the oldest XID of any prepared transaction, or nextXid
299 : : * if there are none.
300 : : */
301 : : void
7627 tgl@sss.pgh.pa.us 302 :CBC 1075 : StartupSUBTRANS(TransactionId oldestActiveXID)
303 : : {
304 : : FullTransactionId nextXid;
305 : : int64 startPage;
306 : : int64 endPage;
791 alvherre@alvh.no-ip. 307 : 1075 : LWLock *prevlock = NULL;
308 : : LWLock *lock;
309 : :
310 : : /*
311 : : * Since we don't expect pg_subtrans to be valid across crashes, we
312 : : * initialize the currently-active page(s) to zeroes during startup.
313 : : * Whenever we advance into a new page, ExtendSUBTRANS will likewise zero
314 : : * the new page without regard to whatever was previously on disk.
315 : : */
7627 tgl@sss.pgh.pa.us 316 : 1075 : startPage = TransactionIdToPage(oldestActiveXID);
879 heikki.linnakangas@i 317 : 1075 : nextXid = TransamVariables->nextXid;
2093 andres@anarazel.de 318 : 1075 : endPage = TransactionIdToPage(XidFromFullTransactionId(nextXid));
319 : :
320 : : for (;;)
321 : : {
797 alvherre@alvh.no-ip. 322 : 1077 : lock = SimpleLruGetBankLock(SubTransCtl, startPage);
323 [ + - ]: 1077 : if (prevlock != lock)
324 : : {
791 325 [ + + ]: 1077 : if (prevlock)
326 : 2 : LWLockRelease(prevlock);
797 327 : 1077 : LWLockAcquire(lock, LW_EXCLUSIVE);
328 : 1077 : prevlock = lock;
329 : : }
330 : :
302 alvherre@kurilemu.de 331 :GNC 1077 : (void) SimpleLruZeroPage(SubTransCtl, startPage);
791 alvherre@alvh.no-ip. 332 [ + + ]:CBC 1077 : if (startPage == endPage)
333 : 1075 : break;
334 : :
7627 tgl@sss.pgh.pa.us 335 : 2 : startPage++;
336 : : /* must account for wraparound */
3728 simon@2ndQuadrant.co 337 [ - + ]: 2 : if (startPage > TransactionIdToPage(MaxTransactionId))
3617 rhaas@postgresql.org 338 :UBC 0 : startPage = 0;
339 : : }
340 : :
797 alvherre@alvh.no-ip. 341 :CBC 1075 : LWLockRelease(lock);
7978 tgl@sss.pgh.pa.us 342 : 1075 : }
343 : :
344 : : /*
345 : : * Perform a checkpoint --- either during shutdown, or on-the-fly
346 : : */
347 : : void
348 : 1944 : CheckPointSUBTRANS(void)
349 : : {
350 : : /*
351 : : * Write dirty SUBTRANS pages to disk
352 : : *
353 : : * This is not actually necessary from a correctness point of view. We do
354 : : * it merely to improve the odds that writing of dirty pages is done by
355 : : * the checkpoint process and not by backends.
356 : : */
357 : : TRACE_POSTGRESQL_SUBTRANS_CHECKPOINT_START(true);
2048 tmunro@postgresql.or 358 : 1944 : SimpleLruWriteAll(SubTransCtl, true);
359 : : TRACE_POSTGRESQL_SUBTRANS_CHECKPOINT_DONE(true);
7978 tgl@sss.pgh.pa.us 360 : 1944 : }
361 : :
362 : :
363 : : /*
364 : : * Make sure that SUBTRANS has room for a newly-allocated XID.
365 : : *
366 : : * NB: this is called while holding XidGenLock. We want it to be very fast
367 : : * most of the time; even when it's not so fast, no actual I/O need happen
368 : : * unless we're forced to write out a dirty subtrans page to make room
369 : : * in shared memory.
370 : : */
371 : : void
372 : 196632 : ExtendSUBTRANS(TransactionId newestXact)
373 : : {
374 : : int64 pageno;
375 : : LWLock *lock;
376 : :
377 : : /*
378 : : * No work except at first XID of a page. But beware: just after
379 : : * wraparound, the first XID of page zero is FirstNormalTransactionId.
380 : : */
381 [ + + + + ]: 196632 : if (TransactionIdToEntry(newestXact) != 0 &&
382 : : !TransactionIdEquals(newestXact, FirstNormalTransactionId))
383 : 196513 : return;
384 : :
385 : 119 : pageno = TransactionIdToPage(newestXact);
386 : :
797 alvherre@alvh.no-ip. 387 : 119 : lock = SimpleLruGetBankLock(SubTransCtl, pageno);
388 : 119 : LWLockAcquire(lock, LW_EXCLUSIVE);
389 : :
390 : : /* Zero the page */
302 alvherre@kurilemu.de 391 :GNC 119 : SimpleLruZeroPage(SubTransCtl, pageno);
392 : :
797 alvherre@alvh.no-ip. 393 :CBC 119 : LWLockRelease(lock);
394 : : }
395 : :
396 : :
397 : : /*
398 : : * Remove all SUBTRANS segments before the one holding the passed transaction ID
399 : : *
400 : : * oldestXact is the oldest TransactionXmin of any running transaction. This
401 : : * is called only during checkpoint.
402 : : */
403 : : void
7978 tgl@sss.pgh.pa.us 404 : 1914 : TruncateSUBTRANS(TransactionId oldestXact)
405 : : {
406 : : int64 cutoffPage;
407 : :
408 : : /*
409 : : * The cutoff point is the start of the segment containing oldestXact. We
410 : : * pass the *page* containing oldestXact to SimpleLruTruncate. We step
411 : : * back one transaction to avoid passing a cutoff page that hasn't been
412 : : * created yet in the rare case that oldestXact would be the first item on
413 : : * a page and oldestXact == next XID. In that case, if we didn't subtract
414 : : * one, we'd trigger SimpleLruTruncate's wraparound detection.
415 : : */
3939 heikki.linnakangas@i 416 [ + + ]: 2091 : TransactionIdRetreat(oldestXact);
7978 tgl@sss.pgh.pa.us 417 : 1914 : cutoffPage = TransactionIdToPage(oldestXact);
418 : :
419 : 1914 : SimpleLruTruncate(SubTransCtl, cutoffPage);
420 : 1914 : }
421 : :
422 : :
423 : : /*
424 : : * Decide whether a SUBTRANS page number is "older" for truncation purposes.
425 : : * Analogous to CLOGPagePrecedes().
426 : : */
427 : : static bool
888 akorotkov@postgresql 428 : 54674 : SubTransPagePrecedes(int64 page1, int64 page2)
429 : : {
430 : : TransactionId xid1;
431 : : TransactionId xid2;
432 : :
7978 tgl@sss.pgh.pa.us 433 : 54674 : xid1 = ((TransactionId) page1) * SUBTRANS_XACTS_PER_PAGE;
1935 noah@leadboat.com 434 : 54674 : xid1 += FirstNormalTransactionId + 1;
7978 tgl@sss.pgh.pa.us 435 : 54674 : xid2 = ((TransactionId) page2) * SUBTRANS_XACTS_PER_PAGE;
1935 noah@leadboat.com 436 : 54674 : xid2 += FirstNormalTransactionId + 1;
437 : :
438 [ + + + + ]: 87392 : return (TransactionIdPrecedes(xid1, xid2) &&
439 : 32718 : TransactionIdPrecedes(xid1, xid2 + SUBTRANS_XACTS_PER_PAGE - 1));
440 : : }
441 : :
442 : : static int
53 heikki.linnakangas@i 443 :UNC 0 : subtrans_errdetail_for_io_error(const void *opaque_data)
444 : : {
445 : 0 : TransactionId xid = *(const TransactionId *) opaque_data;
446 : :
447 : 0 : return errdetail("Could not access subtransaction status of transaction %u.", xid);
448 : : }
|