Branch data 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
61 : 253 : TransactionIdToPage(TransactionId xid)
62 : : {
63 : 253 : 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
84 : 226 : SubTransSetParent(TransactionId xid, TransactionId parent)
85 : : {
86 : 226 : int64 pageno = TransactionIdToPage(xid);
87 : 226 : int entryno = TransactionIdToEntry(xid);
88 : 226 : int slotno;
89 : 226 : LWLock *lock;
90 : 226 : TransactionId *ptr;
91 : :
92 [ + - ]: 226 : Assert(TransactionIdIsValid(parent));
93 [ + - ]: 226 : Assert(TransactionIdFollows(xid, parent));
94 : :
95 : 226 : lock = SimpleLruGetBankLock(SubTransCtl, pageno);
96 : 226 : LWLockAcquire(lock, LW_EXCLUSIVE);
97 : :
98 : 226 : slotno = SimpleLruReadPage(SubTransCtl, pageno, true, xid);
99 : 226 : ptr = (TransactionId *) SubTransCtl->shared->page_buffer[slotno];
100 : 226 : 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 : : */
107 [ - + ]: 226 : if (*ptr != parent)
108 : : {
109 [ + - ]: 226 : Assert(*ptr == InvalidTransactionId);
110 : 226 : *ptr = parent;
111 : 226 : SubTransCtl->shared->page_dirty[slotno] = true;
112 : 226 : }
113 : :
114 : 226 : LWLockRelease(lock);
115 : 226 : }
116 : :
117 : : /*
118 : : * Interrogate the parent of a transaction in the subtrans log.
119 : : */
120 : : TransactionId
121 : 1 : SubTransGetParent(TransactionId xid)
122 : : {
123 : 1 : int64 pageno = TransactionIdToPage(xid);
124 : 1 : int entryno = TransactionIdToEntry(xid);
125 : 1 : int slotno;
126 : 1 : TransactionId *ptr;
127 : 1 : TransactionId parent;
128 : :
129 : : /* Can't ask about stuff that might not be around anymore */
130 [ + - ]: 1 : Assert(TransactionIdFollowsOrEquals(xid, TransactionXmin));
131 : :
132 : : /* Bootstrap and frozen XIDs have no parent */
133 [ + - ]: 1 : if (!TransactionIdIsNormal(xid))
134 : 0 : return InvalidTransactionId;
135 : :
136 : : /* lock is acquired by SimpleLruReadPage_ReadOnly */
137 : :
138 : 1 : slotno = SimpleLruReadPage_ReadOnly(SubTransCtl, pageno, xid);
139 : 1 : ptr = (TransactionId *) SubTransCtl->shared->page_buffer[slotno];
140 : 1 : ptr += entryno;
141 : :
142 : 1 : parent = *ptr;
143 : :
144 : 1 : LWLockRelease(SimpleLruGetBankLock(SubTransCtl, pageno));
145 : :
146 : 1 : return parent;
147 : 1 : }
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 : 1 : SubTransGetTopmostTransaction(TransactionId xid)
163 : : {
164 : 1 : TransactionId parentXid = xid,
165 : 1 : previousXid = xid;
166 : :
167 : : /* Can't ask about stuff that might not be around anymore */
168 [ + - ]: 1 : Assert(TransactionIdFollowsOrEquals(xid, TransactionXmin));
169 : :
170 [ + + ]: 2 : while (TransactionIdIsValid(parentXid))
171 : : {
172 : 1 : previousXid = parentXid;
173 [ + - ]: 1 : if (TransactionIdPrecedes(parentXid, TransactionXmin))
174 : 0 : break;
175 : 1 : 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 : : */
182 [ + - ]: 1 : if (!TransactionIdPrecedes(parentXid, previousXid))
183 [ # # # # ]: 0 : elog(ERROR, "pg_subtrans contains invalid entry: xid %u points to parent xid %u",
184 : : previousXid, parentXid);
185 : : }
186 : :
187 [ + - ]: 1 : Assert(TransactionIdIsValid(previousXid));
188 : :
189 : 2 : return previousXid;
190 : 1 : }
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
200 : 21 : SUBTRANSShmemBuffers(void)
201 : : {
202 : : /* auto-tune based on shared buffers */
203 [ + + ]: 21 : if (subtransaction_buffers == 0)
204 : 15 : return SimpleLruAutotuneBuffers(512, 1024);
205 : :
206 [ - + + - : 6 : return Min(Max(16, subtransaction_buffers), SLRU_MAX_ALLOWED_BUFFERS);
- + ]
207 : 21 : }
208 : :
209 : : /*
210 : : * Initialization of shared memory for SUBTRANS
211 : : */
212 : : Size
213 : 9 : SUBTRANSShmemSize(void)
214 : : {
215 : 9 : return SimpleLruShmemSize(SUBTRANSShmemBuffers(), 0);
216 : : }
217 : :
218 : : void
219 : 6 : SUBTRANSShmemInit(void)
220 : : {
221 : : /* If auto-tuning is requested, now is the time to do it */
222 [ - + ]: 6 : if (subtransaction_buffers == 0)
223 : : {
224 : 6 : char buf[32];
225 : :
226 : 6 : snprintf(buf, sizeof(buf), "%d", SUBTRANSShmemBuffers());
227 : 6 : 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 [ + - ]: 6 : if (subtransaction_buffers == 0) /* failed to apply it? */
237 : 0 : SetConfigOption("subtransaction_buffers", buf, PGC_POSTMASTER,
238 : : PGC_S_OVERRIDE);
239 : 6 : }
240 [ + - ]: 6 : Assert(subtransaction_buffers != 0);
241 : :
242 : 6 : SubTransCtl->PagePrecedes = SubTransPagePrecedes;
243 : 6 : SimpleLruInit(SubTransCtl, "subtransaction", SUBTRANSShmemBuffers(), 0,
244 : : "pg_subtrans", LWTRANCHE_SUBTRANS_BUFFER,
245 : : LWTRANCHE_SUBTRANS_SLRU, SYNC_HANDLER_NONE, false);
246 : 6 : SlruPagePrecedesUnitTests(SubTransCtl, SUBTRANS_XACTS_PER_PAGE);
247 : 6 : }
248 : :
249 : : /*
250 : : * GUC check_hook for subtransaction_buffers
251 : : */
252 : : bool
253 : 12 : check_subtrans_buffers(int *newval, void **extra, GucSource source)
254 : : {
255 : 12 : 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
269 : 1 : BootStrapSUBTRANS(void)
270 : : {
271 : : /* Zero the initial page and flush it to disk */
272 : 1 : SimpleLruZeroAndWritePage(SubTransCtl, 0);
273 : 1 : }
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
283 : 4 : StartupSUBTRANS(TransactionId oldestActiveXID)
284 : : {
285 : 4 : FullTransactionId nextXid;
286 : 4 : int64 startPage;
287 : 4 : int64 endPage;
288 : 4 : LWLock *prevlock = NULL;
289 : 4 : 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 : : */
297 : 4 : startPage = TransactionIdToPage(oldestActiveXID);
298 : 4 : nextXid = TransamVariables->nextXid;
299 : 4 : endPage = TransactionIdToPage(XidFromFullTransactionId(nextXid));
300 : :
301 : 4 : for (;;)
302 : : {
303 : 4 : lock = SimpleLruGetBankLock(SubTransCtl, startPage);
304 [ - + ]: 4 : if (prevlock != lock)
305 : : {
306 [ + - ]: 4 : if (prevlock)
307 : 0 : LWLockRelease(prevlock);
308 : 4 : LWLockAcquire(lock, LW_EXCLUSIVE);
309 : 4 : prevlock = lock;
310 : 4 : }
311 : :
312 : 4 : (void) SimpleLruZeroPage(SubTransCtl, startPage);
313 [ + - ]: 4 : if (startPage == endPage)
314 : 4 : break;
315 : :
316 : 0 : startPage++;
317 : : /* must account for wraparound */
318 [ # # ]: 0 : if (startPage > TransactionIdToPage(MaxTransactionId))
319 : 0 : startPage = 0;
320 : : }
321 : :
322 : 4 : LWLockRelease(lock);
323 : 4 : }
324 : :
325 : : /*
326 : : * Perform a checkpoint --- either during shutdown, or on-the-fly
327 : : */
328 : : void
329 : 7 : 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 : 7 : TRACE_POSTGRESQL_SUBTRANS_CHECKPOINT_START(true);
339 : 7 : SimpleLruWriteAll(SubTransCtl, true);
340 : 7 : TRACE_POSTGRESQL_SUBTRANS_CHECKPOINT_DONE(true);
341 : 7 : }
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 : 21995 : ExtendSUBTRANS(TransactionId newestXact)
354 : : {
355 : 21995 : int64 pageno;
356 : 21995 : 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 [ + + + + ]: 21995 : if (TransactionIdToEntry(newestXact) != 0 &&
363 : 21985 : !TransactionIdEquals(newestXact, FirstNormalTransactionId))
364 : 21984 : return;
365 : :
366 : 11 : pageno = TransactionIdToPage(newestXact);
367 : :
368 : 11 : lock = SimpleLruGetBankLock(SubTransCtl, pageno);
369 : 11 : LWLockAcquire(lock, LW_EXCLUSIVE);
370 : :
371 : : /* Zero the page */
372 : 11 : SimpleLruZeroPage(SubTransCtl, pageno);
373 : :
374 : 11 : LWLockRelease(lock);
375 [ - + ]: 21995 : }
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
385 : 7 : TruncateSUBTRANS(TransactionId oldestXact)
386 : : {
387 : 7 : 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 : : */
397 [ + + ]: 10 : TransactionIdRetreat(oldestXact);
398 : 7 : cutoffPage = TransactionIdToPage(oldestXact);
399 : :
400 : 7 : SimpleLruTruncate(SubTransCtl, cutoffPage);
401 : 7 : }
402 : :
403 : :
404 : : /*
405 : : * Decide whether a SUBTRANS page number is "older" for truncation purposes.
406 : : * Analogous to CLOGPagePrecedes().
407 : : */
408 : : static bool
409 : 255 : SubTransPagePrecedes(int64 page1, int64 page2)
410 : : {
411 : 255 : TransactionId xid1;
412 : 255 : TransactionId xid2;
413 : :
414 : 255 : xid1 = ((TransactionId) page1) * SUBTRANS_XACTS_PER_PAGE;
415 : 255 : xid1 += FirstNormalTransactionId + 1;
416 : 255 : xid2 = ((TransactionId) page2) * SUBTRANS_XACTS_PER_PAGE;
417 : 255 : xid2 += FirstNormalTransactionId + 1;
418 : :
419 [ + + ]: 411 : return (TransactionIdPrecedes(xid1, xid2) &&
420 : 156 : TransactionIdPrecedes(xid1, xid2 + SUBTRANS_XACTS_PER_PAGE - 1));
421 : 255 : }
|