From da5c4a6790dd19f58999c47da8296d08056fe9f0 Mon Sep 17 00:00:00 2001 From: raminfp Date: Fri, 20 Feb 2026 19:11:42 +0330 Subject: [PATCH 1/6] gh-145040: Fix crash in sqlite3 when connection is closed during aggregate callback Fix a segmentation fault in the _sqlite module that occurs when Connection.close() is called inside an aggregate step() callback. After stmt_step() returns, _pysqlite_query_execute() calls sqlite3_last_insert_rowid(self->connection->db) without checking if self->connection->db is still valid. If the connection was closed during the callback, self->connection->db is NULL, causing a NULL pointer dereference. The fix adds a NULL check for self->connection->db after stmt_step() returns, raising ProgrammingError instead of crashing. --- Lib/test/test_sqlite3/test_userfunctions.py | 23 +++++++++++++++++++ ...00.gh-issue-145040.sqlite3-close-crash.rst | 6 +++++ Modules/_sqlite/cursor.c | 7 +++++- 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2026-02-20-00-00-00.gh-issue-145040.sqlite3-close-crash.rst diff --git a/Lib/test/test_sqlite3/test_userfunctions.py b/Lib/test/test_sqlite3/test_userfunctions.py index 11cf877a011c78..662ef42472f8ca 100644 --- a/Lib/test/test_sqlite3/test_userfunctions.py +++ b/Lib/test/test_sqlite3/test_userfunctions.py @@ -723,6 +723,29 @@ def test_agg_keyword_args(self): 'takes exactly 3 positional arguments'): self.con.create_aggregate("test", 1, aggregate_class=AggrText) + def test_aggr_close_conn_in_step(self): + """Connection.close() in an aggregate step callback must not crash.""" + con = sqlite.connect(":memory:", autocommit=True) + cur = con.cursor() + cur.execute("CREATE TABLE t(x INTEGER)") + for i in range(50): + cur.execute("INSERT INTO t VALUES (?)", (i,)) + + class CloseConnAgg: + def __init__(self): + self.total = 0 + + def step(self, value): + self.total += value + con.close() + + def finalize(self): + return self.total + + con.create_aggregate("agg_close", 1, CloseConnAgg) + with self.assertRaises(sqlite.ProgrammingError): + con.execute("SELECT agg_close(x) FROM t") + class AuthorizerTests(unittest.TestCase): @staticmethod diff --git a/Misc/NEWS.d/next/Library/2026-02-20-00-00-00.gh-issue-145040.sqlite3-close-crash.rst b/Misc/NEWS.d/next/Library/2026-02-20-00-00-00.gh-issue-145040.sqlite3-close-crash.rst new file mode 100644 index 00000000000000..9f4510e8a16796 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-02-20-00-00-00.gh-issue-145040.sqlite3-close-crash.rst @@ -0,0 +1,6 @@ +Fixed a crash in the :mod:`sqlite3` module when +:meth:`~sqlite3.Connection.close` is called on the connection during an +aggregate callback (e.g., in the ``step`` method). The interpreter now raises +:exc:`~sqlite3.ProgrammingError` instead of crashing with a segmentation +fault. + diff --git a/Modules/_sqlite/cursor.c b/Modules/_sqlite/cursor.c index 5a61e43617984d..115aef6fb0874a 100644 --- a/Modules/_sqlite/cursor.c +++ b/Modules/_sqlite/cursor.c @@ -906,6 +906,11 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation } rc = stmt_step(self->statement->st); + if (self->connection->db == NULL) { + PyErr_SetString(state->ProgrammingError, + "Cannot operate on a closed database."); + goto error; + } if (rc != SQLITE_DONE && rc != SQLITE_ROW) { if (PyErr_Occurred()) { /* there was an error that occurred in a user-defined callback */ @@ -967,7 +972,7 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation Py_XDECREF(parameters); } - if (!multiple) { + if (!multiple && self->connection->db) { sqlite_int64 lastrowid; Py_BEGIN_ALLOW_THREADS From 1ecb567b3b91e432a7c5c6699e422054dcde90b3 Mon Sep 17 00:00:00 2001 From: Ramin Farajpour Cami Date: Fri, 20 Feb 2026 20:54:18 +0330 Subject: [PATCH 2/6] Update Misc/NEWS.d/next/Library/2026-02-20-00-00-00.gh-issue-145040.sqlite3-close-crash.rst Co-authored-by: Benedikt Johannes --- .../2026-02-20-00-00-00.gh-issue-145040.sqlite3-close-crash.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2026-02-20-00-00-00.gh-issue-145040.sqlite3-close-crash.rst b/Misc/NEWS.d/next/Library/2026-02-20-00-00-00.gh-issue-145040.sqlite3-close-crash.rst index 9f4510e8a16796..affd20f89bdb33 100644 --- a/Misc/NEWS.d/next/Library/2026-02-20-00-00-00.gh-issue-145040.sqlite3-close-crash.rst +++ b/Misc/NEWS.d/next/Library/2026-02-20-00-00-00.gh-issue-145040.sqlite3-close-crash.rst @@ -3,4 +3,3 @@ Fixed a crash in the :mod:`sqlite3` module when aggregate callback (e.g., in the ``step`` method). The interpreter now raises :exc:`~sqlite3.ProgrammingError` instead of crashing with a segmentation fault. - From 05086f0c108b1d0be1b08ff7ddf66241cba75b44 Mon Sep 17 00:00:00 2001 From: raminfp Date: Sat, 21 Feb 2026 03:42:05 +0330 Subject: [PATCH 3/6] gh-145040: Fix crash in sqlite3 when connection is closed from within a callback --- Lib/test/test_sqlite3/test_userfunctions.py | 98 ++++++++++++++++++- ...00.gh-issue-145040.sqlite3-close-crash.rst | 11 ++- Modules/_sqlite/cursor.c | 13 ++- 3 files changed, 114 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_sqlite3/test_userfunctions.py b/Lib/test/test_sqlite3/test_userfunctions.py index 662ef42472f8ca..3d47edf6ab98f1 100644 --- a/Lib/test/test_sqlite3/test_userfunctions.py +++ b/Lib/test/test_sqlite3/test_userfunctions.py @@ -587,6 +587,33 @@ def value(self): return 1 << 65 self.assertRaisesRegex(sqlite.DataError, "string or blob too big", self.cur.execute, self.query % "err_val_ret") + def test_close_conn_in_window_func_value(self): + """gh-145040: closing connection in window function value() callback.""" + con = sqlite.connect(":memory:", autocommit=True) + con.execute("CREATE TABLE t(x INTEGER)") + con.executemany("INSERT INTO t VALUES(?)", + [(i,) for i in range(20)]) + + class CloseConnWindow: + def step(self, value): + pass + def finalize(self): + return 0 + def value(self): + con.close() + return 0 + def inverse(self, value): + pass + + con.create_window_function("evil_win", 1, CloseConnWindow) + msg = "from within a callback" + with self.assertRaisesRegex(sqlite.ProgrammingError, msg): + cursor = con.execute( + "SELECT evil_win(x) OVER " + "(ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) FROM t" + ) + list(cursor) + class AggregateTests(unittest.TestCase): def setUp(self): @@ -743,9 +770,78 @@ def finalize(self): return self.total con.create_aggregate("agg_close", 1, CloseConnAgg) - with self.assertRaises(sqlite.ProgrammingError): + msg = "from within a callback" + with self.assertRaisesRegex(sqlite.ProgrammingError, msg): con.execute("SELECT agg_close(x) FROM t") + def test_close_conn_in_udf_during_executemany(self): + """gh-145040: closing connection in UDF during executemany.""" + con = sqlite.connect(":memory:", autocommit=True) + con.execute("CREATE TABLE t(x)") + + def close_conn(x): + con.close() + return x + + con.create_function("close_conn", 1, close_conn) + msg = "from within a callback" + with self.assertRaisesRegex(sqlite.ProgrammingError, msg): + con.executemany("INSERT INTO t VALUES(close_conn(?))", + [(i,) for i in range(10)]) + + def test_close_conn_in_progress_handler_during_iternext(self): + """gh-145040: closing connection in progress handler during iteration.""" + con = sqlite.connect(":memory:", autocommit=True) + con.execute("CREATE TABLE t(x)") + con.executemany("INSERT INTO t VALUES(?)", + [(i,) for i in range(100)]) + + count = 0 + def close_progress(): + nonlocal count + count += 1 + if count >= 5: + con.close() + return 1 + return 0 + + cursor = con.execute("SELECT * FROM t") + con.set_progress_handler(close_progress, 1) + msg = "from within a callback" + import test.support + with test.support.catch_unraisable_exception(): + with self.assertRaisesRegex(sqlite.ProgrammingError, msg): + for row in cursor: + pass + del cursor + gc_collect() + + def test_close_conn_in_collation_callback(self): + """gh-145040: closing connection in collation callback.""" + con = sqlite.connect(":memory:", autocommit=True) + con.execute("CREATE TABLE t(x TEXT)") + con.executemany("INSERT INTO t VALUES(?)", + [(f"item_{i}",) for i in range(50)]) + + count = 0 + def evil_collation(a, b): + nonlocal count + count += 1 + if count == 10: + con.close() + if a < b: + return -1 + elif a > b: + return 1 + return 0 + + con.create_collation("evil_coll", evil_collation) + msg = "from within a callback" + with self.assertRaisesRegex(sqlite.ProgrammingError, msg): + con.execute( + "SELECT * FROM t ORDER BY x COLLATE evil_coll" + ) + class AuthorizerTests(unittest.TestCase): @staticmethod diff --git a/Misc/NEWS.d/next/Library/2026-02-20-00-00-00.gh-issue-145040.sqlite3-close-crash.rst b/Misc/NEWS.d/next/Library/2026-02-20-00-00-00.gh-issue-145040.sqlite3-close-crash.rst index affd20f89bdb33..291672919c6b79 100644 --- a/Misc/NEWS.d/next/Library/2026-02-20-00-00-00.gh-issue-145040.sqlite3-close-crash.rst +++ b/Misc/NEWS.d/next/Library/2026-02-20-00-00-00.gh-issue-145040.sqlite3-close-crash.rst @@ -1,5 +1,6 @@ -Fixed a crash in the :mod:`sqlite3` module when -:meth:`~sqlite3.Connection.close` is called on the connection during an -aggregate callback (e.g., in the ``step`` method). The interpreter now raises -:exc:`~sqlite3.ProgrammingError` instead of crashing with a segmentation -fault. +Fixed a crash in the :mod:`sqlite3` module caused by closing the database +connection from within a callback function invoked during +:func:`sqlite3_step` (e.g., an aggregate ``step``, a user-defined function +via :meth:`~sqlite3.Connection.create_function`, a progress handler, or a +collation callback). Raise :exc:`~sqlite3.ProgrammingError` instead of +crashing. diff --git a/Modules/_sqlite/cursor.c b/Modules/_sqlite/cursor.c index 115aef6fb0874a..282a111a868ff0 100644 --- a/Modules/_sqlite/cursor.c +++ b/Modules/_sqlite/cursor.c @@ -908,7 +908,8 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation rc = stmt_step(self->statement->st); if (self->connection->db == NULL) { PyErr_SetString(state->ProgrammingError, - "Cannot operate on a closed database."); + "Cannot close the database connection " + "from within a callback function."); goto error; } if (rc != SQLITE_DONE && rc != SQLITE_ROW) { @@ -972,7 +973,7 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation Py_XDECREF(parameters); } - if (!multiple && self->connection->db) { + if (!multiple) { sqlite_int64 lastrowid; Py_BEGIN_ALLOW_THREADS @@ -1161,6 +1162,14 @@ pysqlite_cursor_iternext(PyObject *op) return NULL; } int rc = stmt_step(stmt); + if (self->connection->db == NULL) { + Py_DECREF(row); + Py_CLEAR(self->statement); + PyErr_SetString(self->connection->state->ProgrammingError, + "Cannot close the database connection " + "from within a callback function."); + return NULL; + } if (rc == SQLITE_DONE) { if (self->statement->is_dml) { self->rowcount = (long)sqlite3_changes(self->connection->db); From c7dbc7e54e1102a9b28ac48b982dd5ccf5affeb4 Mon Sep 17 00:00:00 2001 From: raminfp Date: Sat, 21 Feb 2026 03:54:05 +0330 Subject: [PATCH 4/6] Fix NEWS: use literal markup for sqlite3_step() --- .../2026-02-20-00-00-00.gh-issue-145040.sqlite3-close-crash.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2026-02-20-00-00-00.gh-issue-145040.sqlite3-close-crash.rst b/Misc/NEWS.d/next/Library/2026-02-20-00-00-00.gh-issue-145040.sqlite3-close-crash.rst index 291672919c6b79..08225bf941d813 100644 --- a/Misc/NEWS.d/next/Library/2026-02-20-00-00-00.gh-issue-145040.sqlite3-close-crash.rst +++ b/Misc/NEWS.d/next/Library/2026-02-20-00-00-00.gh-issue-145040.sqlite3-close-crash.rst @@ -1,6 +1,6 @@ Fixed a crash in the :mod:`sqlite3` module caused by closing the database connection from within a callback function invoked during -:func:`sqlite3_step` (e.g., an aggregate ``step``, a user-defined function +``sqlite3_step()`` (e.g., an aggregate ``step``, a user-defined function via :meth:`~sqlite3.Connection.create_function`, a progress handler, or a collation callback). Raise :exc:`~sqlite3.ProgrammingError` instead of crashing. From eb69460eed1ad0aeaa5ae100601064d79ba671ec Mon Sep 17 00:00:00 2001 From: raminfp Date: Fri, 27 Feb 2026 08:54:55 +0330 Subject: [PATCH 5/6] gh-145040: Prevent closing sqlite3 connection from within a callback Instead of detecting a closed connection after the damage has been done, prevent Connection.close() from succeeding while a SQLite callback is executing. This aligns with the SQLite C API docs, which state that applications must not close the database connection from within a callback. Add an in_callback counter to the connection object, incremented before stmt_step() and decremented after. If close() is called while the counter is positive, ProgrammingError is raised and the database connection remains open. A counter (rather than a boolean flag) is used to correctly handle nested callbacks. Also convert test docstrings to comments per reviewer feedback, and add a test for the nested callback scenario. --- Lib/test/test_sqlite3/test_userfunctions.py | 33 +++++++++++++++++---- Modules/_sqlite/connection.c | 12 ++++++++ Modules/_sqlite/connection.h | 9 ++++++ Modules/_sqlite/cursor.c | 12 ++++++-- 4 files changed, 59 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_sqlite3/test_userfunctions.py b/Lib/test/test_sqlite3/test_userfunctions.py index 3d47edf6ab98f1..c72aee4435aeef 100644 --- a/Lib/test/test_sqlite3/test_userfunctions.py +++ b/Lib/test/test_sqlite3/test_userfunctions.py @@ -588,7 +588,7 @@ def value(self): return 1 << 65 self.cur.execute, self.query % "err_val_ret") def test_close_conn_in_window_func_value(self): - """gh-145040: closing connection in window function value() callback.""" + # gh-145040: closing connection in window function value() callback. con = sqlite.connect(":memory:", autocommit=True) con.execute("CREATE TABLE t(x INTEGER)") con.executemany("INSERT INTO t VALUES(?)", @@ -751,7 +751,7 @@ def test_agg_keyword_args(self): self.con.create_aggregate("test", 1, aggregate_class=AggrText) def test_aggr_close_conn_in_step(self): - """Connection.close() in an aggregate step callback must not crash.""" + # Connection.close() in an aggregate step callback must not crash. con = sqlite.connect(":memory:", autocommit=True) cur = con.cursor() cur.execute("CREATE TABLE t(x INTEGER)") @@ -774,8 +774,31 @@ def finalize(self): with self.assertRaisesRegex(sqlite.ProgrammingError, msg): con.execute("SELECT agg_close(x) FROM t") + def test_close_conn_in_nested_callback(self): + # gh-145040: close() must be prevented even in nested callbacks. + con = sqlite.connect(":memory:", autocommit=True) + con.execute("CREATE TABLE t(x INTEGER)") + for i in range(5): + con.execute("INSERT INTO t VALUES(?)", (i,)) + + def outer_func(x): + con.close() + return x + + def inner_func(x): + return x * 10 + + con.create_function("outer_func", 1, outer_func) + con.create_function("inner_func", 1, inner_func) + msg = "from within a callback" + with self.assertRaisesRegex(sqlite.ProgrammingError, msg): + con.execute("SELECT outer_func(inner_func(x)) FROM t") + # Connection must still be usable after the failed close attempt. + self.assertEqual(con.execute("SELECT 1").fetchone(), (1,)) + con.close() + def test_close_conn_in_udf_during_executemany(self): - """gh-145040: closing connection in UDF during executemany.""" + # gh-145040: closing connection in UDF during executemany. con = sqlite.connect(":memory:", autocommit=True) con.execute("CREATE TABLE t(x)") @@ -790,7 +813,7 @@ def close_conn(x): [(i,) for i in range(10)]) def test_close_conn_in_progress_handler_during_iternext(self): - """gh-145040: closing connection in progress handler during iteration.""" + # gh-145040: closing connection in progress handler during iteration. con = sqlite.connect(":memory:", autocommit=True) con.execute("CREATE TABLE t(x)") con.executemany("INSERT INTO t VALUES(?)", @@ -817,7 +840,7 @@ def close_progress(): gc_collect() def test_close_conn_in_collation_callback(self): - """gh-145040: closing connection in collation callback.""" + # gh-145040: closing connection in collation callback. con = sqlite.connect(":memory:", autocommit=True) con.execute("CREATE TABLE t(x TEXT)") con.executemany("INSERT INTO t VALUES(?)", diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index af63271b9fd971..f1297ec6dbe23a 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -302,6 +302,8 @@ pysqlite_connection_init_impl(pysqlite_Connection *self, PyObject *database, self->blobs = blobs; self->row_factory = Py_NewRef(Py_None); self->text_factory = Py_NewRef(&PyUnicode_Type); + self->in_callback = 0; + self->close_attempted_in_callback = 0; self->trace_ctx = NULL; self->progress_ctx = NULL; self->authorizer_ctx = NULL; @@ -655,6 +657,16 @@ pysqlite_connection_close_impl(pysqlite_Connection *self) return NULL; } + if (self->in_callback > 0) { + self->close_attempted_in_callback = 1; + PyTypeObject *tp = Py_TYPE(self); + pysqlite_state *state = pysqlite_get_state_by_type(tp); + PyErr_SetString(state->ProgrammingError, + "Cannot close the database connection " + "from within a callback function."); + return NULL; + } + pysqlite_close_all_blobs(self); Py_CLEAR(self->statement_cache); if (connection_close(self) < 0) { diff --git a/Modules/_sqlite/connection.h b/Modules/_sqlite/connection.h index a2241bd540669c..ddc8e4bf2ee93a 100644 --- a/Modules/_sqlite/connection.h +++ b/Modules/_sqlite/connection.h @@ -65,6 +65,15 @@ typedef struct int initialized; + /* set to 1 while a SQLite callback (UDF, aggregate, progress handler, + * etc.) is executing; used to prevent closing the connection from + * within a callback, which is illegal per the SQLite C API docs */ + int in_callback; + + /* set to 1 when close() is attempted during a callback; checked after + * stmt_step() returns to raise the appropriate ProgrammingError */ + int close_attempted_in_callback; + /* thread identification of the thread the connection was created in */ unsigned long thread_ident; diff --git a/Modules/_sqlite/cursor.c b/Modules/_sqlite/cursor.c index 282a111a868ff0..319a40f652e97d 100644 --- a/Modules/_sqlite/cursor.c +++ b/Modules/_sqlite/cursor.c @@ -905,8 +905,12 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation goto error; } + self->connection->in_callback++; rc = stmt_step(self->statement->st); - if (self->connection->db == NULL) { + self->connection->in_callback--; + if (self->connection->close_attempted_in_callback) { + self->connection->close_attempted_in_callback = 0; + PyErr_Clear(); PyErr_SetString(state->ProgrammingError, "Cannot close the database connection " "from within a callback function."); @@ -1161,10 +1165,14 @@ pysqlite_cursor_iternext(PyObject *op) if (row == NULL) { return NULL; } + self->connection->in_callback++; int rc = stmt_step(stmt); - if (self->connection->db == NULL) { + self->connection->in_callback--; + if (self->connection->close_attempted_in_callback) { + self->connection->close_attempted_in_callback = 0; Py_DECREF(row); Py_CLEAR(self->statement); + PyErr_Clear(); PyErr_SetString(self->connection->state->ProgrammingError, "Cannot close the database connection " "from within a callback function."); From 6f07252ce9b1ebb75b22ccca4ad1f5508dc215c8 Mon Sep 17 00:00:00 2001 From: raminfp Date: Fri, 27 Feb 2026 09:07:58 +0330 Subject: [PATCH 6/6] gh-145040: Fix close_attempted_in_callback flag consumed by nested callbacks Only check and consume the close_attempted_in_callback flag when in_callback reaches zero (the outermost level). Previously, a nested stmt_step() inside a callback could consume the flag, causing the outermost caller to miss the error. --- Lib/test/test_sqlite3/test_userfunctions.py | 27 +++++++++++++++++++++ Modules/_sqlite/cursor.c | 8 ++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_sqlite3/test_userfunctions.py b/Lib/test/test_sqlite3/test_userfunctions.py index c72aee4435aeef..d31497b6956188 100644 --- a/Lib/test/test_sqlite3/test_userfunctions.py +++ b/Lib/test/test_sqlite3/test_userfunctions.py @@ -797,6 +797,33 @@ def inner_func(x): self.assertEqual(con.execute("SELECT 1").fetchone(), (1,)) con.close() + def test_close_conn_in_nested_callback_caught(self): + # gh-145040: close attempt must propagate even if the exception + # is caught inside the callback and a nested execute consumes + # the flag. + con = sqlite.connect(":memory:", autocommit=True) + con.execute("CREATE TABLE t(x INTEGER)") + con.execute("INSERT INTO t VALUES(1)") + + def swallow_close(x): + try: + con.close() + except sqlite.ProgrammingError: + pass + try: + con.execute("SELECT 1") + except sqlite.ProgrammingError: + pass + return x + + con.create_function("swallow_close", 1, swallow_close) + msg = "from within a callback" + with self.assertRaisesRegex(sqlite.ProgrammingError, msg): + con.execute("SELECT swallow_close(x) FROM t") + # Connection must still be usable. + self.assertEqual(con.execute("SELECT 1").fetchone(), (1,)) + con.close() + def test_close_conn_in_udf_during_executemany(self): # gh-145040: closing connection in UDF during executemany. con = sqlite.connect(":memory:", autocommit=True) diff --git a/Modules/_sqlite/cursor.c b/Modules/_sqlite/cursor.c index 319a40f652e97d..323c95acbcb291 100644 --- a/Modules/_sqlite/cursor.c +++ b/Modules/_sqlite/cursor.c @@ -908,7 +908,9 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation self->connection->in_callback++; rc = stmt_step(self->statement->st); self->connection->in_callback--; - if (self->connection->close_attempted_in_callback) { + if (self->connection->close_attempted_in_callback + && self->connection->in_callback == 0) + { self->connection->close_attempted_in_callback = 0; PyErr_Clear(); PyErr_SetString(state->ProgrammingError, @@ -1168,7 +1170,9 @@ pysqlite_cursor_iternext(PyObject *op) self->connection->in_callback++; int rc = stmt_step(stmt); self->connection->in_callback--; - if (self->connection->close_attempted_in_callback) { + if (self->connection->close_attempted_in_callback + && self->connection->in_callback == 0) + { self->connection->close_attempted_in_callback = 0; Py_DECREF(row); Py_CLEAR(self->statement);