Content-Length: 603203 | pFad | http://github.com/tonybelloni/postgres/commit/1518d07842dcb412ea6b8bb8172c40da7499b174

C3 Fix crash when logical decoding is invoked from a PL function. · tonybelloni/postgres@1518d07 · GitHub
Skip to content

Commit 1518d07

Browse files
committed
Fix crash when logical decoding is invoked from a PL function.
The logical decoding functions do BeginInternalSubTransaction and RollbackAndReleaseCurrentSubTransaction to clean up after themselves. It turns out that AtEOSubXact_SPI has an unrecognized assumption that we always need to cancel the active SPI operation in the SPI context that surrounds the subtransaction (if there is one). That's true when the RollbackAndReleaseCurrentSubTransaction call is coming from the SPI-using function itself, but not when it's happening inside some unrelated function invoked by a SPI query. In practice the affected callers are the various PLs. To fix, record the current subtransaction ID when we begin a SPI operation, and clean up only if that ID is the subtransaction being canceled. Also, remove AtEOSubXact_SPI's assertion that it must have cleaned up the surrounding SPI context's active tuptable. That's proven wrong by the same test case. Also clarify (or, if you prefer, reinterpret) the calling conventions for _SPI_begin_call and _SPI_end_call. The memory context cleanup in the latter means that these have always had the flavor of a matched resource-management pair, but they weren't documented that way before. Per report from Ben Chobot. Back-patch to 9.4 where logical decoding came in. In principle, the SPI changes should go all the way back, since the problem dates back to commit 7ec1c5a. But given the lack of field complaints it seems few people are using internal subtransactions in this way. So I don't feel a need to take any risks in 9.2/9.3. Discussion: https://postgr.es/m/73FBA179-C68C-4540-9473-71E865408B15@silentmedia.com
1 parent 45866c7 commit 1518d07

File tree

4 files changed

+70
-11
lines changed

4 files changed

+70
-11
lines changed

contrib/test_decoding/expected/decoding_into_rel.out

+25
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,31 @@ SELECT * FROM changeresult;
5959

