Re: Race condition in TransactionIdIsInProgress - Mailing list pgsql-hackers
From | Andres Freund |
---|---|
Subject | Re: Race condition in TransactionIdIsInProgress |
Date | |
Msg-id | 20220211055609.mjtbvsy5psgneoy3@alap3.anarazel.de Whole thread Raw |
In response to | Re: Race condition in TransactionIdIsInProgress (Robert Haas <robertmhaas@gmail.com>) |
Responses |
Re: Race condition in TransactionIdIsInProgress
|
List | pgsql-hackers |
Hi, On 2022-02-10 15:21:27 -0500, Robert Haas wrote: > On Thu, Feb 10, 2022 at 11:46 AM Konstantin Knizhnik <knizhnik@garret.ru> wrote: > > Postgres first records state transaction in CLOG, then removes > > transaction from procarray and finally releases locks. > > But it can happen that transaction is marked as committed in CLOG, > > XMAX_COMMITTED bit is set in modified tuple but > > TransactionIdIsInProgress still returns true. As a result "t_xmin %u is > > uncommitted in tuple (%u,%u) to be updated in table" > > error is reported. > > This is exactly why the HeapTupleSatisfiesFOO() functions all test > TransactionIdIsInProgress() first and TransactionIdDidCommit() only if > it returns false. I don't think it's really legal to test > TransactionIdDidCommit() without checking TransactionIdIsInProgress() > first. And I bet that's exactly what this code is doing... The problem is that the TransactionIdDidAbort() call is coming from within TransactionIdIsInProgress(). If I understand correctly, the problem is that the xid cache stuff doesn't correctly work for subtransactions and breaks TransactionIdIsInProgress(). For the problem to occur you need an xid that is for a subtransaction that suboverflowed and that is 'clog committed' but not 'procarray committed'. If that xid is tested twice with TransactionIdIsInProgress(), you will get a bogus result on the second call within the same session. The first TransactionIdIsInProgress() will go to /* * Step 4: have to check pg_subtrans. * * At this point, we know it's either a subtransaction of one of the Xids * in xids[], or it's not running. If it's an already-failed * subtransaction, we want to say "not running" even though its parent may * still be running. So first, check pg_xact to see if it's been aborted. */ xc_slow_answer_inc(); if (TransactionIdDidAbort(xid)) return false; and fetch that xid. The TransactionLogFetch() will set cachedFetchXid, because the xid's status is TRANSACTION_STATUS_COMMITTED. Because the transaction didn't abort TransactionIdIsInProgress() will go onto the the following loop, see the toplevel xid is in progress in the procarray and return true. The second call to TransactionIdIsInProgress() will see that the fetched transaction matches the cached transactionid and return false. /* * We may have just checked the status of this transaction, so if it is * already known to be completed, we can fall out without any access to * shared memory. */ if (TransactionIdIsKnownCompleted(xid)) { xc_by_known_xact_inc(); return false; } Without any improper uses of TransactionIdDidAbort()/TransactionIdDidCommit() before TransactionIdIsInProgress(). And other sessions will still return true. And even *this* session can return true again, if another transaction is checked that ends up caching another transaction status in cachedFetchXid. It looks to me like the TransactionIdIsKnownCompleted() path in TransactionIdIsInProgress() is just entirely broken. An prior TransactionLogFetch() can't be allowed to make subsequent TransactionIdIsInProgress() calls return wrong values. If I understand the problem correctly, a SELECT pg_xact_status(clog-committed-pgproc-in-progres) can make TransactionIdIsInProgress() return wrong values too. It's not just TransactionIdIsInProgress() itself. Looks like this problem goes back all the way back to commit 611b4393f22f2bb43135501cd6b7591299b6b453 Author: Tom Lane <tgl@sss.pgh.pa.us> Date: 2008-03-11 20:20:35 +0000 Make TransactionIdIsInProgress check transam.c's single-item XID status cache before it goes groveling through the ProcArray. In situations where the same recently-committed transaction ID is checked repeatedly by tqual.c, this saves a lot of shared-memory searches. And it's cheap enough that it shouldn't hurt noticeably when it doesn't help. Concept and patch by Simon, some minor tweaking and comment-cleanup by Tom. I think this may actually mean that the hot corruption problem fixed in commit 18b87b201f7 Author: Andres Freund <andres@anarazel.de> Date: 2021-12-10 20:12:26 -0800 Fix possible HOT corruption when RECENTLY_DEAD changes to DEAD while pruning. for 14/master is present in older branches too :(. Need to trace through the HTSV and pruning logic with a bit more braincells than currently available to be sure. Greetings, Andres Freund
pgsql-hackers by date: