8000 Handle duplicate XIDs in txid_snapshot. · larkly/postgres-docker@f47e4ce · GitHub
[go: up one dir, main page]

Skip to content

Commit f47e4ce

Browse files
committed
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.
1 parent 8c19b80 commit f47e4ce

File tree

3 files changed

+54
-17
lines changed

3 files changed

+54
-17
lines changed

src/backend/utils/adt/txid.c

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -130,16 +130,34 @@ cmp_txid(const void *aa, const void *bb)
130130
}
131131

132132
/*
133-
* sort a snapshot's txids, so we can use bsearch() later.
133+
* Sort a snapshot's txids, so we can use bsearch() later. Also remove
134+
* any duplicates.
134135
*
135136
* For consistency of on-disk representation, we always sort even if bsearch
136137
* will not be used.
137138
*/
138139
static void
139140
sort_snapshot(TxidSnapshot *snap)
140141
{
142+
txid last = 0;
143+
int nxip, idx1, idx2;
144+
141145
if (snap->nxip > 1)
146+
{
142147
qsort(snap->xip, snap->nxip, sizeof(txid), cmp_txid);
148+
149+
/* remove duplicates */
150+
nxip = snap->nxip;
151+
idx1 = idx2 = 0;
152+
while (idx1 < nxip)
153+
{
154+
if (snap->xip[idx1] != last)
155+
last = snap->xip[idx2++] = snap->xip[idx1];
156+
else
157+
snap->nxip--;
158+
idx1++;
159+
}
160+
}
143161
}
144162

145163
/*
@@ -294,10 +312,12 @@ parse_snapshot(const char *str)
294312
str = endp;
295313

296314
/* require the input to be in order */
297-
if (val < xmin || val >= xmax || val <= last_val)
315+
if (val < xmin || val >= xmax || val < last_val)
298316
goto bad_format;
299317

300-
buf_add_txid(buf, val);
318+
/* skip duplicates */
319+
if (val != last_val)
320+
buf_add_txid(buf, val);
301321
last_val = val;
302322

303323
if (*str == ',')
@@ -359,8 +379,7 @@ txid_current_snapshot(PG_FUNCTION_ARGS)
359379
{
360380
TxidSnapshot *snap;
361381
uint32 nxip,
362-
i,
363-
size;
382+
i;
364383
TxidEpoch state;
365384
Snapshot cur;
366385

@@ -372,9 +391,7 @@ txid_current_snapshot(PG_FUNCTION_ARGS)
372391

373392
/* allocate */
374393
nxip = cur->xcnt;
375-
size = TXID_SNAPSHOT_SIZE(nxip);
376-
snap = palloc(size);
377-
SET_VARSIZE(snap, size);
394+
snap = palloc(TXID_SNAPSHOT_SIZE(nxip));
378395

379396
/* fill */
380397
snap->xmin = convert_xid(cur->xmin, &state);
@@ -383,9 +400,18 @@ txid_current_snapshot(PG_FUNCTION_ARGS)
383400
for (i = 0; i < nxip; i++)
384401
snap->xip[i] = convert_xid(cur->xip[i], &state);
385402

386-
/* we want them guaranteed to be in ascending order */
403+
/*
404+
* We want them guaranteed to be in ascending order. This also removes
405+
* any duplicate xids. Normally, an XID can only be assigned to one
406+
* backend, but when preparing a transaction for two-phase commit, there
407+
* is a transient state when both the original backend and the dummy
408+
* PGPROC entry reserved for the prepared transaction hold the same XID.
409+
*/
387410
sort_snapshot(snap);
388411

412+
/* set size after sorting, because it may have removed duplicate xips */
413+
SET_VARSIZE(snap, TXID_SNAPSHOT_SIZE(snap->nxip));
414+
389415
PG_RETURN_POINTER(snap);
390416
}
391417

@@ -463,18 +489,27 @@ txid_snapshot_recv(PG_FUNCTION_ARGS)
463489
snap = palloc(TXID_SNAPSHOT_SIZE(nxip));
464490
snap->xmin = xmin;
465491
snap->xmax = xmax;
466-
snap->nxip = nxip;
467-
SET_VARSIZE(snap, TXID_SNAPSHOT_SIZE(nxip));
468492

469493
for (i = 0; i < nxip; i++)
470494
{
471495
txid cur = pq_getmsgint64(buf);
472496

473-
if (cur <= last || cur < xmin || cur >= xmax)
497+
if (cur < last || cur < xmin || cur >= xmax)
474498
goto bad_format;
499+
500+
/* skip duplicate xips */
501+
if (cur == last)
502+
{
503+
i--;
504+
nxip--;
505+
continue;
506+
}
507+
475508
snap->xip[i] = cur;
476509
last = cur;
477510
}
511+
snap->nxip = nxip;
512+
SET_VARSIZE(snap, TXID_SNAPSHOT_SIZE(nxip));
478513
PG_RETURN_POINTER(snap);
479514

480515
bad_format:

src/test/regress/expected/txid.out

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ select '12:18:14,16'::txid_snapshot;
1212
12:18:14,16
1313
(1 row)
1414

15+
select '12:16:14,14'::txid_snapshot;
16+
txid_snapshot
17+
---------------
18+
12:16:14
19+
(1 row)
20+
1521
-- errors
1622
select '31:12:'::txid_snapshot;
1723
ERROR: invalid input for txid_snapshot: "31:12:"
@@ -29,10 +35,6 @@ select '12:16:14,13'::txid_snapshot;
2935
ERROR: invalid input for txid_snapshot: "12:16:14,13"
3036
LINE 1: select '12:16:14,13'::txid_snapshot;
3137
^
32-
select '12:16:14,14'::txid_snapshot;
33-
ERROR: invalid input for txid_snapshot: "12:16:14,14"
34-
LINE 1: select '12:16:14,14'::txid_snapshot;
35-
^
3638
create temp table snapshot_test (
3739
nr integer,
3840
snap txid_snapshot

src/test/regress/sql/txid.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33
-- i/o
44
select '12:13:'::txid_snapshot;
55
select '12:18:14,16'::txid_snapshot;
6+
select '12:16:14,14'::txid_snapshot;
67

78
-- errors
89
select '31:12:'::txid_snapshot;
910
select '0:1:'::txid_snapshot;
1011
select '12:13:0'::txid_snapshot;
1112
select '12:16:14,13'::txid_snapshot;
12-
select '12:16:14,14'::txid_snapshot;
1313

1414
create temp table snapshot_test (
1515
nr integer,

0 commit comments

Comments
 (0)
0