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