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