Handle duplicate XIDs in txid_snapshot.

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.
This commit is contained in:
Heikki Linnakangas 2014-05-15 18:29:20 +03:00
parent bb38fb0d43
commit 8f9b9590d7
3 changed files with 54 additions and 17 deletions

View File

@ -131,7 +131,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 * For consistency of on-disk representation, we always sort even if bsearch
* will not be used. * will not be used.
@ -139,8 +140,25 @@ cmp_txid(const void *aa, const void *bb)
static void static void
sort_snapshot(TxidSnapshot *snap) sort_snapshot(TxidSnapshot *snap)
{ {
txid last = 0;
int nxip, idx1, idx2;
if (snap->nxip > 1) if (snap->nxip > 1)
{
qsort(snap->xip, snap->nxip, sizeof(txid), cmp_txid); 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++;
}
}
} }
/* /*
@ -295,10 +313,12 @@ parse_snapshot(const char *str)
str = endp; str = endp;
/* require the input to be in order */ /* 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; goto bad_format;
buf_add_txid(buf, val); /* skip duplicates */
if (val != last_val)
buf_add_txid(buf, val);
last_val = val; last_val = val;
if (*str == ',') if (*str == ',')
@ -361,8 +381,7 @@ txid_current_snapshot(PG_FUNCTION_ARGS)
{ {
TxidSnapshot *snap; TxidSnapshot *snap;
uint32 nxip, uint32 nxip,
i, i;
size;
TxidEpoch state; TxidEpoch state;
Snapshot cur; Snapshot cur;
@ -381,9 +400,7 @@ txid_current_snapshot(PG_FUNCTION_ARGS)
/* allocate */ /* allocate */
nxip = cur->xcnt; nxip = cur->xcnt;
size = TXID_SNAPSHOT_SIZE(nxip); snap = palloc(TXID_SNAPSHOT_SIZE(nxip));
snap = palloc(size);
SET_VARSIZE(snap, size);
/* fill */ /* fill */
snap->xmin = convert_xid(cur->xmin, &state); snap->xmin = convert_xid(cur->xmin, &state);
@ -392,9 +409,18 @@ txid_current_snapshot(PG_FUNCTION_ARGS)
for (i = 0; i < nxip; i++) for (i = 0; i < nxip; i++)
snap->xip[i] = convert_xid(cur->xip[i], &state); 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); 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); PG_RETURN_POINTER(snap);
} }
@ -472,18 +498,27 @@ txid_snapshot_recv(PG_FUNCTION_ARGS)
snap = palloc(TXID_SNAPSHOT_SIZE(nxip)); snap = palloc(TXID_SNAPSHOT_SIZE(nxip));
snap->xmin = xmin; snap->xmin = xmin;
snap->xmax = xmax; snap->xmax = xmax;
snap->nxip = nxip;
SET_VARSIZE(snap, TXID_SNAPSHOT_SIZE(nxip));
for (i = 0; i < nxip; i++) for (i = 0; i < nxip; i++)
{ {
txid cur = pq_getmsgint64(buf); txid cur = pq_getmsgint64(buf);
if (cur <= last || cur < xmin || cur >= xmax) if (cur < last || cur < xmin || cur >= xmax)
goto bad_format; goto bad_format;
/* skip duplicate xips */
if (cur == last)
{
i--;
nxip--;
continue;
}
snap->xip[i] = cur; snap->xip[i] = cur;
last = cur; last = cur;
} }
snap->nxip = nxip;
SET_VARSIZE(snap, TXID_SNAPSHOT_SIZE(nxip));
PG_RETURN_POINTER(snap); PG_RETURN_POINTER(snap);
bad_format: bad_format:

View File

@ -12,6 +12,12 @@ select '12:18:14,16'::txid_snapshot;
12:18:14,16 12:18:14,16
(1 row) (1 row)
select '12:16:14,14'::txid_snapshot;
txid_snapshot
---------------
12:16:14
(1 row)
-- errors -- errors
select '31:12:'::txid_snapshot; select '31:12:'::txid_snapshot;
ERROR: invalid input for txid_snapshot: "31:12:" 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" ERROR: invalid input for txid_snapshot: "12:16:14,13"
LINE 1: select '12:16:14,13'::txid_snapshot; 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 ( create temp table snapshot_test (
nr integer, nr integer,
snap txid_snapshot snap txid_snapshot

View File

@ -3,13 +3,13 @@
-- i/o -- i/o
select '12:13:'::txid_snapshot; select '12:13:'::txid_snapshot;
select '12:18:14,16'::txid_snapshot; select '12:18:14,16'::txid_snapshot;
select '12:16:14,14'::txid_snapshot;
-- errors -- errors
select '31:12:'::txid_snapshot; select '31:12:'::txid_snapshot;
select '0:1:'::txid_snapshot; select '0:1:'::txid_snapshot;
select '12:13:0'::txid_snapshot; select '12:13:0'::txid_snapshot;
select '12:16:14,13'::txid_snapshot; select '12:16:14,13'::txid_snapshot;
select '12:16:14,14'::txid_snapshot;
create temp table snapshot_test ( create temp table snapshot_test (
nr integer, nr integer,