Handle duplicate XIDs in txid_snapshot.
authorHeikki Linnakangas <heikki.linnakangas@iki.fi>
Thu, 15 May 2014 15:29:20 +0000 (18:29 +0300)
committerHeikki Linnakangas <heikki.linnakangas@iki.fi>
Thu, 15 May 2014 15:31:22 +0000 (18:31 +0300)
The proc array can contain duplicate XIDs, when a transaction is just being
prepared for two-phase commit. To cope, remove any duplicates in
txid_current_snapshot(). Also ignore duplicates in the input functions, so
that if e.g. you have an old pg_dump file that already contains duplicates,
it will be accepted.

Report and fix by Jan Wieck. Backpatch to all supported versions.

src/backend/utils/adt/txid.c
src/test/regress/expected/txid.out
src/test/regress/sql/txid.sql

index fa01b9a5fd43052d82eb68b55f219b1c9eaaf20b..9aa31a402d81186c715ab3c955c7469a8cbef560 100644 (file)
@@ -139,7 +139,8 @@ cmp_txid(const void *aa, const void *bb)
 }
 
 /*
- * sort a snapshot's txids, so we can use bsearch() later.
+ * Sort a snapshot's txids, so we can use bsearch() later.  Also remove
+ * any duplicates.
  *
  * For consistency of on-disk representation, we always sort even if bsearch
  * will not be used.
@@ -147,8 +148,25 @@ cmp_txid(const void *aa, const void *bb)
 static void
 sort_snapshot(TxidSnapshot *snap)
 {
+   txid    last = 0;
+   int     nxip, idx1, idx2;
+
    if (snap->nxip > 1)
+   {
        qsort(snap->xip, snap->nxip, sizeof(txid), cmp_txid);
+
+       /* remove duplicates */
+       nxip = snap->nxip;
+       idx1 = idx2 = 0;
+       while (idx1 < nxip)
+       {
+           if (snap->xip[idx1] != last)
+               last = snap->xip[idx2++] = snap->xip[idx1];
+           else
+               snap->nxip--;
+           idx1++;
+       }
+   }
 }
 
 /*
@@ -303,10 +321,12 @@ parse_snapshot(const char *str)
        str = endp;
 
        /* require the input to be in order */
-       if (val < xmin || val >= xmax || val <= last_val)
+       if (val < xmin || val >= xmax || val < last_val)
            goto bad_format;
 
-       buf_add_txid(buf, val);
+       /* skip duplicates */
+       if (val != last_val)
+           buf_add_txid(buf, val);
        last_val = val;
 
        if (*str == ',')
@@ -360,8 +380,7 @@ txid_current_snapshot(PG_FUNCTION_ARGS)
 {
    TxidSnapshot *snap;
    uint32      nxip,
-               i,
-               size;
+               i;
    TxidEpoch   state;
    Snapshot    cur;
 
@@ -373,9 +392,7 @@ txid_current_snapshot(PG_FUNCTION_ARGS)
 
    /* allocate */
    nxip = cur->xcnt;
-   size = TXID_SNAPSHOT_SIZE(nxip);
-   snap = palloc(size);
-   SET_VARSIZE(snap, size);
+   snap = palloc(TXID_SNAPSHOT_SIZE(nxip));
 
    /* fill */
    snap->xmin = convert_xid(cur->xmin, &state);
@@ -384,9 +401,18 @@ txid_current_snapshot(PG_FUNCTION_ARGS)
    for (i = 0; i < nxip; i++)
        snap->xip[i] = convert_xid(cur->xip[i], &state);
 
-   /* we want them guaranteed to be in ascending order */
+   /*
+    * We want them guaranteed to be in ascending order.  This also removes
+    * any duplicate xids.  Normally, an XID can only be assigned to one
+    * backend, but when preparing a transaction for two-phase commit, there
+    * is a transient state when both the original backend and the dummy
+    * PGPROC entry reserved for the prepared transaction hold the same XID.
+    */
    sort_snapshot(snap);
 
+   /* set size after sorting, because it may have removed duplicate xips */
+   SET_VARSIZE(snap, TXID_SNAPSHOT_SIZE(snap->nxip));
+
    PG_RETURN_POINTER(snap);
 }
 
@@ -464,18 +490,27 @@ txid_snapshot_recv(PG_FUNCTION_ARGS)
    snap = palloc(TXID_SNAPSHOT_SIZE(nxip));
    snap->xmin = xmin;
    snap->xmax = xmax;
-   snap->nxip = nxip;
-   SET_VARSIZE(snap, TXID_SNAPSHOT_SIZE(nxip));
 
    for (i = 0; i < nxip; i++)
    {
        txid        cur = pq_getmsgint64(buf);
 
-       if (cur <= last || cur < xmin || cur >= xmax)
+       if (cur < last || cur < xmin || cur >= xmax)
            goto bad_format;
+
+       /* skip duplicate xips */
+       if (cur == last)
+       {
+           i--;
+           nxip--;
+           continue;
+       }
+
        snap->xip[i] = cur;
        last = cur;
    }
+   snap->nxip = nxip;
+   SET_VARSIZE(snap, TXID_SNAPSHOT_SIZE(nxip));
    PG_RETURN_POINTER(snap);
 
 bad_format:
index 930b86a65628482cc4e841f89ffbab6055cfe720..7750b7b98f937b2c81a80dc0dba91e34a34c33fb 100644 (file)
@@ -12,6 +12,12 @@ select '12:18:14,16'::txid_snapshot;
  12:18:14,16
 (1 row)
 
+select '12:16:14,14'::txid_snapshot;
+ txid_snapshot 
+---------------
+ 12:16:14
+(1 row)
+
 -- errors
 select '31:12:'::txid_snapshot;
 ERROR:  invalid input for txid_snapshot: "31:12:"
@@ -29,10 +35,6 @@ select '12:16:14,13'::txid_snapshot;
 ERROR:  invalid input for txid_snapshot: "12:16:14,13"
 LINE 1: select '12:16:14,13'::txid_snapshot;
                ^
-select '12:16:14,14'::txid_snapshot;
-ERROR:  invalid input for txid_snapshot: "12:16:14,14"
-LINE 1: select '12:16:14,14'::txid_snapshot;
-               ^
 create temp table snapshot_test (
    nr  integer,
    snap    txid_snapshot
index ecae10e024d3961cab062698e421213293ea856b..b6650b922e6d4b0df87fde088be491600b386e2f 100644 (file)
@@ -3,13 +3,13 @@
 -- i/o
 select '12:13:'::txid_snapshot;
 select '12:18:14,16'::txid_snapshot;
+select '12:16:14,14'::txid_snapshot;
 
 -- errors
 select '31:12:'::txid_snapshot;
 select '0:1:'::txid_snapshot;
 select '12:13:0'::txid_snapshot;
 select '12:16:14,13'::txid_snapshot;
-select '12:16:14,14'::txid_snapshot;
 
 create temp table snapshot_test (
    nr  integer,