postgres.git / summary / log / commit / refs

commit    7aea1050ebdf21cb0714bfec46d5bba6d85cf3bf
Author:   Tom Lane <tgl@sss.pgh.pa.us>
Date:     Thu Mar 13 16:03:07 2014 +0000
First released in: 9.4.0, 9.0.17, 9.1.13, 9.2.8, 9.3.4

    Avoid transaction-commit race condition while receiving a NOTIFY message.
    
    Use TransactionIdIsInProgress, then TransactionIdDidCommit, to distinguish
    whether a NOTIFY message's originating transaction is in progress,
    committed, or aborted.  The previous coding could accept a message from a
    transaction that was still in-progress according to the PGPROC array;
    if the client were fast enough at starting a new transaction, it might fail
    to see table rows added/updated by the message-sending transaction.  Which
    of course would usually be the point of receiving the message.  We noted
    this type of race condition long ago in tqual.c, but async.c overlooked it.
    
    The race condition probably cannot occur unless there are multiple NOTIFY
    senders in action, since an individual backend doesn't send NOTIFY signals
    until well after it's done committing.  But if two senders commit in close
    succession, it's certainly possible that we could see the second sender's
    message within the race condition window while responding to the signal
    from the first one.
    
    Per bug #9557 from Marko Tiikkaja.  This patch is slightly more invasive
    than what he proposed, since it removes the now-redundant
    TransactionIdDidAbort call.
    
    Back-patch to 9.0, where the current NOTIFY implementation was introduced.


src/backend/commands/async.c | 42 ++++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c index 0022ad27adb..fa830acfddf 100644 --- a/src/backend/commands/async.c +++ b/src/backend/commands/async.c @@ -126,6 +126,7 @@ #include "miscadmin.h" #include "storage/ipc.h" #include "storage/lmgr.h" +#include "storage/procarray.h" #include "storage/procsignal.h" #include "storage/sinval.h" #include "tcop/tcopprot.h" @@ -2001,7 +2002,27 @@ asyncQueueProcessPageEntries(QueuePosition *current, /* Ignore messages destined for other databases */ if (qe->dboid == MyDatabaseId) { - if (TransactionIdDidCommit(qe->xid)) + if (TransactionIdIsInProgress(qe->xid)) + { + /* + * The source transaction is still in progress, so we can't + * process this message yet. Break out of the loop, but first + * back up *current so we will reprocess the message next + * time. (Note: it is unlikely but not impossible for + * TransactionIdDidCommit to fail, so we can't really avoid + * this advance-then-back-up behavior when dealing with an + * uncommitted message.) + * + * Note that we must test TransactionIdIsInProgress before we + * test TransactionIdDidCommit, else we might return a message + * from a transaction that is not yet visible to snapshots; + * compare the comments at the head of tqual.c. + */ + *current = thisentry; + reachedStop = true; + break; + } + else if (TransactionIdDidCommit(qe->xid)) { /* qe->data is the null-terminated channel name */ char *channel = qe->data; @@ -2014,27 +2035,12 @@ asyncQueueProcessPageEntries(QueuePosition *current, NotifyMyFrontEnd(channel, payload, qe->srcPid); } } - else if (TransactionIdDidAbort(qe->xid)) - { - /* - * If the source transaction aborted, we just ignore its - * notifications. - */ - } else { /* - * The transaction has neither committed nor aborted so far, - * so we can't process its message yet. Break out of the - * loop, but first back up *current so we will reprocess the - * message next time. (Note: it is unlikely but not - * impossible for TransactionIdDidCommit to fail, so we can't - * really avoid this advance-then-back-up behavior when - * dealing with an uncommitted message.) + * The source transaction aborted or crashed, so we just + * ignore its notifications. */ - *current = thisentry; - reachedStop = true; - break; } } [parent: 9954e1f03804]