Thread: Cleaning up recovery from subtransaction start failure

Cleaning up recovery from subtransaction start failure

From
Tom Lane
Date:
BTW, I'm not going to make the lock release/XactLockTableWait fix just yet,
because exhaustion of shared memory provides an easy test case for a
problem that I want to fix first.  What I noticed while testing the
reported case is that you get

WARNING:  out of shared memory
CONTEXT:  PL/pgSQL function "do_standard_mgc" line 2 at block variables initialization

(this is ShmemAlloc choking)

ERROR:  out of shared memory
HINT:  You may need to increase max_locks_per_transaction.
CONTEXT:  PL/pgSQL function "do_standard_mgc" line 2 at block variables initialization

(this is LockAcquire choking as a result)

WARNING:  StartAbortedSubTransaction while in START state

(this is xact.c trying to cope with the resultant error exit)

and after that we are stuck inside an aborted subtransaction:

(gdb) p *CurrentTransactionState
$2 = {transactionIdData = 0, name = 0x0, savepointLevel = 0, commandId = 8414, state = TRANS_ABORT, blockState =
TBLOCK_SUBABORT,nestingLevel = 2, curTransactionContext = 0x4011b6d0, curTransactionOwner = 0x4017b1f8, childXids =
0x0,currentUser = 0, prevXactReadOnly = 0 '\000', parent = 0x40001130}
 
(gdb) p *CurrentTransactionState->parent
$3 = {transactionIdData = 78724, name = 0x0, savepointLevel = 0, commandId = 8414, state = TRANS_INPROGRESS, blockState
=TBLOCK_STARTED, nestingLevel = 1, curTransactionContext = 0x400bc6b8, curTransactionOwner = 0x40069d58, childXids =
0x400c3030,currentUser = 0, prevXactReadOnly = 0 '\000', parent = 0x0}
 
(gdb)

which means that I have to type "abort;" to get the system back into a
usable state.  Since I did not type "begin;" first, that is clearly a
bug.

I don't actually like StartAbortedSubTransaction at all --- ISTM that if
you get a failure trying to enter a subxact, it's better *not* to enter
the subxact and instead to treat the error as putting the calling xact
in abort state.

Also, I found a problem a couple days ago with errors occurring during
transaction commit, for instance a sequence likebegin;set constraints all deferred;insert into foo () -- assume this
violatesa deferred FK constraintsavepoint a;commit;
 

