Age Owner Branch data TLA Line data Source code
1 : : /*--------------------------------------------------------------------------
2 : : *
3 : : * xid_wraparound.c
4 : : * Utilities for testing XID wraparound
5 : : *
6 : : *
7 : : * Portions Copyright (c) 1996-2025, 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 : :
646 msawada@postgresql.o 22 :UBC 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 : : FullTransactionId lastxid;
36 : :
37 [ # # ]: 0 : if (nxids < 0)
161 peter@eisentraut.org 38 [ # # ]: 0 : elog(ERROR, "invalid nxids argument: %" PRId64, nxids);
39 : :
646 msawada@postgresql.o 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 : : }
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 : : FullTransactionId lastxid;
57 : :
58 [ # # ]: 0 : if (!FullTransactionIdIsNormal(targetxid))
161 peter@eisentraut.org 59 [ # # ]: 0 : elog(ERROR, "targetxid %" PRIu64 " is not normal",
60 : : U64FromFullTransactionId(targetxid));
61 : :
646 msawada@postgresql.o 62 : 0 : lastxid = consume_xids_common(targetxid, 0);
63 : :
64 : 0 : PG_RETURN_FULLTRANSACTIONID(lastxid);
65 : : }
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 : : for (;;)
95 : 0 : {
96 : : 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 : : }
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 : : }
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)
161 peter@eisentraut.org 139 [ # # ]: 0 : elog(NOTICE, "consumed %" PRIu64 " / %" PRIu64 " XIDs, latest %u:%u",
140 : : consumed, nxids,
141 : : EpochFromFullTransactionId(lastxid),
142 : : XidFromFullTransactionId(lastxid));
143 : : else
646 msawada@postgresql.o 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 : : }
151 : : }
152 : :
153 : 0 : return lastxid;
154 : : }
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 : : uint32 rem;
175 : : 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 : : }
198 : :
199 : : static int64
200 : 0 : consume_xids_shortcut(void)
201 : : {
202 : : FullTransactionId nextXid;
203 : : uint32 consumed;
204 : :
205 : 0 : LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
638 heikki.linnakangas@i 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 : : */
646 msawada@postgresql.o 212 : 0 : consumed = XidSkip(nextXid);
213 [ # # ]: 0 : if (consumed > 0)
638 heikki.linnakangas@i 214 : 0 : TransamVariables->nextXid.value += (uint64) consumed;
215 : :
646 msawada@postgresql.o 216 : 0 : LWLockRelease(XidGenLock);
217 : :
218 : 0 : return consumed;
219 : : }
|