Line data Source code
1 : /*-------------------------------------------------------------------------
2 : * xid8funcs.c
3 : *
4 : * Export internal transaction IDs to user level.
5 : *
6 : * Note that only top-level transaction IDs are exposed to user sessions.
7 : * This is important because xid8s frequently persist beyond the global
8 : * xmin horizon, or may even be shipped to other machines, so we cannot
9 : * rely on being able to correlate subtransaction IDs with their parents
10 : * via functions such as SubTransGetTopmostTransaction().
11 : *
12 : * These functions are used to support the txid_XXX functions and the newer
13 : * pg_current_xact_id, pg_current_snapshot and related fmgr functions, since
14 : * the only difference between them is whether they expose xid8 or int8 values
15 : * to users. The txid_XXX variants should eventually be dropped.
16 : *
17 : *
18 : * Copyright (c) 2003-2025, PostgreSQL Global Development Group
19 : * Author: Jan Wieck, Afilias USA INC.
20 : * 64-bit txids: Marko Kreen, Skype Technologies
21 : *
22 : * src/backend/utils/adt/xid8funcs.c
23 : *
24 : *-------------------------------------------------------------------------
25 : */
26 :
27 : #include "postgres.h"
28 :
29 : #include "access/transam.h"
30 : #include "access/xact.h"
31 : #include "funcapi.h"
32 : #include "lib/qunique.h"
33 : #include "libpq/pqformat.h"
34 : #include "miscadmin.h"
35 : #include "storage/lwlock.h"
36 : #include "storage/procarray.h"
37 : #include "storage/procnumber.h"
38 : #include "utils/builtins.h"
39 : #include "utils/memutils.h"
40 : #include "utils/snapmgr.h"
41 : #include "utils/xid8.h"
42 : #include "varatt.h"
43 :
44 :
45 : /*
46 : * If defined, use bsearch() function for searching for xid8s in snapshots
47 : * that have more than the specified number of values.
48 : */
49 : #define USE_BSEARCH_IF_NXIP_GREATER 30
50 :
51 :
52 : /*
53 : * Snapshot containing FullTransactionIds.
54 : */
55 : typedef struct
56 : {
57 : /*
58 : * 4-byte length hdr, should not be touched directly.
59 : *
60 : * Explicit embedding is ok as we want always correct alignment anyway.
61 : */
62 : int32 __varsz;
63 :
64 : uint32 nxip; /* number of fxids in xip array */
65 : FullTransactionId xmin;
66 : FullTransactionId xmax;
67 : /* in-progress fxids, xmin <= xip[i] < xmax: */
68 : FullTransactionId xip[FLEXIBLE_ARRAY_MEMBER];
69 : } pg_snapshot;
70 :
71 : #define PG_SNAPSHOT_SIZE(nxip) \
72 : (offsetof(pg_snapshot, xip) + sizeof(FullTransactionId) * (nxip))
73 : #define PG_SNAPSHOT_MAX_NXIP \
74 : ((MaxAllocSize - offsetof(pg_snapshot, xip)) / sizeof(FullTransactionId))
75 :
76 : /*
77 : * Compile-time limits on the procarray (MAX_BACKENDS processes plus
78 : * MAX_BACKENDS prepared transactions) guarantee nxip won't be too large.
79 : */
80 : StaticAssertDecl(MAX_BACKENDS * 2 <= PG_SNAPSHOT_MAX_NXIP,
81 : "possible overflow in pg_current_snapshot()");
82 :
83 :
84 : /*
85 : * Helper to get a TransactionId from a 64-bit xid with wraparound detection.
86 : *
87 : * It is an ERROR if the xid is in the future. Otherwise, returns true if
88 : * the transaction is still new enough that we can determine whether it
89 : * committed and false otherwise. If *extracted_xid is not NULL, it is set
90 : * to the low 32 bits of the transaction ID (i.e. the actual XID, without the
91 : * epoch).
92 : *
93 : * The caller must hold XactTruncationLock since it's dealing with arbitrary
94 : * XIDs, and must continue to hold it until it's done with any clog lookups
95 : * relating to those XIDs.
96 : */
97 : static bool
98 84 : TransactionIdInRecentPast(FullTransactionId fxid, TransactionId *extracted_xid)
99 : {
100 84 : TransactionId xid = XidFromFullTransactionId(fxid);
101 : FullTransactionId now_fullxid;
102 : TransactionId oldest_clog_xid;
103 : FullTransactionId oldest_clog_fxid;
104 :
105 84 : now_fullxid = ReadNextFullTransactionId();
106 :
107 84 : if (extracted_xid != NULL)
108 84 : *extracted_xid = xid;
109 :
110 84 : if (!TransactionIdIsValid(xid))
111 0 : return false;
112 :
113 : /* For non-normal transaction IDs, we can ignore the epoch. */
114 84 : if (!TransactionIdIsNormal(xid))
115 24 : return true;
116 :
117 : /* If the transaction ID is in the future, throw an error. */
118 60 : if (!FullTransactionIdPrecedes(fxid, now_fullxid))
119 12 : ereport(ERROR,
120 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
121 : errmsg("transaction ID %" PRIu64 " is in the future",
122 : U64FromFullTransactionId(fxid))));
123 :
124 : /*
125 : * TransamVariables->oldestClogXid is protected by XactTruncationLock, but
126 : * we don't acquire that lock here. Instead, we require the caller to
127 : * acquire it, because the caller is presumably going to look up the
128 : * returned XID. If we took and released the lock within this function, a
129 : * CLOG truncation could occur before the caller finished with the XID.
130 : */
131 : Assert(LWLockHeldByMe(XactTruncationLock));
132 :
133 : /*
134 : * If fxid is not older than TransamVariables->oldestClogXid, the relevant
135 : * CLOG entry is guaranteed to still exist.
136 : *
137 : * TransamVariables->oldestXid governs allowable XIDs. Usually,
138 : * oldestClogXid==oldestXid. It's also possible for oldestClogXid to
139 : * follow oldestXid, in which case oldestXid might advance after our
140 : * ReadNextFullTransactionId() call. If oldestXid has advanced, that
141 : * advancement reinstated the usual oldestClogXid==oldestXid. Whether or
142 : * not that happened, oldestClogXid is allowable relative to now_fullxid.
143 : */
144 48 : oldest_clog_xid = TransamVariables->oldestClogXid;
145 : oldest_clog_fxid =
146 48 : FullTransactionIdFromAllowableAt(now_fullxid, oldest_clog_xid);
147 48 : return !FullTransactionIdPrecedes(fxid, oldest_clog_fxid);
148 : }
149 :
150 : /*
151 : * txid comparator for qsort/bsearch
152 : */
153 : static int
154 2786 : cmp_fxid(const void *aa, const void *bb)
155 : {
156 2786 : FullTransactionId a = *(const FullTransactionId *) aa;
157 2786 : FullTransactionId b = *(const FullTransactionId *) bb;
158 :
159 2786 : if (FullTransactionIdPrecedes(a, b))
160 678 : return -1;
161 2108 : if (FullTransactionIdPrecedes(b, a))
162 1736 : return 1;
163 372 : return 0;
164 : }
165 :
166 : /*
167 : * Sort a snapshot's txids, so we can use bsearch() later. Also remove
168 : * any duplicates.
169 : *
170 : * For consistency of on-disk representation, we always sort even if bsearch
171 : * will not be used.
172 : */
173 : static void
174 40 : sort_snapshot(pg_snapshot *snap)
175 : {
176 40 : if (snap->nxip > 1)
177 : {
178 18 : qsort(snap->xip, snap->nxip, sizeof(FullTransactionId), cmp_fxid);
179 18 : snap->nxip = qunique(snap->xip, snap->nxip, sizeof(FullTransactionId),
180 : cmp_fxid);
181 : }
182 40 : }
183 :
184 : /*
185 : * check fxid visibility.
186 : */
187 : static bool
188 1020 : is_visible_fxid(FullTransactionId value, const pg_snapshot *snap)
189 : {
190 1020 : if (FullTransactionIdPrecedes(value, snap->xmin))
191 132 : return true;
192 888 : else if (!FullTransactionIdPrecedes(value, snap->xmax))
193 168 : return false;
194 : #ifdef USE_BSEARCH_IF_NXIP_GREATER
195 720 : else if (snap->nxip > USE_BSEARCH_IF_NXIP_GREATER)
196 : {
197 : void *res;
198 :
199 600 : res = bsearch(&value, snap->xip, snap->nxip, sizeof(FullTransactionId),
200 : cmp_fxid);
201 : /* if found, transaction is still in progress */
202 600 : return (res) ? false : true;
203 : }
204 : #endif
205 : else
206 : {
207 : uint32 i;
208 :
209 360 : for (i = 0; i < snap->nxip; i++)
210 : {
211 288 : if (FullTransactionIdEquals(value, snap->xip[i]))
212 48 : return false;
213 : }
214 72 : return true;
215 : }
216 : }
217 :
218 : /*
219 : * helper functions to use StringInfo for pg_snapshot creation.
220 : */
221 :
222 : static StringInfo
223 186 : buf_init(FullTransactionId xmin, FullTransactionId xmax)
224 : {
225 : pg_snapshot snap;
226 : StringInfo buf;
227 :
228 186 : snap.xmin = xmin;
229 186 : snap.xmax = xmax;
230 186 : snap.nxip = 0;
231 :
232 186 : buf = makeStringInfo();
233 186 : appendBinaryStringInfo(buf, &snap, PG_SNAPSHOT_SIZE(0));
234 186 : return buf;
235 : }
236 :
237 : static void
238 624 : buf_add_txid(StringInfo buf, FullTransactionId fxid)
239 : {
240 624 : pg_snapshot *snap = (pg_snapshot *) buf->data;
241 :
242 : /* do this before possible realloc */
243 624 : snap->nxip++;
244 :
245 624 : appendBinaryStringInfo(buf, &fxid, sizeof(fxid));
246 624 : }
247 :
248 : static pg_snapshot *
249 150 : buf_finalize(StringInfo buf)
250 : {
251 150 : pg_snapshot *snap = (pg_snapshot *) buf->data;
252 :
253 150 : SET_VARSIZE(snap, buf->len);
254 :
255 : /* buf is not needed anymore */
256 150 : buf->data = NULL;
257 150 : pfree(buf);
258 :
259 150 : return snap;
260 : }
261 :
262 : /*
263 : * parse snapshot from cstring
264 : */
265 : static pg_snapshot *
266 234 : parse_snapshot(const char *str, Node *escontext)
267 : {
268 : FullTransactionId xmin;
269 : FullTransactionId xmax;
270 234 : FullTransactionId last_val = InvalidFullTransactionId;
271 : FullTransactionId val;
272 234 : const char *str_start = str;
273 : char *endp;
274 : StringInfo buf;
275 :
276 234 : xmin = FullTransactionIdFromU64(strtou64(str, &endp, 10));
277 234 : if (*endp != ':')
278 0 : goto bad_format;
279 234 : str = endp + 1;
280 :
281 234 : xmax = FullTransactionIdFromU64(strtou64(str, &endp, 10));
282 234 : if (*endp != ':')
283 0 : goto bad_format;
284 234 : str = endp + 1;
285 :
286 : /* it should look sane */
287 234 : if (!FullTransactionIdIsValid(xmin) ||
288 222 : !FullTransactionIdIsValid(xmax) ||
289 210 : FullTransactionIdPrecedes(xmax, xmin))
290 48 : goto bad_format;
291 :
292 : /* allocate buffer */
293 186 : buf = buf_init(xmin, xmax);
294 :
295 : /* loop over values */
296 822 : while (*str != '\0')
297 : {
298 : /* read next value */
299 672 : val = FullTransactionIdFromU64(strtou64(str, &endp, 10));
300 672 : str = endp;
301 :
302 : /* require the input to be in order */
303 672 : if (FullTransactionIdPrecedes(val, xmin) ||
304 660 : FullTransactionIdFollowsOrEquals(val, xmax) ||
305 660 : FullTransactionIdPrecedes(val, last_val))
306 36 : goto bad_format;
307 :
308 : /* skip duplicates */
309 636 : if (!FullTransactionIdEquals(val, last_val))
310 624 : buf_add_txid(buf, val);
311 636 : last_val = val;
312 :
313 636 : if (*str == ',')
314 516 : str++;
315 120 : else if (*str != '\0')
316 0 : goto bad_format;
317 : }
318 :
319 150 : return buf_finalize(buf);
320 :
321 84 : bad_format:
322 84 : ereturn(escontext, NULL,
323 : (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
324 : errmsg("invalid input syntax for type %s: \"%s\"",
325 : "pg_snapshot", str_start)));
326 : }
327 :
328 : /*
329 : * pg_current_xact_id() returns xid8
330 : *
331 : * Return the current toplevel full transaction ID.
332 : * If the current transaction does not have one, one is assigned.
333 : */
334 : Datum
335 5742 : pg_current_xact_id(PG_FUNCTION_ARGS)
336 : {
337 : /*
338 : * Must prevent during recovery because if an xid is not assigned we try
339 : * to assign one, which would fail. Programs already rely on this function
340 : * to always return a valid current xid, so we should not change this to
341 : * return NULL or similar invalid xid.
342 : */
343 5742 : PreventCommandDuringRecovery("pg_current_xact_id()");
344 :
345 5742 : PG_RETURN_FULLTRANSACTIONID(GetTopFullTransactionId());
346 : }
347 :
348 : /*
349 : * Same as pg_current_xact_id() but doesn't assign a new xid if there
350 : * isn't one yet.
351 : */
352 : Datum
353 24 : pg_current_xact_id_if_assigned(PG_FUNCTION_ARGS)
354 : {
355 24 : FullTransactionId topfxid = GetTopFullTransactionIdIfAny();
356 :
357 24 : if (!FullTransactionIdIsValid(topfxid))
358 12 : PG_RETURN_NULL();
359 :
360 12 : PG_RETURN_FULLTRANSACTIONID(topfxid);
361 : }
362 :
363 : /*
364 : * pg_current_snapshot() returns pg_snapshot
365 : *
366 : * Return current snapshot
367 : *
368 : * Note that only top-transaction XIDs are included in the snapshot.
369 : */
370 : Datum
371 40 : pg_current_snapshot(PG_FUNCTION_ARGS)
372 : {
373 : pg_snapshot *snap;
374 : uint32 nxip,
375 : i;
376 : Snapshot cur;
377 40 : FullTransactionId next_fxid = ReadNextFullTransactionId();
378 :
379 40 : cur = GetActiveSnapshot();
380 40 : if (cur == NULL)
381 0 : elog(ERROR, "no active snapshot set");
382 :
383 : /* allocate */
384 40 : nxip = cur->xcnt;
385 40 : snap = palloc(PG_SNAPSHOT_SIZE(nxip));
386 :
387 : /*
388 : * Fill. This is the current backend's active snapshot, so MyProc->xmin
389 : * is <= all these XIDs. As long as that remains so, oldestXid can't
390 : * advance past any of these XIDs. Hence, these XIDs remain allowable
391 : * relative to next_fxid.
392 : */
393 40 : snap->xmin = FullTransactionIdFromAllowableAt(next_fxid, cur->xmin);
394 40 : snap->xmax = FullTransactionIdFromAllowableAt(next_fxid, cur->xmax);
395 40 : snap->nxip = nxip;
396 104 : for (i = 0; i < nxip; i++)
397 : snap->xip[i] =
398 64 : FullTransactionIdFromAllowableAt(next_fxid, cur->xip[i]);
399 :
400 : /*
401 : * We want them guaranteed to be in ascending order. This also removes
402 : * any duplicate xids. Normally, an XID can only be assigned to one
403 : * backend, but when preparing a transaction for two-phase commit, there
404 : * is a transient state when both the original backend and the dummy
405 : * PGPROC entry reserved for the prepared transaction hold the same XID.
406 : */
407 40 : sort_snapshot(snap);
408 :
409 : /* set size after sorting, because it may have removed duplicate xips */
410 40 : SET_VARSIZE(snap, PG_SNAPSHOT_SIZE(snap->nxip));
411 :
412 40 : PG_RETURN_POINTER(snap);
413 : }
414 :
415 : /*
416 : * pg_snapshot_in(cstring) returns pg_snapshot
417 : *
418 : * input function for type pg_snapshot
419 : */
420 : Datum
421 234 : pg_snapshot_in(PG_FUNCTION_ARGS)
422 : {
423 234 : char *str = PG_GETARG_CSTRING(0);
424 : pg_snapshot *snap;
425 :
426 234 : snap = parse_snapshot(str, fcinfo->context);
427 :
428 174 : PG_RETURN_POINTER(snap);
429 : }
430 :
431 : /*
432 : * pg_snapshot_out(pg_snapshot) returns cstring
433 : *
434 : * output function for type pg_snapshot
435 : */
436 : Datum
437 124 : pg_snapshot_out(PG_FUNCTION_ARGS)
438 : {
439 124 : pg_snapshot *snap = (pg_snapshot *) PG_GETARG_VARLENA_P(0);
440 : StringInfoData str;
441 : uint32 i;
442 :
443 124 : initStringInfo(&str);
444 :
445 124 : appendStringInfo(&str, UINT64_FORMAT ":",
446 : U64FromFullTransactionId(snap->xmin));
447 124 : appendStringInfo(&str, UINT64_FORMAT ":",
448 : U64FromFullTransactionId(snap->xmax));
449 :
450 688 : for (i = 0; i < snap->nxip; i++)
451 : {
452 564 : if (i > 0)
453 464 : appendStringInfoChar(&str, ',');
454 564 : appendStringInfo(&str, UINT64_FORMAT,
455 : U64FromFullTransactionId(snap->xip[i]));
456 : }
457 :
458 124 : PG_RETURN_CSTRING(str.data);
459 : }
460 :
461 : /*
462 : * pg_snapshot_recv(internal) returns pg_snapshot
463 : *
464 : * binary input function for type pg_snapshot
465 : *
466 : * format: int4 nxip, int8 xmin, int8 xmax, int8 xip
467 : */
468 : Datum
469 0 : pg_snapshot_recv(PG_FUNCTION_ARGS)
470 : {
471 0 : StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
472 : pg_snapshot *snap;
473 0 : FullTransactionId last = InvalidFullTransactionId;
474 : int nxip;
475 : int i;
476 : FullTransactionId xmin;
477 : FullTransactionId xmax;
478 :
479 : /* load and validate nxip */
480 0 : nxip = pq_getmsgint(buf, 4);
481 0 : if (nxip < 0 || nxip > PG_SNAPSHOT_MAX_NXIP)
482 0 : goto bad_format;
483 :
484 0 : xmin = FullTransactionIdFromU64((uint64) pq_getmsgint64(buf));
485 0 : xmax = FullTransactionIdFromU64((uint64) pq_getmsgint64(buf));
486 0 : if (!FullTransactionIdIsValid(xmin) ||
487 0 : !FullTransactionIdIsValid(xmax) ||
488 0 : FullTransactionIdPrecedes(xmax, xmin))
489 0 : goto bad_format;
490 :
491 0 : snap = palloc(PG_SNAPSHOT_SIZE(nxip));
492 0 : snap->xmin = xmin;
493 0 : snap->xmax = xmax;
494 :
495 0 : for (i = 0; i < nxip; i++)
496 : {
497 : FullTransactionId cur =
498 0 : FullTransactionIdFromU64((uint64) pq_getmsgint64(buf));
499 :
500 0 : if (FullTransactionIdPrecedes(cur, last) ||
501 0 : FullTransactionIdPrecedes(cur, xmin) ||
502 0 : FullTransactionIdPrecedes(xmax, cur))
503 0 : goto bad_format;
504 :
505 : /* skip duplicate xips */
506 0 : if (FullTransactionIdEquals(cur, last))
507 : {
508 0 : i--;
509 0 : nxip--;
510 0 : continue;
511 : }
512 :
513 0 : snap->xip[i] = cur;
514 0 : last = cur;
515 : }
516 0 : snap->nxip = nxip;
517 0 : SET_VARSIZE(snap, PG_SNAPSHOT_SIZE(nxip));
518 0 : PG_RETURN_POINTER(snap);
519 :
520 0 : bad_format:
521 0 : ereport(ERROR,
522 : (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
523 : errmsg("invalid external pg_snapshot data")));
524 : PG_RETURN_POINTER(NULL); /* keep compiler quiet */
525 : }
526 :
527 : /*
528 : * pg_snapshot_send(pg_snapshot) returns bytea
529 : *
530 : * binary output function for type pg_snapshot
531 : *
532 : * format: int4 nxip, u64 xmin, u64 xmax, u64 xip...
533 : */
534 : Datum
535 0 : pg_snapshot_send(PG_FUNCTION_ARGS)
536 : {
537 0 : pg_snapshot *snap = (pg_snapshot *) PG_GETARG_VARLENA_P(0);
538 : StringInfoData buf;
539 : uint32 i;
540 :
541 0 : pq_begintypsend(&buf);
542 0 : pq_sendint32(&buf, snap->nxip);
543 0 : pq_sendint64(&buf, (int64) U64FromFullTransactionId(snap->xmin));
544 0 : pq_sendint64(&buf, (int64) U64FromFullTransactionId(snap->xmax));
545 0 : for (i = 0; i < snap->nxip; i++)
546 0 : pq_sendint64(&buf, (int64) U64FromFullTransactionId(snap->xip[i]));
547 0 : PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
548 : }
549 :
550 : /*
551 : * pg_visible_in_snapshot(xid8, pg_snapshot) returns bool
552 : *
553 : * is txid visible in snapshot ?
554 : */
555 : Datum
556 1020 : pg_visible_in_snapshot(PG_FUNCTION_ARGS)
557 : {
558 1020 : FullTransactionId value = PG_GETARG_FULLTRANSACTIONID(0);
559 1020 : pg_snapshot *snap = (pg_snapshot *) PG_GETARG_VARLENA_P(1);
560 :
561 1020 : PG_RETURN_BOOL(is_visible_fxid(value, snap));
562 : }
563 :
564 : /*
565 : * pg_snapshot_xmin(pg_snapshot) returns xid8
566 : *
567 : * return snapshot's xmin
568 : */
569 : Datum
570 76 : pg_snapshot_xmin(PG_FUNCTION_ARGS)
571 : {
572 76 : pg_snapshot *snap = (pg_snapshot *) PG_GETARG_VARLENA_P(0);
573 :
574 76 : PG_RETURN_FULLTRANSACTIONID(snap->xmin);
575 : }
576 :
577 : /*
578 : * pg_snapshot_xmax(pg_snapshot) returns xid8
579 : *
580 : * return snapshot's xmax
581 : */
582 : Datum
583 48 : pg_snapshot_xmax(PG_FUNCTION_ARGS)
584 : {
585 48 : pg_snapshot *snap = (pg_snapshot *) PG_GETARG_VARLENA_P(0);
586 :
587 48 : PG_RETURN_FULLTRANSACTIONID(snap->xmax);
588 : }
589 :
590 : /*
591 : * pg_snapshot_xip(pg_snapshot) returns setof xid8
592 : *
593 : * return in-progress xid8s in snapshot.
594 : */
595 : Datum
596 492 : pg_snapshot_xip(PG_FUNCTION_ARGS)
597 : {
598 : FuncCallContext *fctx;
599 : pg_snapshot *snap;
600 : FullTransactionId value;
601 :
602 : /* on first call initialize fctx and get copy of snapshot */
603 492 : if (SRF_IS_FIRSTCALL())
604 : {
605 48 : pg_snapshot *arg = (pg_snapshot *) PG_GETARG_VARLENA_P(0);
606 :
607 48 : fctx = SRF_FIRSTCALL_INIT();
608 :
609 : /* make a copy of user snapshot */
610 48 : snap = MemoryContextAlloc(fctx->multi_call_memory_ctx, VARSIZE(arg));
611 48 : memcpy(snap, arg, VARSIZE(arg));
612 :
613 48 : fctx->user_fctx = snap;
614 : }
615 :
616 : /* return values one-by-one */
617 492 : fctx = SRF_PERCALL_SETUP();
618 492 : snap = fctx->user_fctx;
619 492 : if (fctx->call_cntr < snap->nxip)
620 : {
621 444 : value = snap->xip[fctx->call_cntr];
622 444 : SRF_RETURN_NEXT(fctx, FullTransactionIdGetDatum(value));
623 : }
624 : else
625 : {
626 48 : SRF_RETURN_DONE(fctx);
627 : }
628 : }
629 :
630 : /*
631 : * Report the status of a recent transaction ID, or null for wrapped,
632 : * truncated away or otherwise too old XIDs.
633 : *
634 : * The passed epoch-qualified xid is treated as a normal xid, not a
635 : * multixact id.
636 : *
637 : * If it points to a committed subxact the result is the subxact status even
638 : * though the parent xact may still be in progress or may have aborted.
639 : */
640 : Datum
641 84 : pg_xact_status(PG_FUNCTION_ARGS)
642 : {
643 : const char *status;
644 84 : FullTransactionId fxid = PG_GETARG_FULLTRANSACTIONID(0);
645 : TransactionId xid;
646 :
647 : /*
648 : * We must protect against concurrent truncation of clog entries to avoid
649 : * an I/O error on SLRU lookup.
650 : */
651 84 : LWLockAcquire(XactTruncationLock, LW_SHARED);
652 84 : if (TransactionIdInRecentPast(fxid, &xid))
653 : {
654 : Assert(TransactionIdIsValid(xid));
655 :
656 : /*
657 : * Like when doing visibility checks on a row, check whether the
658 : * transaction is still in progress before looking into the CLOG.
659 : * Otherwise we would incorrectly return "committed" for a transaction
660 : * that is committing and has already updated the CLOG, but hasn't
661 : * removed its XID from the proc array yet. (See comment on that race
662 : * condition at the top of heapam_visibility.c)
663 : */
664 60 : if (TransactionIdIsInProgress(xid))
665 12 : status = "in progress";
666 48 : else if (TransactionIdDidCommit(xid))
667 36 : status = "committed";
668 : else
669 : {
670 : /* it must have aborted or crashed */
671 12 : status = "aborted";
672 : }
673 : }
674 : else
675 : {
676 12 : status = NULL;
677 : }
678 72 : LWLockRelease(XactTruncationLock);
679 :
680 72 : if (status == NULL)
681 12 : PG_RETURN_NULL();
682 : else
683 60 : PG_RETURN_TEXT_P(cstring_to_text(status));
684 : }
|