Line data Source code
1 : /*--------------------------------------------------------------------------
2 : *
3 : * xid_wraparound.c
4 : * Utilities for testing XID wraparound
5 : *
6 : *
7 : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
8 : * Portions Copyright (c) 1994, Regents of the University of California
9 : *
10 : * IDENTIFICATION
11 : * src/test/modules/xid_wraparound/xid_wraparound.c
12 : *
13 : * -------------------------------------------------------------------------
14 : */
15 : #include "postgres.h"
16 :
17 : #include "access/xact.h"
18 : #include "miscadmin.h"
19 : #include "storage/proc.h"
20 : #include "utils/xid8.h"
21 :
22 0 : PG_MODULE_MAGIC;
23 :
24 : static int64 consume_xids_shortcut(void);
25 : static FullTransactionId consume_xids_common(FullTransactionId untilxid, uint64 nxids);
26 :
27 : /*
28 : * Consume the specified number of XIDs.
29 : */
30 0 : PG_FUNCTION_INFO_V1(consume_xids);
31 : Datum
32 0 : consume_xids(PG_FUNCTION_ARGS)
33 : {
34 0 : int64 nxids = PG_GETARG_INT64(0);
35 0 : FullTransactionId lastxid;
36 :
37 0 : if (nxids < 0)
38 0 : elog(ERROR, "invalid nxids argument: %" PRId64, nxids);
39 :
40 0 : if (nxids == 0)
41 0 : lastxid = ReadNextFullTransactionId();
42 : else
43 0 : lastxid = consume_xids_common(InvalidFullTransactionId, (uint64) nxids);
44 :
45 0 : PG_RETURN_FULLTRANSACTIONID(lastxid);
46 0 : }
47 :
48 : /*
49 : * Consume XIDs, up to the given XID.
50 : */
51 0 : PG_FUNCTION_INFO_V1(consume_xids_until);
52 : Datum
53 0 : consume_xids_until(PG_FUNCTION_ARGS)
54 : {
55 0 : FullTransactionId targetxid = PG_GETARG_FULLTRANSACTIONID(0);
56 0 : FullTransactionId lastxid;
57 :
58 0 : if (!FullTransactionIdIsNormal(targetxid))
59 0 : elog(ERROR, "targetxid %" PRIu64 " is not normal",
60 : U64FromFullTransactionId(targetxid));
61 :
62 0 : lastxid = consume_xids_common(targetxid, 0);
63 :
64 0 : PG_RETURN_FULLTRANSACTIONID(lastxid);
65 0 : }
66 :
67 : /*
68 : * Common functionality between the two public functions.
69 : */
70 : static FullTransactionId
71 0 : consume_xids_common(FullTransactionId untilxid, uint64 nxids)
72 : {
73 : FullTransactionId lastxid;
74 0 : uint64 last_reported_at = 0;
75 0 : uint64 consumed = 0;
76 :
77 : /* Print a NOTICE every REPORT_INTERVAL xids */
78 : #define REPORT_INTERVAL (10 * 1000000)
79 :
80 : /* initialize 'lastxid' with the system's current next XID */
81 0 : lastxid = ReadNextFullTransactionId();
82 :
83 : /*
84 : * We consume XIDs by calling GetNewTransactionId(true), which marks the
85 : * consumed XIDs as subtransactions of the current top-level transaction.
86 : * For that to work, this transaction must have a top-level XID.
87 : *
88 : * GetNewTransactionId registers them in the subxid cache in PGPROC, until
89 : * the cache overflows, but beyond that, we don't keep track of the
90 : * consumed XIDs.
91 : */
92 0 : (void) GetTopTransactionId();
93 :
94 0 : for (;;)
95 : {
96 0 : uint64 xids_left;
97 :
98 0 : CHECK_FOR_INTERRUPTS();
99 :
100 : /* How many XIDs do we have left to consume? */
101 0 : if (nxids > 0)
102 : {
103 0 : if (consumed >= nxids)
104 0 : break;
105 0 : xids_left = nxids - consumed;
106 0 : }
107 : else
108 : {
109 0 : if (FullTransactionIdFollowsOrEquals(lastxid, untilxid))
110 0 : break;
111 0 : xids_left = U64FromFullTransactionId(untilxid) - U64FromFullTransactionId(lastxid);
112 : }
113 :
114 : /*
115 : * If we still have plenty of XIDs to consume, try to take a shortcut
116 : * and bump up the nextXid counter directly.
117 : */
118 0 : if (xids_left > 2000 &&
119 0 : consumed - last_reported_at < REPORT_INTERVAL &&
120 0 : MyProc->subxidStatus.overflowed)
121 : {
122 0 : int64 consumed_by_shortcut = consume_xids_shortcut();
123 :
124 0 : if (consumed_by_shortcut > 0)
125 : {
126 0 : consumed += consumed_by_shortcut;
127 0 : continue;
128 : }
129 0 : }
130 :
131 : /* Slow path: Call GetNewTransactionId to allocate a new XID. */
132 0 : lastxid = GetNewTransactionId(true);
133 0 : consumed++;
134 :
135 : /* Report progress */
136 0 : if (consumed - last_reported_at >= REPORT_INTERVAL)
137 : {
138 0 : if (nxids > 0)
139 0 : elog(NOTICE, "consumed %" PRIu64 " / %" PRIu64 " XIDs, latest %u:%u",
140 : consumed, nxids,
141 : EpochFromFullTransactionId(lastxid),
142 : XidFromFullTransactionId(lastxid));
143 : else
144 0 : elog(NOTICE, "consumed up to %u:%u / %u:%u",
145 : EpochFromFullTransactionId(lastxid),
146 : XidFromFullTransactionId(lastxid),
147 : EpochFromFullTransactionId(untilxid),
148 : XidFromFullTransactionId(untilxid));
149 0 : last_reported_at = consumed;
150 0 : }
151 0 : }
152 :
153 : return lastxid;
154 0 : }
155 :
156 : /*
157 : * These constants copied from .c files, because they're private.
158 : */
159 : #define COMMIT_TS_XACTS_PER_PAGE (BLCKSZ / 10)
160 : #define SUBTRANS_XACTS_PER_PAGE (BLCKSZ / sizeof(TransactionId))
161 : #define CLOG_XACTS_PER_BYTE 4
162 : #define CLOG_XACTS_PER_PAGE (BLCKSZ * CLOG_XACTS_PER_BYTE)
163 :
164 : /*
165 : * All the interesting action in GetNewTransactionId happens when we extend
166 : * the SLRUs, or at the uint32 wraparound. If the nextXid counter is not close
167 : * to any of those interesting values, take a shortcut and bump nextXID
168 : * directly, close to the next "interesting" value.
169 : */
170 : static inline uint32
171 0 : XidSkip(FullTransactionId fullxid)
172 : {
173 0 : uint32 low = XidFromFullTransactionId(fullxid);
174 0 : uint32 rem;
175 0 : uint32 distance;
176 :
177 0 : if (low < 5 || low >= UINT32_MAX - 5)
178 0 : return 0;
179 0 : distance = UINT32_MAX - 5 - low;
180 :
181 0 : rem = low % COMMIT_TS_XACTS_PER_PAGE;
182 0 : if (rem == 0)
183 0 : return 0;
184 0 : distance = Min(distance, COMMIT_TS_XACTS_PER_PAGE - rem);
185 :
186 0 : rem = low % SUBTRANS_XACTS_PER_PAGE;
187 0 : if (rem == 0)
188 0 : return 0;
189 0 : distance = Min(distance, SUBTRANS_XACTS_PER_PAGE - rem);
190 :
191 0 : rem = low % CLOG_XACTS_PER_PAGE;
192 0 : if (rem == 0)
193 0 : return 0;
194 0 : distance = Min(distance, CLOG_XACTS_PER_PAGE - rem);
195 :
196 0 : return distance;
197 0 : }
198 :
199 : static int64
200 0 : consume_xids_shortcut(void)
201 : {
202 0 : FullTransactionId nextXid;
203 0 : uint32 consumed;
204 :
205 0 : LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
206 0 : nextXid = TransamVariables->nextXid;
207 :
208 : /*
209 : * Go slow near the "interesting values". The interesting zones include 5
210 : * transactions before and after SLRU page switches.
211 : */
212 0 : consumed = XidSkip(nextXid);
213 0 : if (consumed > 0)
214 0 : TransamVariables->nextXid.value += (uint64) consumed;
215 :
216 0 : LWLockRelease(XidGenLock);
217 :
218 0 : return consumed;
219 0 : }
|