Branch data Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * clog.c
4 : : * PostgreSQL transaction-commit-log manager
5 : : *
6 : : * This module stores two bits per transaction regarding its commit/abort
7 : : * status; the status for four transactions fit in a byte.
8 : : *
9 : : * This would be a pretty simple abstraction on top of slru.c, except that
10 : : * for performance reasons we allow multiple transactions that are
11 : : * committing concurrently to form a queue, so that a single process can
12 : : * update the status for all of them within a single lock acquisition run.
13 : : *
14 : : * XLOG interactions: this module generates an XLOG record whenever a new
15 : : * CLOG page is initialized to zeroes. Other writes of CLOG come from
16 : : * recording of transaction commit or abort in xact.c, which generates its
17 : : * own XLOG records for these events and will re-perform the status update
18 : : * on redo; so we need make no additional XLOG entry here. For synchronous
19 : : * transaction commits, the XLOG is guaranteed flushed through the XLOG commit
20 : : * record before we are called to log a commit, so the WAL rule "write xlog
21 : : * before data" is satisfied automatically. However, for async commits we
22 : : * must track the latest LSN affecting each CLOG page, so that we can flush
23 : : * XLOG that far and satisfy the WAL rule. We don't have to worry about this
24 : : * for aborts (whether sync or async), since the post-crash assumption would
25 : : * be that such transactions failed anyway.
26 : : *
27 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
28 : : * Portions Copyright (c) 1994, Regents of the University of California
29 : : *
30 : : * src/backend/access/transam/clog.c
31 : : *
32 : : *-------------------------------------------------------------------------
33 : : */
34 : : #include "postgres.h"
35 : :
36 : : #include "access/clog.h"
37 : : #include "access/slru.h"
38 : : #include "access/transam.h"
39 : : #include "access/xlog.h"
40 : : #include "access/xloginsert.h"
41 : : #include "access/xlogutils.h"
42 : : #include "miscadmin.h"
43 : : #include "pg_trace.h"
44 : : #include "pgstat.h"
45 : : #include "storage/proc.h"
46 : : #include "storage/subsystems.h"
47 : : #include "storage/sync.h"
48 : : #include "utils/guc_hooks.h"
49 : : #include "utils/wait_event.h"
50 : :
51 : : /*
52 : : * Defines for CLOG page sizes. A page is the same BLCKSZ as is used
53 : : * everywhere else in Postgres.
54 : : *
55 : : * Note: because TransactionIds are 32 bits and wrap around at 0xFFFFFFFF,
56 : : * CLOG page numbering also wraps around at 0xFFFFFFFF/CLOG_XACTS_PER_PAGE,
57 : : * and CLOG segment numbering at
58 : : * 0xFFFFFFFF/CLOG_XACTS_PER_PAGE/SLRU_PAGES_PER_SEGMENT. We need take no
59 : : * explicit notice of that fact in this module, except when comparing segment
60 : : * and page numbers in TruncateCLOG (see CLOGPagePrecedes).
61 : : */
62 : :
63 : : /* We need two bits per xact, so four xacts fit in a byte */
64 : : #define CLOG_BITS_PER_XACT 2
65 : : #define CLOG_XACTS_PER_BYTE 4
66 : : #define CLOG_XACTS_PER_PAGE (BLCKSZ * CLOG_XACTS_PER_BYTE)
67 : : #define CLOG_XACT_BITMASK ((1 << CLOG_BITS_PER_XACT) - 1)
68 : :
69 : : /*
70 : : * Because space used in CLOG by each transaction is so small, we place a
71 : : * smaller limit on the number of CLOG buffers than SLRU allows. No other
72 : : * SLRU needs this.
73 : : */
74 : : #define CLOG_MAX_ALLOWED_BUFFERS \
75 : : Min(SLRU_MAX_ALLOWED_BUFFERS, \
76 : : (((MaxTransactionId / 2) + (CLOG_XACTS_PER_PAGE - 1)) / CLOG_XACTS_PER_PAGE))
77 : :
78 : :
79 : : /*
80 : : * Although we return an int64 the actual value can't currently exceed
81 : : * 0xFFFFFFFF/CLOG_XACTS_PER_PAGE.
82 : : */
83 : : static inline int64
84 : 1538911 : TransactionIdToPage(TransactionId xid)
85 : : {
86 : 1538911 : return xid / (int64) CLOG_XACTS_PER_PAGE;
87 : : }
88 : :
89 : : #define TransactionIdToPgIndex(xid) ((xid) % (TransactionId) CLOG_XACTS_PER_PAGE)
90 : : #define TransactionIdToByte(xid) (TransactionIdToPgIndex(xid) / CLOG_XACTS_PER_BYTE)
91 : : #define TransactionIdToBIndex(xid) ((xid) % (TransactionId) CLOG_XACTS_PER_BYTE)
92 : :
93 : : /* We store the latest async LSN for each group of transactions */
94 : : #define CLOG_XACTS_PER_LSN_GROUP 32 /* keep this a power of 2 */
95 : : #define CLOG_LSNS_PER_PAGE (CLOG_XACTS_PER_PAGE / CLOG_XACTS_PER_LSN_GROUP)
96 : :
97 : : #define GetLSNIndex(slotno, xid) ((slotno) * CLOG_LSNS_PER_PAGE + \
98 : : ((xid) % (TransactionId) CLOG_XACTS_PER_PAGE) / CLOG_XACTS_PER_LSN_GROUP)
99 : :
100 : : /*
101 : : * The number of subtransactions below which we consider to apply clog group
102 : : * update optimization. Testing reveals that the number higher than this can
103 : : * hurt performance.
104 : : */
105 : : #define THRESHOLD_SUBTRANS_CLOG_OPT 5
106 : :
107 : : /*
108 : : * Link to shared-memory data structures for CLOG control
109 : : */
110 : : static void CLOGShmemRequest(void *arg);
111 : : static void CLOGShmemInit(void *arg);
112 : : static bool CLOGPagePrecedes(int64 page1, int64 page2);
113 : : static int clog_errdetail_for_io_error(const void *opaque_data);
114 : :
115 : : const ShmemCallbacks CLOGShmemCallbacks = {
116 : : .request_fn = CLOGShmemRequest,
117 : : .init_fn = CLOGShmemInit,
118 : : };
119 : :
120 : : static SlruDesc XactSlruDesc;
121 : :
122 : : #define XactCtl (&XactSlruDesc)
123 : :
124 : :
125 : : static void WriteTruncateXlogRec(int64 pageno, TransactionId oldestXact,
126 : : Oid oldestXactDb);
127 : : static void TransactionIdSetPageStatus(TransactionId xid, int nsubxids,
128 : : TransactionId *subxids, XidStatus status,
129 : : XLogRecPtr lsn, int64 pageno,
130 : : bool all_xact_same_page);
131 : : static void TransactionIdSetStatusBit(TransactionId xid, XidStatus status,
132 : : XLogRecPtr lsn, int slotno);
133 : : static void set_status_by_pages(int nsubxids, TransactionId *subxids,
134 : : XidStatus status, XLogRecPtr lsn);
135 : : static bool TransactionGroupUpdateXidStatus(TransactionId xid,
136 : : XidStatus status, XLogRecPtr lsn, int64 pageno);
137 : : static void TransactionIdSetPageStatusInternal(TransactionId xid, int nsubxids,
138 : : TransactionId *subxids, XidStatus status,
139 : : XLogRecPtr lsn, int64 pageno);
140 : :
141 : :
142 : : /*
143 : : * TransactionIdSetTreeStatus
144 : : *
145 : : * Record the final state of transaction entries in the commit log for
146 : : * a transaction and its subtransaction tree. Take care to ensure this is
147 : : * efficient, and as atomic as possible.
148 : : *
149 : : * xid is a single xid to set status for. This will typically be
150 : : * the top level transactionid for a top level commit or abort. It can
151 : : * also be a subtransaction when we record transaction aborts.
152 : : *
153 : : * subxids is an array of xids of length nsubxids, representing subtransactions
154 : : * in the tree of xid. In various cases nsubxids may be zero.
155 : : *
156 : : * lsn must be the WAL location of the commit record when recording an async
157 : : * commit. For a synchronous commit it can be InvalidXLogRecPtr, since the
158 : : * caller guarantees the commit record is already flushed in that case. It
159 : : * should be InvalidXLogRecPtr for abort cases, too.
160 : : *
161 : : * In the commit case, atomicity is limited by whether all the subxids are in
162 : : * the same CLOG page as xid. If they all are, then the lock will be grabbed
163 : : * only once, and the status will be set to committed directly. Otherwise
164 : : * we must
165 : : * 1. set sub-committed all subxids that are not on the same page as the
166 : : * main xid
167 : : * 2. atomically set committed the main xid and the subxids on the same page
168 : : * 3. go over the first bunch again and set them committed
169 : : * Note that as far as concurrent checkers are concerned, main transaction
170 : : * commit as a whole is still atomic.
171 : : *
172 : : * Example:
173 : : * TransactionId t commits and has subxids t1, t2, t3, t4
174 : : * t is on page p1, t1 is also on p1, t2 and t3 are on p2, t4 is on p3
175 : : * 1. update pages2-3:
176 : : * page2: set t2,t3 as sub-committed
177 : : * page3: set t4 as sub-committed
178 : : * 2. update page1:
179 : : * page1: set t,t1 as committed
180 : : * 3. update pages2-3:
181 : : * page2: set t2,t3 as committed
182 : : * page3: set t4 as committed
183 : : *
184 : : * NB: this is a low-level routine and is NOT the preferred entry point
185 : : * for most uses; functions in transam.c are the intended callers.
186 : : *
187 : : * XXX Think about issuing POSIX_FADV_WILLNEED on pages that we will need,
188 : : * but aren't yet in cache, as well as hinting pages not to fall out of
189 : : * cache yet.
190 : : */
191 : : void
192 : 195639 : TransactionIdSetTreeStatus(TransactionId xid, int nsubxids,
193 : : TransactionId *subxids, XidStatus status, XLogRecPtr lsn)
194 : : {
195 : 195639 : int64 pageno = TransactionIdToPage(xid); /* get page of parent */
196 : : int i;
197 : :
198 : : Assert(status == TRANSACTION_STATUS_COMMITTED ||
199 : : status == TRANSACTION_STATUS_ABORTED);
200 : :
201 : : /*
202 : : * See how many subxids, if any, are on the same page as the parent, if
203 : : * any.
204 : : */
205 [ + + ]: 200703 : for (i = 0; i < nsubxids; i++)
206 : : {
207 [ - + ]: 5064 : if (TransactionIdToPage(subxids[i]) != pageno)
208 : 0 : break;
209 : : }
210 : :
211 : : /*
212 : : * Do all items fit on a single page?
213 : : */
214 [ + - ]: 195639 : if (i == nsubxids)
215 : : {
216 : : /*
217 : : * Set the parent and all subtransactions in a single call
218 : : */
219 : 195639 : TransactionIdSetPageStatus(xid, nsubxids, subxids, status, lsn,
220 : : pageno, true);
221 : : }
222 : : else
223 : : {
224 : 0 : int nsubxids_on_first_page = i;
225 : :
226 : : /*
227 : : * If this is a commit then we care about doing this correctly (i.e.
228 : : * using the subcommitted intermediate status). By here, we know
229 : : * we're updating more than one page of clog, so we must mark entries
230 : : * that are *not* on the first page so that they show as subcommitted
231 : : * before we then return to update the status to fully committed.
232 : : *
233 : : * To avoid touching the first page twice, skip marking subcommitted
234 : : * for the subxids on that first page.
235 : : */
236 [ # # ]: 0 : if (status == TRANSACTION_STATUS_COMMITTED)
237 : 0 : set_status_by_pages(nsubxids - nsubxids_on_first_page,
238 : 0 : subxids + nsubxids_on_first_page,
239 : : TRANSACTION_STATUS_SUB_COMMITTED, lsn);
240 : :
241 : : /*
242 : : * Now set the parent and subtransactions on same page as the parent,
243 : : * if any
244 : : */
245 : 0 : pageno = TransactionIdToPage(xid);
246 : 0 : TransactionIdSetPageStatus(xid, nsubxids_on_first_page, subxids, status,
247 : : lsn, pageno, false);
248 : :
249 : : /*
250 : : * Now work through the rest of the subxids one clog page at a time,
251 : : * starting from the second page onwards, like we did above.
252 : : */
253 : 0 : set_status_by_pages(nsubxids - nsubxids_on_first_page,
254 : 0 : subxids + nsubxids_on_first_page,
255 : : status, lsn);
256 : : }
257 : 195639 : }
258 : :
259 : : /*
260 : : * Helper for TransactionIdSetTreeStatus: set the status for a bunch of
261 : : * transactions, chunking in the separate CLOG pages involved. We never
262 : : * pass the whole transaction tree to this function, only subtransactions
263 : : * that are on different pages to the top level transaction id.
264 : : */
265 : : static void
266 : 0 : set_status_by_pages(int nsubxids, TransactionId *subxids,
267 : : XidStatus status, XLogRecPtr lsn)
268 : : {
269 : 0 : int64 pageno = TransactionIdToPage(subxids[0]);
270 : 0 : int offset = 0;
271 : 0 : int i = 0;
272 : :
273 : : Assert(nsubxids > 0); /* else the pageno fetch above is unsafe */
274 : :
275 [ # # ]: 0 : while (i < nsubxids)
276 : : {
277 : 0 : int num_on_page = 0;
278 : : int64 nextpageno;
279 : :
280 : : do
281 : : {
282 : 0 : nextpageno = TransactionIdToPage(subxids[i]);
283 [ # # ]: 0 : if (nextpageno != pageno)
284 : 0 : break;
285 : 0 : num_on_page++;
286 : 0 : i++;
287 [ # # ]: 0 : } while (i < nsubxids);
288 : :
289 : 0 : TransactionIdSetPageStatus(InvalidTransactionId,
290 : 0 : num_on_page, subxids + offset,
291 : : status, lsn, pageno, false);
292 : 0 : offset = i;
293 : 0 : pageno = nextpageno;
294 : : }
295 : 0 : }
296 : :
297 : : /*
298 : : * Record the final state of transaction entries in the commit log for all
299 : : * entries on a single page. Atomic only on this page.
300 : : */
301 : : static void
302 : 195639 : TransactionIdSetPageStatus(TransactionId xid, int nsubxids,
303 : : TransactionId *subxids, XidStatus status,
304 : : XLogRecPtr lsn, int64 pageno,
305 : : bool all_xact_same_page)
306 : : {
307 : : LWLock *lock;
308 : :
309 : : /* Can't use group update when PGPROC overflows. */
310 : : StaticAssertDecl(THRESHOLD_SUBTRANS_CLOG_OPT <= PGPROC_MAX_CACHED_SUBXIDS,
311 : : "group clog threshold less than PGPROC cached subxids");
312 : :
313 : : /* Get the SLRU bank lock for the page we are going to access. */
314 : 195639 : lock = SimpleLruGetBankLock(XactCtl, pageno);
315 : :
316 : : /*
317 : : * When there is contention on the SLRU bank lock we need, we try to group
318 : : * multiple updates; a single leader process will perform transaction
319 : : * status updates for multiple backends so that the number of times the
320 : : * bank lock needs to be acquired is reduced.
321 : : *
322 : : * For this optimization to be safe, the XID and subxids in MyProc must be
323 : : * the same as the ones for which we're setting the status. Check that
324 : : * this is the case.
325 : : *
326 : : * For this optimization to be efficient, we shouldn't have too many
327 : : * sub-XIDs and all of the XIDs for which we're adjusting clog should be
328 : : * on the same page. Check those conditions, too.
329 : : */
330 [ + - + + : 195639 : if (all_xact_same_page && xid == MyProc->xid &&
+ + ]
331 : 168233 : nsubxids <= THRESHOLD_SUBTRANS_CLOG_OPT &&
332 [ + + + + ]: 168233 : nsubxids == MyProc->subxidStatus.count &&
333 : 488 : (nsubxids == 0 ||
334 [ + - ]: 488 : memcmp(subxids, MyProc->subxids.xids,
335 : : nsubxids * sizeof(TransactionId)) == 0))
336 : : {
337 : : /*
338 : : * If we can immediately acquire the lock, we update the status of our
339 : : * own XID and release the lock. If not, try use group XID update. If
340 : : * that doesn't work out, fall back to waiting for the lock to perform
341 : : * an update for this transaction only.
342 : : */
343 [ + + ]: 168123 : if (LWLockConditionalAcquire(lock, LW_EXCLUSIVE))
344 : : {
345 : : /* Got the lock without waiting! Do the update. */
346 : 168047 : TransactionIdSetPageStatusInternal(xid, nsubxids, subxids, status,
347 : : lsn, pageno);
348 : 168047 : LWLockRelease(lock);
349 : 168047 : return;
350 : : }
351 [ + - ]: 76 : else if (TransactionGroupUpdateXidStatus(xid, status, lsn, pageno))
352 : : {
353 : : /* Group update mechanism has done the work. */
354 : 76 : return;
355 : : }
356 : :
357 : : /* Fall through only if update isn't done yet. */
358 : : }
359 : :
360 : : /* Group update not applicable, or couldn't accept this page number. */
361 : 27516 : LWLockAcquire(lock, LW_EXCLUSIVE);
362 : 27516 : TransactionIdSetPageStatusInternal(xid, nsubxids, subxids, status,
363 : : lsn, pageno);
364 : 27516 : LWLockRelease(lock);
365 : : }
366 : :
367 : : /*
368 : : * Record the final state of transaction entry in the commit log
369 : : *
370 : : * We don't do any locking here; caller must handle that.
371 : : */
372 : : static void
373 : 195639 : TransactionIdSetPageStatusInternal(TransactionId xid, int nsubxids,
374 : : TransactionId *subxids, XidStatus status,
375 : : XLogRecPtr lsn, int64 pageno)
376 : : {
377 : : int slotno;
378 : : int i;
379 : :
380 : : Assert(status == TRANSACTION_STATUS_COMMITTED ||
381 : : status == TRANSACTION_STATUS_ABORTED ||
382 : : (status == TRANSACTION_STATUS_SUB_COMMITTED && !TransactionIdIsValid(xid)));
383 : : Assert(LWLockHeldByMeInMode(SimpleLruGetBankLock(XactCtl, pageno),
384 : : LW_EXCLUSIVE));
385 : :
386 : : /*
387 : : * If we're doing an async commit (ie, lsn is valid), then we must wait
388 : : * for any active write on the page slot to complete. Otherwise our
389 : : * update could reach disk in that write, which will not do since we
390 : : * mustn't let it reach disk until we've done the appropriate WAL flush.
391 : : * But when lsn is invalid, it's OK to scribble on a page while it is
392 : : * write-busy, since we don't care if the update reaches disk sooner than
393 : : * we think.
394 : : */
395 : 195639 : slotno = SimpleLruReadPage(XactCtl, pageno, !XLogRecPtrIsValid(lsn), &xid);
396 : :
397 : : /*
398 : : * Set the main transaction id, if any.
399 : : *
400 : : * If we update more than one xid on this page while it is being written
401 : : * out, we might find that some of the bits go to disk and others don't.
402 : : * If we are updating commits on the page with the top-level xid that
403 : : * could break atomicity, so we subcommit the subxids first before we mark
404 : : * the top-level commit.
405 : : */
406 [ + - ]: 195639 : if (TransactionIdIsValid(xid))
407 : : {
408 : : /* Subtransactions first, if needed ... */
409 [ + + ]: 195639 : if (status == TRANSACTION_STATUS_COMMITTED)
410 : : {
411 [ + + ]: 188804 : for (i = 0; i < nsubxids; i++)
412 : : {
413 : : Assert(XactCtl->shared->page_number[slotno] == TransactionIdToPage(subxids[i]));
414 : 4737 : TransactionIdSetStatusBit(subxids[i],
415 : : TRANSACTION_STATUS_SUB_COMMITTED,
416 : : lsn, slotno);
417 : : }
418 : : }
419 : :
420 : : /* ... then the main transaction */
421 : 195639 : TransactionIdSetStatusBit(xid, status, lsn, slotno);
422 : : }
423 : :
424 : : /* Set the subtransactions */
425 [ + + ]: 200703 : for (i = 0; i < nsubxids; i++)
426 : : {
427 : : Assert(XactCtl->shared->page_number[slotno] == TransactionIdToPage(subxids[i]));
428 : 5064 : TransactionIdSetStatusBit(subxids[i], status, lsn, slotno);
429 : : }
430 : :
431 : 195639 : XactCtl->shared->page_dirty[slotno] = true;
432 : 195639 : }
433 : :
434 : : /*
435 : : * Subroutine for TransactionIdSetPageStatus, q.v.
436 : : *
437 : : * When we cannot immediately acquire the SLRU bank lock in exclusive mode at
438 : : * commit time, add ourselves to a list of processes that need their XIDs
439 : : * status update. The first process to add itself to the list will acquire
440 : : * the lock in exclusive mode and set transaction status as required on behalf
441 : : * of all group members. This avoids a great deal of contention when many
442 : : * processes are trying to commit at once, since the lock need not be
443 : : * repeatedly handed off from one committing process to the next.
444 : : *
445 : : * Returns true when transaction status has been updated in clog; returns
446 : : * false if we decided against applying the optimization because the page
447 : : * number we need to update differs from those processes already waiting.
448 : : */
449 : : static bool
450 : 76 : TransactionGroupUpdateXidStatus(TransactionId xid, XidStatus status,
451 : : XLogRecPtr lsn, int64 pageno)
452 : : {
453 : 76 : PGPROC *proc = MyProc;
454 : : uint32 nextidx;
455 : : uint32 wakeidx;
456 : : int64 prevpageno;
457 : 76 : LWLock *prevlock = NULL;
458 : :
459 : : /* We should definitely have an XID whose status needs to be updated. */
460 : : Assert(TransactionIdIsValid(xid));
461 : :
462 : : /*
463 : : * Prepare to add ourselves to the list of processes needing a group XID
464 : : * status update.
465 : : */
466 : 76 : proc->clogGroupMember = true;
467 : 76 : proc->clogGroupMemberXid = xid;
468 : 76 : proc->clogGroupMemberXidStatus = status;
469 : 76 : proc->clogGroupMemberPage = pageno;
470 : 76 : proc->clogGroupMemberLsn = lsn;
471 : :
472 : : /*
473 : : * We put ourselves in the queue by writing MyProcNumber to
474 : : * ProcGlobal->clogGroupFirst. However, if there's already a process
475 : : * listed there, we compare our pageno with that of that process; if it
476 : : * differs, we cannot participate in the group, so we return for caller to
477 : : * update pg_xact in the normal way.
478 : : *
479 : : * If we're not the first process in the list, we must follow the leader.
480 : : * We do this by storing the data we want updated in our PGPROC entry
481 : : * where the leader can find it, then going to sleep.
482 : : *
483 : : * If no process is already in the list, we're the leader; our first step
484 : : * is to lock the SLRU bank to which our page belongs, then we close out
485 : : * the group by resetting the list pointer from ProcGlobal->clogGroupFirst
486 : : * (this lets other processes set up other groups later); finally we do
487 : : * the SLRU updates, release the SLRU bank lock, and wake up the sleeping
488 : : * processes.
489 : : *
490 : : * If another group starts to update a page in a different SLRU bank, they
491 : : * can proceed concurrently, since the bank lock they're going to use is
492 : : * different from ours. If another group starts to update a page in the
493 : : * same bank as ours, they wait until we release the lock.
494 : : */
495 : 76 : nextidx = pg_atomic_read_u32(&ProcGlobal->clogGroupFirst);
496 : :
497 : : while (true)
498 : : {
499 : : /*
500 : : * Add the proc to list, if the clog page where we need to update the
501 : : * current transaction status is same as group leader's clog page.
502 : : *
503 : : * There is a race condition here, which is that after doing the below
504 : : * check and before adding this proc's clog update to a group, the
505 : : * group leader might have already finished the group update for this
506 : : * page and becomes group leader of another group, updating a
507 : : * different page. This will lead to a situation where a single group
508 : : * can have different clog page updates. This isn't likely and will
509 : : * still work, just less efficiently -- we handle this case by
510 : : * switching to a different bank lock in the loop below.
511 : : */
512 [ + + ]: 76 : if (nextidx != INVALID_PROC_NUMBER &&
513 [ - + ]: 8 : GetPGProcByNumber(nextidx)->clogGroupMemberPage != proc->clogGroupMemberPage)
514 : : {
515 : : /*
516 : : * Ensure that this proc is not a member of any clog group that
517 : : * needs an XID status update.
518 : : */
519 : 0 : proc->clogGroupMember = false;
520 : 0 : pg_atomic_write_u32(&proc->clogGroupNext, INVALID_PROC_NUMBER);
521 : 0 : return false;
522 : : }
523 : :
524 : 76 : pg_atomic_write_u32(&proc->clogGroupNext, nextidx);
525 : :
526 [ + - ]: 76 : if (pg_atomic_compare_exchange_u32(&ProcGlobal->clogGroupFirst,
527 : : &nextidx,
528 : : (uint32) MyProcNumber))
529 : 76 : break;
530 : : }
531 : :
532 : : /*
533 : : * If the list was not empty, the leader will update the status of our
534 : : * XID. It is impossible to have followers without a leader because the
535 : : * first process that has added itself to the list will always have
536 : : * nextidx as INVALID_PROC_NUMBER.
537 : : */
538 [ + + ]: 76 : if (nextidx != INVALID_PROC_NUMBER)
539 : : {
540 : 8 : int extraWaits = 0;
541 : :
542 : : /* Sleep until the leader updates our XID status. */
543 : 8 : pgstat_report_wait_start(WAIT_EVENT_XACT_GROUP_UPDATE);
544 : : for (;;)
545 : : {
546 : : /* acts as a read barrier */
547 : 8 : PGSemaphoreLock(proc->sem);
548 [ + - ]: 8 : if (!proc->clogGroupMember)
549 : 8 : break;
550 : 0 : extraWaits++;
551 : : }
552 : 8 : pgstat_report_wait_end();
553 : :
554 : : Assert(pg_atomic_read_u32(&proc->clogGroupNext) == INVALID_PROC_NUMBER);
555 : :
556 : : /* Fix semaphore count for any absorbed wakeups */
557 [ - + ]: 8 : while (extraWaits-- > 0)
558 : 0 : PGSemaphoreUnlock(proc->sem);
559 : 8 : return true;
560 : : }
561 : :
562 : : /*
563 : : * By here, we know we're the leader process. Acquire the SLRU bank lock
564 : : * that corresponds to the page we originally wanted to modify.
565 : : */
566 : 68 : prevpageno = proc->clogGroupMemberPage;
567 : 68 : prevlock = SimpleLruGetBankLock(XactCtl, prevpageno);
568 : 68 : LWLockAcquire(prevlock, LW_EXCLUSIVE);
569 : :
570 : : /*
571 : : * Now that we've got the lock, clear the list of processes waiting for
572 : : * group XID status update, saving a pointer to the head of the list.
573 : : * (Trying to pop elements one at a time could lead to an ABA problem.)
574 : : *
575 : : * At this point, any processes trying to do this would create a separate
576 : : * group.
577 : : */
578 : 68 : nextidx = pg_atomic_exchange_u32(&ProcGlobal->clogGroupFirst,
579 : : INVALID_PROC_NUMBER);
580 : :
581 : : /* Remember head of list so we can perform wakeups after dropping lock. */
582 : 68 : wakeidx = nextidx;
583 : :
584 : : /* Walk the list and update the status of all XIDs. */
585 [ + + ]: 144 : while (nextidx != INVALID_PROC_NUMBER)
586 : : {
587 : 76 : PGPROC *nextproc = GetPGProcByNumber(nextidx);
588 : 76 : int64 thispageno = nextproc->clogGroupMemberPage;
589 : :
590 : : /*
591 : : * If the page to update belongs to a different bank than the previous
592 : : * one, exchange bank lock to the new one. This should be quite rare,
593 : : * as described above.
594 : : *
595 : : * (We could try to optimize this by waking up the processes for which
596 : : * we have already updated the status while we exchange the lock, but
597 : : * the code doesn't do that at present. I think it'd require
598 : : * additional bookkeeping, making the common path slower in order to
599 : : * improve an infrequent case.)
600 : : */
601 [ - + ]: 76 : if (thispageno != prevpageno)
602 : : {
603 : 0 : LWLock *lock = SimpleLruGetBankLock(XactCtl, thispageno);
604 : :
605 [ # # ]: 0 : if (prevlock != lock)
606 : : {
607 : 0 : LWLockRelease(prevlock);
608 : 0 : LWLockAcquire(lock, LW_EXCLUSIVE);
609 : : }
610 : 0 : prevlock = lock;
611 : 0 : prevpageno = thispageno;
612 : : }
613 : :
614 : : /*
615 : : * Transactions with more than THRESHOLD_SUBTRANS_CLOG_OPT sub-XIDs
616 : : * should not use group XID status update mechanism.
617 : : */
618 : : Assert(nextproc->subxidStatus.count <= THRESHOLD_SUBTRANS_CLOG_OPT);
619 : :
620 : 76 : TransactionIdSetPageStatusInternal(nextproc->clogGroupMemberXid,
621 : 76 : nextproc->subxidStatus.count,
622 : 76 : nextproc->subxids.xids,
623 : : nextproc->clogGroupMemberXidStatus,
624 : : nextproc->clogGroupMemberLsn,
625 : : nextproc->clogGroupMemberPage);
626 : :
627 : : /* Move to next proc in list. */
628 : 76 : nextidx = pg_atomic_read_u32(&nextproc->clogGroupNext);
629 : : }
630 : :
631 : : /* We're done with the lock now. */
632 [ + - ]: 68 : if (prevlock != NULL)
633 : 68 : LWLockRelease(prevlock);
634 : :
635 : : /*
636 : : * Now that we've released the lock, go back and wake everybody up. We
637 : : * don't do this under the lock so as to keep lock hold times to a
638 : : * minimum.
639 : : *
640 : : * (Perhaps we could do this in two passes, the first setting
641 : : * clogGroupNext to invalid while saving the semaphores to an array, then
642 : : * a single write barrier, then another pass unlocking the semaphores.)
643 : : */
644 [ + + ]: 144 : while (wakeidx != INVALID_PROC_NUMBER)
645 : : {
646 : 76 : PGPROC *wakeproc = GetPGProcByNumber(wakeidx);
647 : :
648 : 76 : wakeidx = pg_atomic_read_u32(&wakeproc->clogGroupNext);
649 : 76 : pg_atomic_write_u32(&wakeproc->clogGroupNext, INVALID_PROC_NUMBER);
650 : :
651 : : /* ensure all previous writes are visible before follower continues. */
652 : 76 : pg_write_barrier();
653 : :
654 : 76 : wakeproc->clogGroupMember = false;
655 : :
656 [ + + ]: 76 : if (wakeproc != MyProc)
657 : 8 : PGSemaphoreUnlock(wakeproc->sem);
658 : : }
659 : :
660 : 68 : return true;
661 : : }
662 : :
663 : : /*
664 : : * Sets the commit status of a single transaction.
665 : : *
666 : : * Caller must hold the corresponding SLRU bank lock, will be held at exit.
667 : : */
668 : : static void
669 : 205440 : TransactionIdSetStatusBit(TransactionId xid, XidStatus status, XLogRecPtr lsn, int slotno)
670 : : {
671 : 205440 : int byteno = TransactionIdToByte(xid);
672 : 205440 : int bshift = TransactionIdToBIndex(xid) * CLOG_BITS_PER_XACT;
673 : : char *byteptr;
674 : : char byteval;
675 : : char curval;
676 : :
677 : : Assert(XactCtl->shared->page_number[slotno] == TransactionIdToPage(xid));
678 : : Assert(LWLockHeldByMeInMode(SimpleLruGetBankLock(XactCtl,
679 : : XactCtl->shared->page_number[slotno]),
680 : : LW_EXCLUSIVE));
681 : :
682 : 205440 : byteptr = XactCtl->shared->page_buffer[slotno] + byteno;
683 : 205440 : curval = (*byteptr >> bshift) & CLOG_XACT_BITMASK;
684 : :
685 : : /*
686 : : * When replaying transactions during recovery we still need to perform
687 : : * the two phases of subcommit and then commit. However, some transactions
688 : : * are already correctly marked, so we just treat those as a no-op which
689 : : * allows us to keep the following Assert as restrictive as possible.
690 : : */
691 [ + + + + : 205440 : if (InRecovery && status == TRANSACTION_STATUS_SUB_COMMITTED &&
- + ]
692 : : curval == TRANSACTION_STATUS_COMMITTED)
693 : 0 : return;
694 : :
695 : : /*
696 : : * Current state change should be from 0 or subcommitted to target state
697 : : * or we should already be there when replaying changes during recovery.
698 : : */
699 : : Assert(curval == 0 ||
700 : : (curval == TRANSACTION_STATUS_SUB_COMMITTED &&
701 : : status != TRANSACTION_STATUS_IN_PROGRESS) ||
702 : : curval == status);
703 : :
704 : : /* note this assumes exclusive access to the clog page */
705 : 205440 : byteval = *byteptr;
706 : 205440 : byteval &= ~(((1 << CLOG_BITS_PER_XACT) - 1) << bshift);
707 : 205440 : byteval |= (status << bshift);
708 : 205440 : *byteptr = byteval;
709 : :
710 : : /*
711 : : * Update the group LSN if the transaction completion LSN is higher.
712 : : *
713 : : * Note: lsn will be invalid when supplied during InRecovery processing,
714 : : * so we don't need to do anything special to avoid LSN updates during
715 : : * recovery. After recovery completes the next clog change will set the
716 : : * LSN correctly.
717 : : */
718 [ + + ]: 205440 : if (XLogRecPtrIsValid(lsn))
719 : : {
720 : 31846 : int lsnindex = GetLSNIndex(slotno, xid);
721 : :
722 [ + + ]: 31846 : if (XactCtl->shared->group_lsn[lsnindex] < lsn)
723 : 29228 : XactCtl->shared->group_lsn[lsnindex] = lsn;
724 : : }
725 : : }
726 : :
727 : : /*
728 : : * Interrogate the state of a transaction in the commit log.
729 : : *
730 : : * Aside from the actual commit status, this function returns (into *lsn)
731 : : * an LSN that is late enough to be able to guarantee that if we flush up to
732 : : * that LSN then we will have flushed the transaction's commit record to disk.
733 : : * The result is not necessarily the exact LSN of the transaction's commit
734 : : * record! For example, for long-past transactions (those whose clog pages
735 : : * already migrated to disk), we'll return InvalidXLogRecPtr. Also, because
736 : : * we group transactions on the same clog page to conserve storage, we might
737 : : * return the LSN of a later transaction that falls into the same group.
738 : : *
739 : : * NB: this is a low-level routine and is NOT the preferred entry point
740 : : * for most uses; TransactionLogFetch() in transam.c is the intended caller.
741 : : */
742 : : XidStatus
743 : 902948 : TransactionIdGetStatus(TransactionId xid, XLogRecPtr *lsn)
744 : : {
745 : 902948 : int64 pageno = TransactionIdToPage(xid);
746 : 902948 : int byteno = TransactionIdToByte(xid);
747 : 902948 : int bshift = TransactionIdToBIndex(xid) * CLOG_BITS_PER_XACT;
748 : : int slotno;
749 : : int lsnindex;
750 : : char *byteptr;
751 : : XidStatus status;
752 : :
753 : : /* lock is acquired by SimpleLruReadPage_ReadOnly */
754 : :
755 : 902948 : slotno = SimpleLruReadPage_ReadOnly(XactCtl, pageno, &xid);
756 : 902948 : byteptr = XactCtl->shared->page_buffer[slotno] + byteno;
757 : :
758 : 902948 : status = (*byteptr >> bshift) & CLOG_XACT_BITMASK;
759 : :
760 : 902948 : lsnindex = GetLSNIndex(slotno, xid);
761 : 902948 : *lsn = XactCtl->shared->group_lsn[lsnindex];
762 : :
763 : 902948 : LWLockRelease(SimpleLruGetBankLock(XactCtl, pageno));
764 : :
765 : 902948 : return status;
766 : : }
767 : :
768 : : /*
769 : : * Number of shared CLOG buffers.
770 : : *
771 : : * If asked to autotune, use 2MB for every 1GB of shared buffers, up to 8MB.
772 : : * Otherwise just cap the configured amount to be between 16 and the maximum
773 : : * allowed.
774 : : */
775 : : static int
776 : 2493 : CLOGShmemBuffers(void)
777 : : {
778 : : /* auto-tune based on shared buffers */
779 [ + + ]: 2493 : if (transaction_buffers == 0)
780 : 1244 : return SimpleLruAutotuneBuffers(512, 1024);
781 : :
782 [ + - + - ]: 1249 : return Min(Max(16, transaction_buffers), CLOG_MAX_ALLOWED_BUFFERS);
783 : : }
784 : :
785 : : /*
786 : : * Register shared memory for CLOG
787 : : */
788 : : static void
789 : 1249 : CLOGShmemRequest(void *arg)
790 : : {
791 : : /* If auto-tuning is requested, now is the time to do it */
792 [ + + ]: 1249 : if (transaction_buffers == 0)
793 : : {
794 : : char buf[32];
795 : :
796 : 1244 : snprintf(buf, sizeof(buf), "%d", CLOGShmemBuffers());
797 : 1244 : SetConfigOption("transaction_buffers", buf, PGC_POSTMASTER,
798 : : PGC_S_DYNAMIC_DEFAULT);
799 : :
800 : : /*
801 : : * We prefer to report this value's source as PGC_S_DYNAMIC_DEFAULT.
802 : : * However, if the DBA explicitly set transaction_buffers = 0 in the
803 : : * config file, then PGC_S_DYNAMIC_DEFAULT will fail to override that
804 : : * and we must force the matter with PGC_S_OVERRIDE.
805 : : */
806 [ - + ]: 1244 : if (transaction_buffers == 0) /* failed to apply it? */
807 : 0 : SetConfigOption("transaction_buffers", buf, PGC_POSTMASTER,
808 : : PGC_S_OVERRIDE);
809 : : }
810 : : Assert(transaction_buffers != 0);
811 : 1249 : SimpleLruRequest(.desc = &XactSlruDesc,
812 : : .name = "transaction",
813 : : .Dir = "pg_xact",
814 : : .long_segment_names = false,
815 : :
816 : : .nslots = CLOGShmemBuffers(),
817 : : .nlsns = CLOG_LSNS_PER_PAGE,
818 : :
819 : : .sync_handler = SYNC_HANDLER_CLOG,
820 : : .PagePrecedes = CLOGPagePrecedes,
821 : : .errdetail_for_io_error = clog_errdetail_for_io_error,
822 : :
823 : : .buffer_tranche_id = LWTRANCHE_XACT_BUFFER,
824 : : .bank_tranche_id = LWTRANCHE_XACT_SLRU,
825 : : );
826 : 1249 : }
827 : :
828 : : static void
829 : 1246 : CLOGShmemInit(void *arg)
830 : : {
831 : : SlruPagePrecedesUnitTests(XactCtl, CLOG_XACTS_PER_PAGE);
832 : 1246 : }
833 : :
834 : : /*
835 : : * GUC check_hook for transaction_buffers
836 : : */
837 : : bool
838 : 2534 : check_transaction_buffers(int *newval, void **extra, GucSource source)
839 : : {
840 : 2534 : return check_slru_buffers("transaction_buffers", newval);
841 : : }
842 : :
843 : : /*
844 : : * This func must be called ONCE on system install. It creates
845 : : * the initial CLOG segment. (The CLOG directory is assumed to
846 : : * have been created by initdb, and CLOGShmemInit must have been
847 : : * called already.)
848 : : */
849 : : void
850 : 57 : BootStrapCLOG(void)
851 : : {
852 : : /* Zero the initial page and flush it to disk */
853 : 57 : SimpleLruZeroAndWritePage(XactCtl, 0);
854 : 57 : }
855 : :
856 : : /*
857 : : * This must be called ONCE during postmaster or standalone-backend startup,
858 : : * after StartupXLOG has initialized TransamVariables->nextXid.
859 : : */
860 : : void
861 : 1085 : StartupCLOG(void)
862 : : {
863 : 1085 : TransactionId xid = XidFromFullTransactionId(TransamVariables->nextXid);
864 : 1085 : int64 pageno = TransactionIdToPage(xid);
865 : :
866 : : /*
867 : : * Initialize our idea of the latest page number.
868 : : */
869 : 1085 : pg_atomic_write_u64(&XactCtl->shared->latest_page_number, pageno);
870 : 1085 : }
871 : :
872 : : /*
873 : : * This must be called ONCE at the end of startup/recovery.
874 : : */
875 : : void
876 : 1019 : TrimCLOG(void)
877 : : {
878 : 1019 : TransactionId xid = XidFromFullTransactionId(TransamVariables->nextXid);
879 : 1019 : int64 pageno = TransactionIdToPage(xid);
880 : 1019 : LWLock *lock = SimpleLruGetBankLock(XactCtl, pageno);
881 : :
882 : 1019 : LWLockAcquire(lock, LW_EXCLUSIVE);
883 : :
884 : : /*
885 : : * Zero out the remainder of the current clog page. Under normal
886 : : * circumstances it should be zeroes already, but it seems at least
887 : : * theoretically possible that XLOG replay will have settled on a nextXID
888 : : * value that is less than the last XID actually used and marked by the
889 : : * previous database lifecycle (since subtransaction commit writes clog
890 : : * but makes no WAL entry). Let's just be safe. (We need not worry about
891 : : * pages beyond the current one, since those will be zeroed when first
892 : : * used. For the same reason, there is no need to do anything when
893 : : * nextXid is exactly at a page boundary; and it's likely that the
894 : : * "current" page doesn't exist yet in that case.)
895 : : */
896 [ + + ]: 1019 : if (TransactionIdToPgIndex(xid) != 0)
897 : : {
898 : 1018 : int byteno = TransactionIdToByte(xid);
899 : 1018 : int bshift = TransactionIdToBIndex(xid) * CLOG_BITS_PER_XACT;
900 : : int slotno;
901 : : char *byteptr;
902 : :
903 : 1018 : slotno = SimpleLruReadPage(XactCtl, pageno, false, &xid);
904 : 1018 : byteptr = XactCtl->shared->page_buffer[slotno] + byteno;
905 : :
906 : : /* Zero so-far-unused positions in the current byte */
907 : 1018 : *byteptr &= (1 << bshift) - 1;
908 : : /* Zero the rest of the page */
909 [ + + + - : 1018 : MemSet(byteptr + 1, 0, BLCKSZ - byteno - 1);
+ - - + -
- ]
910 : :
911 : 1018 : XactCtl->shared->page_dirty[slotno] = true;
912 : : }
913 : :
914 : 1019 : LWLockRelease(lock);
915 : 1019 : }
916 : :
917 : : /*
918 : : * Perform a checkpoint --- either during shutdown, or on-the-fly
919 : : */
920 : : void
921 : 1948 : CheckPointCLOG(void)
922 : : {
923 : : /*
924 : : * Write dirty CLOG pages to disk. This may result in sync requests
925 : : * queued for later handling by ProcessSyncRequests(), as part of the
926 : : * checkpoint.
927 : : */
928 : : TRACE_POSTGRESQL_CLOG_CHECKPOINT_START(true);
929 : 1948 : SimpleLruWriteAll(XactCtl, true);
930 : : TRACE_POSTGRESQL_CLOG_CHECKPOINT_DONE(true);
931 : 1948 : }
932 : :
933 : :
934 : : /*
935 : : * Make sure that CLOG has room for a newly-allocated XID.
936 : : *
937 : : * NB: this is called while holding XidGenLock. We want it to be very fast
938 : : * most of the time; even when it's not so fast, no actual I/O need happen
939 : : * unless we're forced to write out a dirty clog or xlog page to make room
940 : : * in shared memory.
941 : : */
942 : : void
943 : 24545498 : ExtendCLOG(TransactionId newestXact)
944 : : {
945 : : int64 pageno;
946 : : LWLock *lock;
947 : :
948 : : /*
949 : : * No work except at first XID of a page. But beware: just after
950 : : * wraparound, the first XID of page zero is FirstNormalTransactionId.
951 : : */
952 [ + + + + ]: 24545498 : if (TransactionIdToPgIndex(newestXact) != 0 &&
953 : : !TransactionIdEquals(newestXact, FirstNormalTransactionId))
954 : 24113484 : return;
955 : :
956 : 432014 : pageno = TransactionIdToPage(newestXact);
957 : 432014 : lock = SimpleLruGetBankLock(XactCtl, pageno);
958 : :
959 : 432014 : LWLockAcquire(lock, LW_EXCLUSIVE);
960 : :
961 : : /* Zero the page and make a WAL entry about it */
962 : 432014 : SimpleLruZeroPage(XactCtl, pageno);
963 : 432014 : XLogSimpleInsertInt64(RM_CLOG_ID, CLOG_ZEROPAGE, pageno);
964 : :
965 : 432014 : LWLockRelease(lock);
966 : : }
967 : :
968 : :
969 : : /*
970 : : * Remove all CLOG segments before the one holding the passed transaction ID
971 : : *
972 : : * Before removing any CLOG data, we must flush XLOG to disk, to ensure that
973 : : * any recently-emitted records with freeze plans have reached disk; otherwise
974 : : * a crash and restart might leave us with some unfrozen tuples referencing
975 : : * removed CLOG data. We choose to emit a special TRUNCATE XLOG record too.
976 : : * Replaying the deletion from XLOG is not critical, since the files could
977 : : * just as well be removed later, but doing so prevents a long-running hot
978 : : * standby server from acquiring an unreasonably bloated CLOG directory.
979 : : *
980 : : * Since CLOG segments hold a large number of transactions, the opportunity to
981 : : * actually remove a segment is fairly rare, and so it seems best not to do
982 : : * the XLOG flush unless we have confirmed that there is a removable segment.
983 : : */
984 : : void
985 : 1142 : TruncateCLOG(TransactionId oldestXact, Oid oldestxid_datoid)
986 : : {
987 : : int64 cutoffPage;
988 : :
989 : : /*
990 : : * The cutoff point is the start of the segment containing oldestXact. We
991 : : * pass the *page* containing oldestXact to SimpleLruTruncate.
992 : : */
993 : 1142 : cutoffPage = TransactionIdToPage(oldestXact);
994 : :
995 : : /* Check to see if there's any files that could be removed */
996 [ + + ]: 1142 : if (!SlruScanDirectory(XactCtl, SlruScanDirCbReportPresence, &cutoffPage))
997 : 1045 : return; /* nothing to remove */
998 : :
999 : : /*
1000 : : * Advance oldestClogXid before truncating clog, so concurrent xact status
1001 : : * lookups can ensure they don't attempt to access truncated-away clog.
1002 : : *
1003 : : * It's only necessary to do this if we will actually truncate away clog
1004 : : * pages.
1005 : : */
1006 : 97 : AdvanceOldestClogXid(oldestXact);
1007 : :
1008 : : /*
1009 : : * Write XLOG record and flush XLOG to disk. We record the oldest xid
1010 : : * we're keeping information about here so we can ensure that it's always
1011 : : * ahead of clog truncation in case we crash, and so a standby finds out
1012 : : * the new valid xid before the next checkpoint.
1013 : : */
1014 : 97 : WriteTruncateXlogRec(cutoffPage, oldestXact, oldestxid_datoid);
1015 : :
1016 : : /* Now we can remove the old CLOG segment(s) */
1017 : 97 : SimpleLruTruncate(XactCtl, cutoffPage);
1018 : : }
1019 : :
1020 : :
1021 : : /*
1022 : : * Decide whether a CLOG page number is "older" for truncation purposes.
1023 : : *
1024 : : * We need to use comparison of TransactionIds here in order to do the right
1025 : : * thing with wraparound XID arithmetic. However, TransactionIdPrecedes()
1026 : : * would get weird about permanent xact IDs. So, offset both such that xid1,
1027 : : * xid2, and xid2 + CLOG_XACTS_PER_PAGE - 1 are all normal XIDs; this offset
1028 : : * is relevant to page 0 and to the page preceding page 0.
1029 : : *
1030 : : * The page containing oldestXact-2^31 is the important edge case. The
1031 : : * portion of that page equaling or following oldestXact-2^31 is expendable,
1032 : : * but the portion preceding oldestXact-2^31 is not. When oldestXact-2^31 is
1033 : : * the first XID of a page and segment, the entire page and segment is
1034 : : * expendable, and we could truncate the segment. Recognizing that case would
1035 : : * require making oldestXact, not just the page containing oldestXact,
1036 : : * available to this callback. The benefit would be rare and small, so we
1037 : : * don't optimize that edge case.
1038 : : */
1039 : : static bool
1040 : 982280 : CLOGPagePrecedes(int64 page1, int64 page2)
1041 : : {
1042 : : TransactionId xid1;
1043 : : TransactionId xid2;
1044 : :
1045 : 982280 : xid1 = ((TransactionId) page1) * CLOG_XACTS_PER_PAGE;
1046 : 982280 : xid1 += FirstNormalTransactionId + 1;
1047 : 982280 : xid2 = ((TransactionId) page2) * CLOG_XACTS_PER_PAGE;
1048 : 982280 : xid2 += FirstNormalTransactionId + 1;
1049 : :
1050 [ + + + - ]: 1009185 : return (TransactionIdPrecedes(xid1, xid2) &&
1051 : 26905 : TransactionIdPrecedes(xid1, xid2 + CLOG_XACTS_PER_PAGE - 1));
1052 : : }
1053 : :
1054 : : static int
1055 : 0 : clog_errdetail_for_io_error(const void *opaque_data)
1056 : : {
1057 : 0 : TransactionId xid = *(const TransactionId *) opaque_data;
1058 : :
1059 : 0 : return errdetail("Could not access commit status of transaction %u.", xid);
1060 : : }
1061 : :
1062 : :
1063 : : /*
1064 : : * Write a TRUNCATE xlog record
1065 : : *
1066 : : * We must flush the xlog record to disk before returning --- see notes
1067 : : * in TruncateCLOG().
1068 : : */
1069 : : static void
1070 : 97 : WriteTruncateXlogRec(int64 pageno, TransactionId oldestXact, Oid oldestXactDb)
1071 : : {
1072 : : XLogRecPtr recptr;
1073 : : xl_clog_truncate xlrec;
1074 : :
1075 : 97 : xlrec.pageno = pageno;
1076 : 97 : xlrec.oldestXact = oldestXact;
1077 : 97 : xlrec.oldestXactDb = oldestXactDb;
1078 : :
1079 : 97 : XLogBeginInsert();
1080 : 97 : XLogRegisterData(&xlrec, sizeof(xl_clog_truncate));
1081 : 97 : recptr = XLogInsert(RM_CLOG_ID, CLOG_TRUNCATE);
1082 : 97 : XLogFlush(recptr);
1083 : 97 : }
1084 : :
1085 : : /*
1086 : : * CLOG resource manager's routines
1087 : : */
1088 : : void
1089 : 0 : clog_redo(XLogReaderState *record)
1090 : : {
1091 : 0 : uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
1092 : :
1093 : : /* Backup blocks are not used in clog records */
1094 : : Assert(!XLogRecHasAnyBlockRefs(record));
1095 : :
1096 [ # # ]: 0 : if (info == CLOG_ZEROPAGE)
1097 : : {
1098 : : int64 pageno;
1099 : :
1100 : 0 : memcpy(&pageno, XLogRecGetData(record), sizeof(pageno));
1101 : 0 : SimpleLruZeroAndWritePage(XactCtl, pageno);
1102 : : }
1103 [ # # ]: 0 : else if (info == CLOG_TRUNCATE)
1104 : : {
1105 : : xl_clog_truncate xlrec;
1106 : :
1107 : 0 : memcpy(&xlrec, XLogRecGetData(record), sizeof(xl_clog_truncate));
1108 : :
1109 : 0 : AdvanceOldestClogXid(xlrec.oldestXact);
1110 : :
1111 : 0 : SimpleLruTruncate(XactCtl, xlrec.pageno);
1112 : : }
1113 : : else
1114 [ # # ]: 0 : elog(PANIC, "clog_redo: unknown op code %u", info);
1115 : 0 : }
1116 : :
1117 : : /*
1118 : : * Entrypoint for sync.c to sync clog files.
1119 : : */
1120 : : int
1121 : 0 : clogsyncfiletag(const FileTag *ftag, char *path)
1122 : : {
1123 : 0 : return SlruSyncFileTag(XactCtl, ftag, path);
1124 : : }
|