|
21 | 21 |
|
22 | 22 | #include "postgres.h"
|
23 | 23 |
|
| 24 | +#include "access/clog.h" |
24 | 25 | #include "access/transam.h"
|
25 | 26 | #include "access/xact.h"
|
26 | 27 | #include "access/xlog.h"
|
27 | 28 | #include "funcapi.h"
|
28 | 29 | #include "miscadmin.h"
|
29 | 30 | #include "libpq/pqformat.h"
|
30 | 31 | #include "postmaster/postmaster.h"
|
| 32 | +#include "storage/lwlock.h" |
31 | 33 | #include "utils/builtins.h"
|
32 | 34 | #include "utils/memutils.h"
|
33 | 35 | #include "utils/snapmgr.h"
|
@@ -92,6 +94,70 @@ load_xid_epoch(TxidEpoch *state)
|
92 | 94 | GetNextXidAndEpoch(&state->last_xid, &state->epoch);
|
93 | 95 | }
|
94 | 96 |
|
| 97 | +/* |
| 98 | + * Helper to get a TransactionId from a 64-bit xid with wraparound detection. |
| 99 | + * |
| 100 | + * It is an ERROR if the xid is in the future. Otherwise, returns true if |
| 101 | + * the transaction is still new enough that we can determine whether it |
| 102 | + * committed and false otherwise. If *extracted_xid is not NULL, it is set |
| 103 | + * to the low 32 bits of the transaction ID (i.e. the actual XID, without the |
| 104 | + * epoch). |
| 105 | + * |
| 106 | + * The caller must hold CLogTruncationLock since it's dealing with arbitrary |
| 107 | + * XIDs, and must continue to hold it until it's done with any clog lookups |
| 108 | + * relating to those XIDs. |
| 109 | + */ |
| 110 | +static bool |
| 111 | +TransactionIdInRecentPast(uint64 xid_with_epoch, TransactionId *extracted_xid) |
| 112 | +{ |
| 113 | + uint32 xid_epoch = (uint32) (xid_with_epoch >> 32); |
| 114 | + TransactionId xid = (TransactionId) xid_with_epoch; |
| 115 | + uint32 now_epoch; |
| 116 | + TransactionId now_epoch_last_xid; |
| 117 | + |
| 118 | + GetNextXidAndEpoch(&now_epoch_last_xid, &now_epoch); |
| 119 | + |
| 120 | + if (extracted_xid != NULL) |
| 121 | + *extracted_xid = xid; |
| 122 | + |
| 123 | + if (!TransactionIdIsValid(xid)) |
| 124 | + return false; |
| 125 | + |
| 126 | + /* For non-normal transaction IDs, we can ignore the epoch. */ |
| 127 | + if (!TransactionIdIsNormal(xid)) |
| 128 | + return true; |
| 129 | + |
| 130 | + /* If the transaction ID is in the future, throw an error. */ |
| 131 | + if (xid_epoch > now_epoch |
| 132 | + || (xid_epoch == now_epoch && xid > now_epoch_last_xid)) |
| 133 | + ereport(ERROR, |
| 134 | + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| 135 | + errmsg("transaction ID " UINT64_FORMAT " is in the future", |
| 136 | + xid_with_epoch))); |
| 137 | + |
| 138 | + /* |
| 139 | + * ShmemVariableCache->oldestClogXid is protected by CLogTruncationLock, |
| 140 | + * but we don't acquire that lock here. Instead, we require the caller to |
| 141 | + * acquire it, because the caller is presumably going to look up the |
| 142 | + * returned XID. If we took and released the lock within this function, a |
| 143 | + * CLOG truncation could occur before the caller finished with the XID. |
| 144 | + */ |
| 145 | + Assert(LWLockHeldByMe(CLogTruncationLock)); |
| 146 | + |
| 147 | + /* |
| 148 | + * If the transaction ID has wrapped around, it's definitely too old to |
| 149 | + * determine the commit status. Otherwise, we can compare it to |
| 150 | + * ShmemVariableCache->oldestClogXid to determine whether the relevant CLOG |
| 151 | + * entry is guaranteed to still exist. |
| 152 | + */ |
| 153 | + if (xid_epoch + 1 < now_epoch |
| 154 | + || (xid_epoch + 1 == now_epoch && xid < now_epoch_last_xid) |
| 155 | + || TransactionIdPrecedes(xid, ShmemVariableCache->oldestClogXid)) |
| 156 | + return false; |
| 157 | + |
| 158 | + return true; |
| 159 | +} |
| 160 | + |
95 | 161 | /*
|
96 | 162 | * do a TransactionId -> txid conversion for an XID near the given epoch
|
97 | 163 | */
|
@@ -354,6 +420,9 @@ parse_snapshot(const char *str)
|
354 | 420 | *
|
355 | 421 | * Return the current toplevel transaction ID as TXID
|
356 | 422 | * If the current transaction does not have one, one is assigned.
|
| 423 | + * |
| 424 | + * This value has the epoch as the high 32 bits and the 32-bit xid |
| 425 | + * as the low 32 bits. |
357 | 426 | */
|
358 | 427 | Datum
|
359 | 428 | txid_current(PG_FUNCTION_ARGS)
|
@@ -658,3 +727,66 @@ txid_snapshot_xip(PG_FUNCTION_ARGS)
|
658 | 727 | SRF_RETURN_DONE(fctx);
|
659 | 728 | }
|
660 | 729 | }
|
| 730 | + |
| 731 | +/* |
| 732 | + * Report the status of a recent transaction ID, or null for wrapped, |
| 733 | + * truncated away or otherwise too old XIDs. |
| 734 | + * |
| 735 | + * The passed epoch-qualified xid is treated as a normal xid, not a |
| 736 | + * multixact id. |
| 737 | + * |
| 738 | + * If it points to a committed subxact the result is the subxact status even |
| 739 | + * though the parent xact may still be in progress or may have aborted. |
| 740 | + */ |
| 741 | +Datum |
| 742 | +txid_status(PG_FUNCTION_ARGS) |
| 743 | +{ |
| 744 | + const char *status; |
| 745 | + uint64 xid_with_epoch = PG_GETARG_INT64(0); |
| 746 | + TransactionId xid; |
| 747 | + |
| 748 | + /* |
| 749 | + * We must protect against concurrent truncation of clog entries to avoid |
| 750 | + * an I/O error on SLRU lookup. |
| 751 | + */ |
| 752 | + LWLockAcquire(CLogTruncationLock, LW_SHARED); |
| 753 | + if (TransactionIdInRecentPast(xid_with_epoch, &xid)) |
| 754 | + { |
| 755 | + Assert(TransactionIdIsValid(xid)); |
| 756 | + |
| 757 | + if (TransactionIdIsCurrentTransactionId(xid)) |
| 758 | + status = gettext_noop("in progress"); |
| 759 | + else if (TransactionIdDidCommit(xid)) |
| 760 | + status = gettext_noop("committed"); |
| 761 | + else if (TransactionIdDidAbort(xid)) |
| 762 | + status = gettext_noop("aborted"); |
| 763 | + else |
| 764 | + { |
| 765 | + /* |
| 766 | + * The xact is not marked as either committed or aborted in clog. |
| 767 | + * |
| 768 | + * It could be a transaction that ended without updating clog or |
| 769 | + * writing an abort record due to a crash. We can safely assume |
| 770 | + * it's aborted if it isn't committed and is older than our |
| 771 | + * snapshot xmin. |
| 772 | + * |
| 773 | + * Otherwise it must be in-progress (or have been at the time |
| 774 | + * we checked commit/abort status). |
| 775 | + */ |
| 776 | + if (TransactionIdPrecedes(xid, GetActiveSnapshot()->xmin)) |
| 777 | + status = gettext_noop("aborted"); |
| 778 | + else |
| 779 | + status = gettext_noop("in progress"); |
| 780 | + } |
| 781 | + } |
| 782 | + else |
| 783 | + { |
| 784 | + status = NULL; |
| 785 | + } |
| 786 | + LWLockRelease(CLogTruncationLock); |
| 787 | + |
| 788 | + if (status == NULL) |
| 789 | + PG_RETURN_NULL(); |
| 790 | + else |
| 791 | + PG_RETURN_TEXT_P(cstring_to_text(status)); |
| 792 | +} |
0 commit comments