6060
DROP TABLE changeresult;
6161
DROP TABLE somechange;
62+
-- check calling logical decoding from pl/pgsql
63+
CREATE FUNCTION slot_changes_wrapper(slot_name name) RETURNS SETOF TEXT AS $$
64+
BEGIN
65+
RETURN QUERY
66+
SELECT data FROM pg_logical_slot_peek_changes(slot_name, NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
67+
END$$ LANGUAGE plpgsql;
68+
SELECT * FROM slot_changes_wrapper('regression_slot');
69+
slot_changes_wrapper
70+
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
71+
BEGIN
72+
table public.changeresult: INSERT: data[text]:'BEGIN'
73+
table public.changeresult: INSERT: data[text]:'table public.changeresult: INSERT: data[text]:''BEGIN'''
74+
table public.changeresult: INSERT: data[text]:'table public.changeresult: INSERT: data[text]:''table public.somechange: INSERT: id[integer]:1'''
75+
table public.changeresult: INSERT: data[text]:'table public.changeresult: INSERT: data[text]:''COMMIT'''
76+
table public.changeresult: INSERT: data[text]:'COMMIT'
77+
table public.changeresult: INSERT: data[text]:'BEGIN'
78+
table public.changeresult: INSERT: data[text]:'table public.changeresult: INSERT: data[text]:''BEGIN'''
79+
table public.changeresult: INSERT: data[text]:'table public.changeresult: INSERT: data[text]:''table public.changeresult: INSERT: data[text]:''''BEGIN'''''''
80+
table public.changeresult: INSERT: data[text]:'table public.changeresult: INSERT: data[text]:''table public.changeresult: INSERT: data[text]:''''table public.somechange: INSERT: id[integer]:1'''''''
81+
table public.changeresult: INSERT: data[text]:'table public.changeresult: INSERT: data[text]:''table public.changeresult: INSERT: data[text]:''''COMMIT'''''''
82+
table public.changeresult: INSERT: data[text]:'table public.changeresult: INSERT: data[text]:''COMMIT'''
83+
table public.changeresult: INSERT: data[text]:'COMMIT'
84+
COMMIT
85+
(14 rows)
86+
6287
SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
6388
data
6489
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

contrib/test_decoding/sql/decoding_into_rel.sql

+11
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,16 @@ INSERT INTO changeresult
2727
SELECT * FROM changeresult;
2828
DROP TABLE changeresult;
2929
DROP TABLE somechange;
30+
31+
-- check calling logical decoding from pl/pgsql
32+
CREATE FUNCTION slot_changes_wrapper(slot_name name) RETURNS SETOF TEXT AS $$
33+
BEGIN
34+
RETURN QUERY
35+
SELECT data FROM pg_logical_slot_peek_changes(slot_name, NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
36+
END$$ LANGUAGE plpgsql;
37+
38+
SELECT * FROM slot_changes_wrapper('regression_slot');
39+
3040
SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
41+
3142
SELECT 'stop' FROM pg_drop_replication_slot('regression_slot');

src/backend/executor/spi.c

+31-11
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,8 @@ static void _SPI_cursor_operation(Portal portal,
7171
static SPIPlanPtr _SPI_make_plan_non_temp(SPIPlanPtr plan);
7272
static SPIPlanPtr _SPI_save_plan(SPIPlanPtr plan);
7373

74-
static int _SPI_begin_call(bool execmem);
75-
static int _SPI_end_call(bool procmem);
74+
static int _SPI_begin_call(bool use_exec);
75+
static int _SPI_end_call(bool use_exec);
7676
static MemoryContext _SPI_execmem(void);
7777
static MemoryContext _SPI_procmem(void);
7878
static bool _SPI_checktuples(void);
@@ -118,6 +118,7 @@ SPI_connect(void)
118118
_SPI_current->processed = 0;
119119
_SPI_current->lastoid = InvalidOid;
120120
_SPI_current->tuptable = NULL;
121+
_SPI_current->execSubid = InvalidSubTransactionId;
121122
slist_init(&_SPI_current->tuptables);
122123
_SPI_current->procCxt = NULL; /* in case we fail to create 'em */
123124
_SPI_current->execCxt = NULL;
@@ -149,7 +150,7 @@ SPI_finish(void)
149150
{
150151
int res;
151152

152-
res = _SPI_begin_call(false); /* live in procedure memory */
153+
res = _SPI_begin_call(false); /* just check we're connected */
153154
if (res < 0)
154155
return res;
155156

@@ -268,8 +269,15 @@ AtEOSubXact_SPI(bool isCommit, SubTransactionId mySubid)
268269
{
269270
slist_mutable_iter siter;
270271

271-
/* free Executor memory the same as _SPI_end_call would do */
272-
MemoryContextResetAndDeleteChildren(_SPI_current->execCxt);
272+
/*
273+
* Throw away executor state if current executor operation was started
274+
* within current subxact (essentially, force a _SPI_end_call(true)).
275+
*/
276+
if (_SPI_current->execSubid >= mySubid)
277+
{
278+
_SPI_current->execSubid = InvalidSubTransactionId;
279+
MemoryContextResetAndDeleteChildren(_SPI_current->execCxt);
280+
}
273281

274282
/* throw away any tuple tables created within current subxact */
275283
slist_foreach_modify(siter, &_SPI_current->tuptables)
@@ -293,8 +301,6 @@ AtEOSubXact_SPI(bool isCommit, SubTransactionId mySubid)
293301
MemoryContextDelete(tuptable->tuptabcxt);
294302
}
295303
}
296-
/* in particular we should have gotten rid of any in-progress table */
297-
Assert(_SPI_current->tuptable == NULL);
298304
}
299305
}
300306

@@ -2446,30 +2452,44 @@ _SPI_procmem(void)
24462452

24472453
/*
24482454
* _SPI_begin_call: begin a SPI operation within a connected procedure
2455+
*
2456+
* use_exec is true if we intend to make use of the procedure's execCxt
2457+
* during this SPI operation. We'll switch into that context, and arrange
2458+
* for it to be cleaned up at _SPI_end_call or if an error occurs.
24492459
*/
24502460
static int
2451-
_SPI_begin_call(bool execmem)
2461+
_SPI_begin_call(bool use_exec)
24522462
{
24532463
if (_SPI_current == NULL)
24542464
return SPI_ERROR_UNCONNECTED;
24552465

2456-
if (execmem) /* switch to the Executor memory context */
2466+
if (use_exec)
2467+
{
2468+
/* remember when the Executor operation started */
2469+
_SPI_current->execSubid = GetCurrentSubTransactionId();
2470+
/* switch to the Executor memory context */
24572471
_SPI_execmem();
2472+
}
24582473

24592474
return 0;
24602475
}
24612476

24622477
/*
24632478
* _SPI_end_call: end a SPI operation within a connected procedure
24642479
*
2480+
* use_exec must be the same as in the previous _SPI_begin_call
2481+
*
24652482
* Note: this currently has no failure return cases, so callers don't check
24662483
*/
24672484
static int
2468-
_SPI_end_call(bool procmem)
2485+
_SPI_end_call(bool use_exec)
24692486
{
2470-
if (procmem) /* switch to the procedure memory context */
2487+
if (use_exec)
24712488
{
2489+
/* switch to the procedure memory context */
24722490
_SPI_procmem();
2491+
/* mark Executor context no longer in use */
2492+
_SPI_current->execSubid = InvalidSubTransactionId;
24732493
/* and free Executor memory */
24742494
MemoryContextResetAndDeleteChildren(_SPI_current->execCxt);
24752495
}

src/include/executor/spi_priv.h

+3
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ typedef struct
2626
Oid lastoid;
2727
SPITupleTable *tuptable; /* tuptable currently being built */
2828

29+
/* subtransaction in which current Executor call was started */
30+
SubTransactionId execSubid;
31+
2932
/* resources of this execution context */
3033
slist_head tuptables; /* list of all live SPITupleTables */
3134
MemoryContext procCxt; /* procedure context */

0 commit comments

Comments
 (0)








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: http://github.com/tonybelloni/postgres/commit/1518d07842dcb412ea6b8bb8172c40da7499b174

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy