Hi hackers, I found a problem when executing the plpython function: After the plpython function returns an error, in the same session, if we continue to execute plpython function, the server panic will be caused.
*Reproduce* preparation SET max_parallel_workers_per_gather=4; SET parallel_setup_cost=1; SET min_parallel_table_scan_size ='4kB'; CREATE TABLE t(i int); INSERT INTO t SELECT generate_series(1, 10000); CREATE EXTENSION plpython3u; CREATE OR REPLACE FUNCTION test_func() RETURNS SETOF int AS $$ plpy.execute("select pg_backend_pid()") for i in range(0, 5): yield (i) $$ LANGUAGE plpython3u parallel safe; execute the function twice in the same session postgres=# SELECT test_func() from t where i>10 and i<100; ERROR: error fetching next item from iterator DETAIL: Exception: cannot start subtransactions during a parallel operation CONTEXT: Traceback (most recent call last): PL/Python function "test_func" postgres=# SELECT test_func(); server closed the connection unexpectedly This probably means the server terminated abnormally before or while processing the request. The connection to the server was lost. Attempting reset: Failed. The connection to the server was lost. Attempting reset: Failed. *Analysis* - There is an SPI call in test_func(): plpy.execute(). - Then the server will start a subtransaction by PLy_spi_subtransaction_begin(); BUT! The Python processor does not know whether an error happened during PLy_spi_subtransaction_begin(). - If there is an error that occurs in PLy_spi_subtransaction_begin(), the SPI call will be terminated but the python error indicator won't be set and the PyObject won't be free. - Then the next plpython UDF in the same session will fail due to the wrong Python environment. *Solution* Use try-catch to catch the error that occurs in PLy_spi_subtransaction_begin(), and set the python error indicator. With Regards Hao Zhang
From 891ff26ba9b52f3eba6f0d112657c3b288524de2 Mon Sep 17 00:00:00 2001 From: Hao Zhang <zhrt1446384...@gmail.com> Date: Thu, 30 Nov 2023 14:51:07 +0800 Subject: [PATCH v1] Fix bug: plpython function causes server panic. --- src/pl/plpython/plpy_cursorobject.c | 12 ++++++--- src/pl/plpython/plpy_spi.c | 41 +++++++++++++++++++++++------ src/pl/plpython/plpy_spi.h | 2 +- 3 files changed, 42 insertions(+), 13 deletions(-) diff --git a/src/pl/plpython/plpy_cursorobject.c b/src/pl/plpython/plpy_cursorobject.c index 57e8f8ec21..8bfe05815b 100644 --- a/src/pl/plpython/plpy_cursorobject.c +++ b/src/pl/plpython/plpy_cursorobject.c @@ -98,7 +98,8 @@ PLy_cursor_query(const char *query) oldcontext = CurrentMemoryContext; oldowner = CurrentResourceOwner; - PLy_spi_subtransaction_begin(oldcontext, oldowner); + if (!PLy_spi_subtransaction_begin(oldcontext, oldowner)) + return NULL; PG_TRY(); { @@ -196,7 +197,8 @@ PLy_cursor_plan(PyObject *ob, PyObject *args) oldcontext = CurrentMemoryContext; oldowner = CurrentResourceOwner; - PLy_spi_subtransaction_begin(oldcontext, oldowner); + if (!PLy_spi_subtransaction_begin(oldcontext, oldowner)) + return NULL; PG_TRY(); { @@ -333,7 +335,8 @@ PLy_cursor_iternext(PyObject *self) oldcontext = CurrentMemoryContext; oldowner = CurrentResourceOwner; - PLy_spi_subtransaction_begin(oldcontext, oldowner); + if (!PLy_spi_subtransaction_begin(oldcontext, oldowner)) + return NULL; PG_TRY(); { @@ -403,7 +406,8 @@ PLy_cursor_fetch(PyObject *self, PyObject *args) oldcontext = CurrentMemoryContext; oldowner = CurrentResourceOwner; - PLy_spi_subtransaction_begin(oldcontext, oldowner); + if (!PLy_spi_subtransaction_begin(oldcontext, oldowner)) + return NULL; PG_TRY(); { diff --git a/src/pl/plpython/plpy_spi.c b/src/pl/plpython/plpy_spi.c index ff87b27de0..58a274eda9 100644 --- a/src/pl/plpython/plpy_spi.c +++ b/src/pl/plpython/plpy_spi.c @@ -77,7 +77,8 @@ PLy_spi_prepare(PyObject *self, PyObject *args) oldcontext = CurrentMemoryContext; oldowner = CurrentResourceOwner; - PLy_spi_subtransaction_begin(oldcontext, oldowner); + if (!PLy_spi_subtransaction_begin(oldcontext, oldowner)) + return NULL; PG_TRY(); { @@ -217,7 +218,8 @@ PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit) oldcontext = CurrentMemoryContext; oldowner = CurrentResourceOwner; - PLy_spi_subtransaction_begin(oldcontext, oldowner); + if (!PLy_spi_subtransaction_begin(oldcontext, oldowner)) + return NULL; PG_TRY(); { @@ -313,7 +315,8 @@ PLy_spi_execute_query(char *query, long limit) oldcontext = CurrentMemoryContext; oldowner = CurrentResourceOwner; - PLy_spi_subtransaction_begin(oldcontext, oldowner); + if (!PLy_spi_subtransaction_begin(oldcontext, oldowner)) + return NULL; PG_TRY(); { @@ -556,7 +559,9 @@ PLy_rollback(PyObject *self, PyObject *args) * MemoryContext oldcontext = CurrentMemoryContext; * ResourceOwner oldowner = CurrentResourceOwner; * - * PLy_spi_subtransaction_begin(oldcontext, oldowner); + * if (!PLy_spi_subtransaction_begin(oldcontext, oldowner)) + * return NULL; + * * PG_TRY(); * { * <call SPI functions> @@ -573,12 +578,32 @@ PLy_rollback(PyObject *self, PyObject *args) * These utilities take care of restoring connection to the SPI manager and * setting a Python exception in case of an abort. */ -void +bool PLy_spi_subtransaction_begin(MemoryContext oldcontext, ResourceOwner oldowner) { - BeginInternalSubTransaction(NULL); - /* Want to run inside function's memory context */ - MemoryContextSwitchTo(oldcontext); + /* + * Catch the error when begin a subtransaction. + * Set the python error indicator if any error occurs to free + * the traced pyobjects. + */ + PG_TRY(); + { + BeginInternalSubTransaction(NULL); + /* Want to run inside function's memory context */ + MemoryContextSwitchTo(oldcontext); + } + PG_CATCH(); + { + MemoryContextSwitchTo(oldcontext); + CurrentResourceOwner = oldowner; + + ErrorData *edata = CopyErrorData(); + PLy_spi_exception_set(PyExc_Exception, edata); + FreeErrorData(edata); + return false; + } + PG_END_TRY(); + return true; } void diff --git a/src/pl/plpython/plpy_spi.h b/src/pl/plpython/plpy_spi.h index 98ccd21093..a43d803928 100644 --- a/src/pl/plpython/plpy_spi.h +++ b/src/pl/plpython/plpy_spi.h @@ -22,7 +22,7 @@ typedef struct PLyExceptionEntry } PLyExceptionEntry; /* handling of SPI operations inside subtransactions */ -extern void PLy_spi_subtransaction_begin(MemoryContext oldcontext, ResourceOwner oldowner); +extern bool PLy_spi_subtransaction_begin(MemoryContext oldcontext, ResourceOwner oldowner); extern void PLy_spi_subtransaction_commit(MemoryContext oldcontext, ResourceOwner oldowner); extern void PLy_spi_subtransaction_abort(MemoryContext oldcontext, ResourceOwner oldowner); -- 2.43.0