The problem is that the COMMIT command only changed the state of the
topmost transaction record (the savepoint's subxact) which we then pop
off the stack during commit.  After that we run deferred triggers.
If we get an error at that stage, there is nothing left on the state
stack to remind us that the user had typed commit ... and so guess what,
we stay inside the aborted transaction, and the user has the same
problem of needing to type abort when he shouldn't.

I put in what I think is a correct fix for that one here:
http://developer.postgresql.org/cvsweb.cgi/pgsql-server/src/backend/access/transam/xact.c.diff?r1=1.186;r2=1.187
(ignore the renamings of trigger functions in the same patch).
Basically the idea is that COMMIT should mark the *entire* state stack
before we start to pop anything, and then we will still remember what we
were doing if we get an error after the first pop.

Since then I have been thinking that we should get rid of some of the
subxact-abort-related states in favor of handling abort cases similarly.
I don't have a test case to prove that there's a problem, but in general
errors during error aborts are a real threat that has to be considered.

Thoughts?
        regards, tom lane


Re: Cleaning up recovery from subtransaction start failure

From
Tom Lane
Date:
I wrote:
> I don't actually like StartAbortedSubTransaction at all --- ISTM that if
> you get a failure trying to enter a subxact, it's better *not* to enter
> the subxact and instead to treat the error as putting the calling xact
> in abort state.

Actually, the more I think about this the more I like the idea I just
mentioned in the other thread, which is to not assign a subxact an XID
right away.  The advantage is that acquiring an XID and a lock on it
are definitely operations that could fail (cf original problem), and
it becomes very painful to ensure that xact.c's internal state stays
legal in event of a failure there, if we insist that that happens during
subxact start.

I've just looked through the modules that use GetCurrentTransactionId
to label their internal state, and without exception they'd be perfectly
happy with an ersatz substitute that only distinguishes among
subtransactions of the current top transaction.  So here is what I am
thinking:

* New datatype SubTransactionId == uint32 (just for clarity of code)

* Special values InvalidSubTransactionId == 0 and TopSubTransactionId == 1; we initialize the counter for each main
transactionso that subxacts get IDs beginning at 2.
 

* Subtransaction start looks about like this:
subxactid = ++SubTransactionIdCounter;if (subxactid == InvalidSubTransactionId){    // oops, wrapped around
--SubTransactionIdCounter;   elog(ERROR, "out of subxact xids");}
 
subxactstate = palloc(sizeof(TransactionStateData));
// initialize subxact block and link it into state stack
// call modules that need start-of-subxact calls

Note that if we get an out-of-memory failure in the palloc, we have not
done anything except waste a SubTransactionId value, so there is no
recovery problem there.  Once the palloc succeeds, no error is possible
between there and having a fully-set-up subxact.  Errors in the "call
modules" phase, if any, can be treated as errors occuring inside an
already-established subxact.  Or we could choose to abort and pop the
subxact, if you like the theory that failure during SAVEPOINT should
not create a broken subxact; this isn't too hard since we know we have
gotten to a valid stack state that abort can cope with, which was
definitely not guaranteed before.

The places that currently call GetCurrentTransactionId for labeling
internal state would all call a new GetCurrentSubTransactionId routine.
Other than that and renaming/retyping their state fields, they'd not
need much change in most cases.

The remaining calls of GetCurrentTransactionId would mostly be the ones
in heapam.c that are labeling tuples about to be written to disk.

One minor point is that GetCurrentTransactionId would now become a
routine with a nonzero prospect of failure.  We'd want to be sure that
it is not called inside critical sections (since ERROR -> PANIC inside
such sections), unless we are certain a current XID has been assigned.
This would require only trivial code rearrangements in heapam.c, but
there is a call in XLogInsert that is a bigger hazard.  I believe all
calls of XLogInsert are *already* inside critical sections, and so if
any of them are in code paths where no XID has been assigned, we have
a hard-to-spot risk of system panic.  But there probably isn't any good
reason to be emitting an XLOG record without having assigned an XID.
I think I would fix this by not having XLogInsert do its own call,
but requiring callers to pass in the XID to use.  It will be easy to
make the callers do GetCurrentTransactionId before they enter their
critical sections.

Bottom line: I'm now leaning towards doing this for 8.0, since it would
make for a useful improvement in robustness, quite aside from any
performance gains from eliminating unnecessary XID assignments.

Comments?
        regards, tom lane


Re: Cleaning up recovery from subtransaction start failure

From
Alvaro Herrera
Date:
On Mon, Sep 13, 2004 at 08:40:46PM -0400, Tom Lane wrote:

> The remaining calls of GetCurrentTransactionId would mostly be the ones
> in heapam.c that are labeling tuples about to be written to disk.
>
> One minor point is that GetCurrentTransactionId would now become a
> routine with a nonzero prospect of failure.

Just to be sure I understand your proposal: the idea is that a
subtransaction would not have a TransactionId assigned right away, but
instead the first call to GetCurrentTransactionId in the subxact would
assign it (and call SubTransSetParent and XactLockTableInsert).  Is this
right?

> Bottom line: I'm now leaning towards doing this for 8.0, since it would
> make for a useful improvement in robustness, quite aside from any
> performance gains from eliminating unnecessary XID assignments.

I agree it's a very interesting robustness improvement.  Also let me
point out explicitly that it would reduce the wastage of pg_clog and
pg_subtrans space by a possibly big margin, reduce the number of locks
taken by XactLockTableInsert(), and reduce the recently talked about
problems of transaction Id wraparound.  The savings could be
considerable, especially where high usage of pl/pgsql exceptions are
used.  IMHO it definitely has to be in 8.0, or we risk too much in Xid
wraparound, especially when lots of people is starting to use Pg on high
volume, high update environments.

-- 
Alvaro Herrera (<alvherre[a]dcc.uchile.cl>)
FOO MANE PADME HUM



Re: Cleaning up recovery from subtransaction start failure

From
Tom Lane
Date:
Alvaro Herrera <alvherre@dcc.uchile.cl> writes:
> Just to be sure I understand your proposal: the idea is that a
> subtransaction would not have a TransactionId assigned right away, but
> instead the first call to GetCurrentTransactionId in the subxact would
> assign it (and call SubTransSetParent and XactLockTableInsert).  Is this
> right?

Right.  The theory being that a subxact that hasn't yet done anything
to global database state need not own an XID.  We had optimized on the
assumption that most subxacts would eventually need an XID, but in
hindsight I'm not sure why we thought that must be so.  In any case,
I can't see any significant performance cost associated with postponing
the assignment, so why not?

> IMHO it definitely has to be in 8.0, or we risk too much in Xid
> wraparound, especially when lots of people is starting to use Pg on high
> volume, high update environments.

I'm leaning the same way.  I'll work on this tomorrow unless someone
raises a good objection before then.
        regards, tom lane