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 "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
837 akorotkov@postgresql 61 :CBC 15089 : TransactionIdToPage(TransactionId xid)
62 : : {
63 : 15089 : 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 : : static int subtrans_errdetail_for_io_error(const void *opaque_data);
79 : :
80 : :
81 : : /*
82 : : * Record the parent of a subtransaction in the subtrans log.
83 : : */
84 : : void
3244 simon@2ndQuadrant.co 85 : 8125 : SubTransSetParent(TransactionId xid, TransactionId parent)
86 : : {
837 akorotkov@postgresql 87 : 8125 : int64 pageno = TransactionIdToPage(xid);
7927 tgl@sss.pgh.pa.us 88 : 8125 : int entryno = TransactionIdToEntry(xid);
89 : : int slotno;
90 : : LWLock *lock;
91 : : TransactionId *ptr;
92 : :
5930 simon@2ndQuadrant.co 93 [ - + ]: 8125 : Assert(TransactionIdIsValid(parent));
3244 94 [ - + ]: 8125 : Assert(TransactionIdFollows(xid, parent));
95 : :
746 alvherre@alvh.no-ip. 96 : 8125 : lock = SimpleLruGetBankLock(SubTransCtl, pageno);
97 : 8125 : LWLockAcquire(lock, LW_EXCLUSIVE);
98 : :
2 heikki.linnakangas@i 99 :GNC 8125 : slotno = SimpleLruReadPage(SubTransCtl, pageno, true, &xid);
7874 tgl@sss.pgh.pa.us 100 :CBC 8125 : ptr = (TransactionId *) SubTransCtl->shared->page_buffer[slotno];
7927 101 : 8125 : 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 : : */
3244 simon@2ndQuadrant.co 108 [ + + ]: 8125 : if (*ptr != parent)
109 : : {
110 [ - + ]: 7538 : Assert(*ptr == InvalidTransactionId);
111 : 7538 : *ptr = parent;
112 : 7538 : SubTransCtl->shared->page_dirty[slotno] = true;
113 : : }
114 : :
746 alvherre@alvh.no-ip. 115 : 8125 : LWLockRelease(lock);
7927 tgl@sss.pgh.pa.us 116 : 8125 : }
117 : :
118 : : /*
119 : : * Interrogate the parent of a transaction in the subtrans log.
120 : : */
121 : : TransactionId
122 : 3092 : SubTransGetParent(TransactionId xid)
123 : : {
837 akorotkov@postgresql 124 : 3092 : int64 pageno = TransactionIdToPage(xid);
7927 tgl@sss.pgh.pa.us 125 : 3092 : 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 */
7850 131 [ - + ]: 3092 : Assert(TransactionIdFollowsOrEquals(xid, TransactionXmin));
132 : :
133 : : /* Bootstrap and frozen XIDs have no parent */
7927 134 [ - + ]: 3092 : if (!TransactionIdIsNormal(xid))
7927 tgl@sss.pgh.pa.us 135 :UBC 0 : return InvalidTransactionId;
136 : :
137 : : /* lock is acquired by SimpleLruReadPage_ReadOnly */
138 : :
2 heikki.linnakangas@i 139 :GNC 3092 : slotno = SimpleLruReadPage_ReadOnly(SubTransCtl, pageno, &xid);
7874 tgl@sss.pgh.pa.us 140 :CBC 3092 : ptr = (TransactionId *) SubTransCtl->shared->page_buffer[slotno];
7927 141 : 3092 : ptr += entryno;
142 : :
143 : 3092 : parent = *ptr;
144 : :
746 alvherre@alvh.no-ip. 145 : 3092 : LWLockRelease(SimpleLruGetBankLock(SubTransCtl, pageno));
146 : :
7927 tgl@sss.pgh.pa.us 147 : 3092 : 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 : 1085 : SubTransGetTopmostTransaction(TransactionId xid)
164 : : {
165 : 1085 : TransactionId parentXid = xid,
7868 bruce@momjian.us 166 : 1085 : previousXid = xid;
167 : :
168 : : /* Can't ask about stuff that might not be around anymore */
7850 tgl@sss.pgh.pa.us 169 [ - + ]: 1085 : Assert(TransactionIdFollowsOrEquals(xid, TransactionXmin));
170 : :
7927 171 [ + + ]: 4177 : while (TransactionIdIsValid(parentXid))
172 : : {
173 : 3092 : previousXid = parentXid;
7850 174 [ - + ]: 3092 : if (TransactionIdPrecedes(parentXid, TransactionXmin))
7875 tgl@sss.pgh.pa.us 175 :UBC 0 : break;
7927 tgl@sss.pgh.pa.us 176 :CBC 3092 : 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 : : */
3244 simon@2ndQuadrant.co 183 [ - + ]: 3092 : if (!TransactionIdPrecedes(parentXid, previousXid))
3244 simon@2ndQuadrant.co 184 [ # # ]:UBC 0 : elog(ERROR, "pg_subtrans contains invalid entry: xid %u points to parent xid %u",
185 : : previousXid, parentXid);
186 : : }
187 : :
7927 tgl@sss.pgh.pa.us 188 [ - + ]:CBC 1085 : Assert(TransactionIdIsValid(previousXid));
189 : :
190 : 1085 : 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
746 alvherre@alvh.no-ip. 201 : 4435 : SUBTRANSShmemBuffers(void)
202 : : {
203 : : /* auto-tune based on shared buffers */
204 [ + + ]: 4435 : if (subtransaction_buffers == 0)
205 : 3266 : return SimpleLruAutotuneBuffers(512, 1024);
206 : :
207 [ + - ]: 1169 : return Min(Max(16, subtransaction_buffers), SLRU_MAX_ALLOWED_BUFFERS);
208 : : }
209 : :
210 : : /*
211 : : * Initialization of shared memory for SUBTRANS
212 : : */
213 : : Size
7927 tgl@sss.pgh.pa.us 214 : 2147 : SUBTRANSShmemSize(void)
215 : : {
746 alvherre@alvh.no-ip. 216 : 2147 : return SimpleLruShmemSize(SUBTRANSShmemBuffers(), 0);
217 : : }
218 : :
219 : : void
7927 tgl@sss.pgh.pa.us 220 : 1150 : SUBTRANSShmemInit(void)
221 : : {
222 : : /* If auto-tuning is requested, now is the time to do it */
746 alvherre@alvh.no-ip. 223 [ + + ]: 1150 : if (subtransaction_buffers == 0)
224 : : {
225 : : char buf[32];
226 : :
227 : 1138 : snprintf(buf, sizeof(buf), "%d", SUBTRANSShmemBuffers());
228 : 1138 : 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 [ - + ]: 1138 : if (subtransaction_buffers == 0) /* failed to apply it? */
746 alvherre@alvh.no-ip. 238 :UBC 0 : SetConfigOption("subtransaction_buffers", buf, PGC_POSTMASTER,
239 : : PGC_S_OVERRIDE);
240 : : }
746 alvherre@alvh.no-ip. 241 [ - + ]:CBC 1150 : Assert(subtransaction_buffers != 0);
242 : :
7927 tgl@sss.pgh.pa.us 243 : 1150 : SubTransCtl->PagePrecedes = SubTransPagePrecedes;
2 heikki.linnakangas@i 244 :GNC 1150 : SubTransCtl->errdetail_for_io_error = subtrans_errdetail_for_io_error;
746 alvherre@alvh.no-ip. 245 :CBC 1150 : SimpleLruInit(SubTransCtl, "subtransaction", SUBTRANSShmemBuffers(), 0,
246 : : "pg_subtrans", LWTRANCHE_SUBTRANS_BUFFER,
247 : : LWTRANCHE_SUBTRANS_SLRU, SYNC_HANDLER_NONE, false);
1884 noah@leadboat.com 248 : 1150 : SlruPagePrecedesUnitTests(SubTransCtl, SUBTRANS_XACTS_PER_PAGE);
7927 tgl@sss.pgh.pa.us 249 : 1150 : }
250 : :
251 : : /*
252 : : * GUC check_hook for subtransaction_buffers
253 : : */
254 : : bool
746 alvherre@alvh.no-ip. 255 : 2346 : check_subtrans_buffers(int *newval, void **extra, GucSource source)
256 : : {
257 : 2346 : return check_slru_buffers("subtransaction_buffers", newval);
258 : : }
259 : :
260 : : /*
261 : : * This func must be called ONCE on system install. It creates
262 : : * the initial SUBTRANS segment. (The SUBTRANS directory is assumed to
263 : : * have been created by the initdb shell script, and SUBTRANSShmemInit
264 : : * must have been called already.)
265 : : *
266 : : * Note: it's not really necessary to create the initial segment now,
267 : : * since slru.c would create it on first write anyway. But we may as well
268 : : * do it to be sure the directory is set up correctly.
269 : : */
270 : : void
7927 tgl@sss.pgh.pa.us 271 : 51 : BootStrapSUBTRANS(void)
272 : : {
273 : : /* Zero the initial page and flush it to disk */
251 alvherre@kurilemu.de 274 :GNC 51 : SimpleLruZeroAndWritePage(SubTransCtl, 0);
7927 tgl@sss.pgh.pa.us 275 :GIC 51 : }
276 : :
277 : : /*
278 : : * This must be called ONCE during postmaster or standalone-backend startup,
279 : : * after StartupXLOG has initialized TransamVariables->nextXid.
280 : : *
281 : : * oldestActiveXID is the oldest XID of any prepared transaction, or nextXid
282 : : * if there are none.
283 : : */
284 : : void
7576 tgl@sss.pgh.pa.us 285 :CBC 998 : StartupSUBTRANS(TransactionId oldestActiveXID)
286 : : {
287 : : FullTransactionId nextXid;
288 : : int64 startPage;
289 : : int64 endPage;
740 alvherre@alvh.no-ip. 290 : 998 : LWLock *prevlock = NULL;
291 : : LWLock *lock;
292 : :
293 : : /*
294 : : * Since we don't expect pg_subtrans to be valid across crashes, we
295 : : * initialize the currently-active page(s) to zeroes during startup.
296 : : * Whenever we advance into a new page, ExtendSUBTRANS will likewise zero
297 : : * the new page without regard to whatever was previously on disk.
298 : : */
7576 tgl@sss.pgh.pa.us 299 : 998 : startPage = TransactionIdToPage(oldestActiveXID);
828 heikki.linnakangas@i 300 : 998 : nextXid = TransamVariables->nextXid;
2042 andres@anarazel.de 301 : 998 : endPage = TransactionIdToPage(XidFromFullTransactionId(nextXid));
302 : :
303 : : for (;;)
304 : : {
746 alvherre@alvh.no-ip. 305 : 1000 : lock = SimpleLruGetBankLock(SubTransCtl, startPage);
306 [ + - ]: 1000 : if (prevlock != lock)
307 : : {
740 308 [ + + ]: 1000 : if (prevlock)
309 : 2 : LWLockRelease(prevlock);
746 310 : 1000 : LWLockAcquire(lock, LW_EXCLUSIVE);
311 : 1000 : prevlock = lock;
312 : : }
313 : :
251 alvherre@kurilemu.de 314 :GNC 1000 : (void) SimpleLruZeroPage(SubTransCtl, startPage);
740 alvherre@alvh.no-ip. 315 [ + + ]:CBC 1000 : if (startPage == endPage)
316 : 998 : break;
317 : :
7576 tgl@sss.pgh.pa.us 318 : 2 : startPage++;
319 : : /* must account for wraparound */
3677 simon@2ndQuadrant.co 320 [ - + ]: 2 : if (startPage > TransactionIdToPage(MaxTransactionId))
3566 rhaas@postgresql.org 321 :UBC 0 : startPage = 0;
322 : : }
323 : :
746 alvherre@alvh.no-ip. 324 :CBC 998 : LWLockRelease(lock);
7927 tgl@sss.pgh.pa.us 325 : 998 : }
326 : :
327 : : /*
328 : : * Perform a checkpoint --- either during shutdown, or on-the-fly
329 : : */
330 : : void
331 : 1802 : CheckPointSUBTRANS(void)
332 : : {
333 : : /*
334 : : * Write dirty SUBTRANS pages to disk
335 : : *
336 : : * This is not actually necessary from a correctness point of view. We do
337 : : * it merely to improve the odds that writing of dirty pages is done by
338 : : * the checkpoint process and not by backends.
339 : : */
340 : : TRACE_POSTGRESQL_SUBTRANS_CHECKPOINT_START(true);
1997 tmunro@postgresql.or 341 : 1802 : SimpleLruWriteAll(SubTransCtl, true);
342 : : TRACE_POSTGRESQL_SUBTRANS_CHECKPOINT_DONE(true);
7927 tgl@sss.pgh.pa.us 343 : 1802 : }
344 : :
345 : :
346 : : /*
347 : : * Make sure that SUBTRANS has room for a newly-allocated XID.
348 : : *
349 : : * NB: this is called while holding XidGenLock. We want it to be very fast
350 : : * most of the time; even when it's not so fast, no actual I/O need happen
351 : : * unless we're forced to write out a dirty subtrans page to make room
352 : : * in shared memory.
353 : : */
354 : : void
355 : 162913 : ExtendSUBTRANS(TransactionId newestXact)
356 : : {
357 : : int64 pageno;
358 : : LWLock *lock;
359 : :
360 : : /*
361 : : * No work except at first XID of a page. But beware: just after
362 : : * wraparound, the first XID of page zero is FirstNormalTransactionId.
363 : : */
364 [ + + + + ]: 162913 : if (TransactionIdToEntry(newestXact) != 0 &&
365 : : !TransactionIdEquals(newestXact, FirstNormalTransactionId))
366 : 162811 : return;
367 : :
368 : 102 : pageno = TransactionIdToPage(newestXact);
369 : :
746 alvherre@alvh.no-ip. 370 : 102 : lock = SimpleLruGetBankLock(SubTransCtl, pageno);
371 : 102 : LWLockAcquire(lock, LW_EXCLUSIVE);
372 : :
373 : : /* Zero the page */
251 alvherre@kurilemu.de 374 :GNC 102 : SimpleLruZeroPage(SubTransCtl, pageno);
375 : :
746 alvherre@alvh.no-ip. 376 :CBC 102 : LWLockRelease(lock);
377 : : }
378 : :
379 : :
380 : : /*
381 : : * Remove all SUBTRANS segments before the one holding the passed transaction ID
382 : : *
383 : : * oldestXact is the oldest TransactionXmin of any running transaction. This
384 : : * is called only during checkpoint.
385 : : */
386 : : void
7927 tgl@sss.pgh.pa.us 387 : 1772 : TruncateSUBTRANS(TransactionId oldestXact)
388 : : {
389 : : int64 cutoffPage;
390 : :
391 : : /*
392 : : * The cutoff point is the start of the segment containing oldestXact. We
393 : : * pass the *page* containing oldestXact to SimpleLruTruncate. We step
394 : : * back one transaction to avoid passing a cutoff page that hasn't been
395 : : * created yet in the rare case that oldestXact would be the first item on
396 : : * a page and oldestXact == next XID. In that case, if we didn't subtract
397 : : * one, we'd trigger SimpleLruTruncate's wraparound detection.
398 : : */
3888 heikki.linnakangas@i 399 [ + + ]: 1931 : TransactionIdRetreat(oldestXact);
7927 tgl@sss.pgh.pa.us 400 : 1772 : cutoffPage = TransactionIdToPage(oldestXact);
401 : :
402 : 1772 : SimpleLruTruncate(SubTransCtl, cutoffPage);
403 : 1772 : }
404 : :
405 : :
406 : : /*
407 : : * Decide whether a SUBTRANS page number is "older" for truncation purposes.
408 : : * Analogous to CLOGPagePrecedes().
409 : : */
410 : : static bool
837 akorotkov@postgresql 411 : 50684 : SubTransPagePrecedes(int64 page1, int64 page2)
412 : : {
413 : : TransactionId xid1;
414 : : TransactionId xid2;
415 : :
7927 tgl@sss.pgh.pa.us 416 : 50684 : xid1 = ((TransactionId) page1) * SUBTRANS_XACTS_PER_PAGE;
1884 noah@leadboat.com 417 : 50684 : xid1 += FirstNormalTransactionId + 1;
7927 tgl@sss.pgh.pa.us 418 : 50684 : xid2 = ((TransactionId) page2) * SUBTRANS_XACTS_PER_PAGE;
1884 noah@leadboat.com 419 : 50684 : xid2 += FirstNormalTransactionId + 1;
420 : :
421 [ + + + + ]: 81022 : return (TransactionIdPrecedes(xid1, xid2) &&
422 : 30338 : TransactionIdPrecedes(xid1, xid2 + SUBTRANS_XACTS_PER_PAGE - 1));
423 : : }
424 : :
425 : : static int
2 heikki.linnakangas@i 426 :UNC 0 : subtrans_errdetail_for_io_error(const void *opaque_data)
427 : : {
428 : 0 : TransactionId xid = *(const TransactionId *) opaque_data;
429 : :
430 : 0 : return errdetail("Could not access subtransaction status of transaction %u.", xid);
431 : : }
|