From 5c3727ccbb3dbc8945626cb45b8a72988b8856c1 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Tue, 3 Feb 2026 14:57:03 +0000 Subject: [PATCH 1/4] Clean up dead weakrefs to sqlite3 Blob objects --- Modules/_sqlite/connection.c | 39 ++++++++++++++++++++++++++++++++++++ Modules/_sqlite/connection.h | 4 ++++ 2 files changed, 43 insertions(+) diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index 892740b05e55c9..0612062b70082e 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -38,6 +38,7 @@ #include "pycore_pyerrors.h" // _PyErr_ChainExceptions1() #include "pycore_pylifecycle.h" // _Py_IsInterpreterFinalizing() #include "pycore_unicodeobject.h" // _PyUnicode_AsUTF8NoNUL +#include "pycore_weakref.h" #include @@ -143,6 +144,7 @@ class _sqlite3.Connection "pysqlite_Connection *" "clinic_state()->ConnectionTyp [clinic start generated code]*/ /*[clinic end generated code: output=da39a3ee5e6b4b0d input=67369db2faf80891]*/ +static int _pysqlite_drop_unused_blob_references(pysqlite_Connection* self); static void incref_callback_context(callback_context *ctx); static void decref_callback_context(callback_context *ctx); static void set_callback_context(callback_context **ctx_pp, @@ -300,6 +302,7 @@ pysqlite_connection_init_impl(pysqlite_Connection *self, PyObject *database, self->thread_ident = PyThread_get_thread_ident(); self->statement_cache = statement_cache; self->blobs = blobs; + self->created_blobs = 0; self->row_factory = Py_NewRef(Py_None); self->text_factory = Py_NewRef(&PyUnicode_Type); self->trace_ctx = NULL; @@ -665,6 +668,10 @@ blobopen_impl(pysqlite_Connection *self, const char *table, const char *col, goto error; } + if (_pysqlite_drop_unused_blob_references(self) < 0) { + goto error; + } + return (PyObject *)obj; error: @@ -1090,6 +1097,38 @@ final_callback(sqlite3_context *context) PyGILState_Release(threadstate); } +static int +_pysqlite_drop_unused_blob_references(pysqlite_Connection* self) +{ + /* we only need to do this once in a while */ + if (self->created_blobs++ < 200) { + return 0; + } + + self->created_blobs = 0; + + PyObject* new_list = PyList_New(0); + if (!new_list) { + return -1; + } + + assert(PyList_CheckExact(self->blobs)); + Py_ssize_t imax = PyList_GET_SIZE(self->blobs); + for (Py_ssize_t i = 0; i < imax; i++) { + PyObject* weakref = PyList_GET_ITEM(self->blobs, i); + if (_PyWeakref_IsDead(weakref)) { + continue; + } + if (PyList_Append(new_list, weakref) != 0) { + Py_DECREF(new_list); + return -1; + } + } + + Py_SETREF(self->blobs, new_list); + return 0; +} + /* Allocate a UDF/callback context structure. In order to ensure that the state * pointer always outlives the callback context, we make sure it owns a * reference to the module itself. create_callback_context() is always called diff --git a/Modules/_sqlite/connection.h b/Modules/_sqlite/connection.h index a2241bd540669c..d3346bb8949cab 100644 --- a/Modules/_sqlite/connection.h +++ b/Modules/_sqlite/connection.h @@ -73,6 +73,10 @@ typedef struct /* Lists of weak references to blobs used within this connection */ PyObject *blobs; + /* Counter for how many blobs were opened in this connection; + * May be reset to 0 at certain intervals. */ + int created_blobs; + PyObject* row_factory; /* Determines how bytestrings from SQLite are converted to Python objects: From 01af294caefea09d286248904bc68a64f2e80380 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Thu, 5 Feb 2026 22:34:05 +0000 Subject: [PATCH 2/4] Fix indentation --- Modules/_sqlite/connection.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index 0612062b70082e..15b2d2baf4809f 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -1117,7 +1117,7 @@ _pysqlite_drop_unused_blob_references(pysqlite_Connection* self) for (Py_ssize_t i = 0; i < imax; i++) { PyObject* weakref = PyList_GET_ITEM(self->blobs, i); if (_PyWeakref_IsDead(weakref)) { - continue; + continue; } if (PyList_Append(new_list, weakref) != 0) { Py_DECREF(new_list); From ba8b3ce534bb63dcaed59b39a0c340b556e7a9f1 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Thu, 5 Feb 2026 22:35:06 +0000 Subject: [PATCH 3/4] Longer intervals between weakref cleanups if list is growing Co-authored-by: Serhiy Storchaka --- Modules/_sqlite/connection.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index 15b2d2baf4809f..b91ecd81aa1741 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -1101,7 +1101,8 @@ static int _pysqlite_drop_unused_blob_references(pysqlite_Connection* self) { /* we only need to do this once in a while */ - if (self->created_blobs++ < 200) { + self->created_blobs++; + if (self->created_blobs < 200 || self->created_blobs < PyList_GET_SIZE(self->blobs) / 4) { return 0; } From ca2eec2c3fe2fb89135148b220908ca35439edcc Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Sat, 1 Aug 2026 10:31:38 +0100 Subject: [PATCH 4/4] Add NEWS entry --- .../next/Library/2026-08-01-10-31-24.gh-issue-144424.Rj5tcX.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-08-01-10-31-24.gh-issue-144424.Rj5tcX.rst diff --git a/Misc/NEWS.d/next/Library/2026-08-01-10-31-24.gh-issue-144424.Rj5tcX.rst b/Misc/NEWS.d/next/Library/2026-08-01-10-31-24.gh-issue-144424.Rj5tcX.rst new file mode 100644 index 00000000000000..604352d46c62df --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-01-10-31-24.gh-issue-144424.Rj5tcX.rst @@ -0,0 +1,2 @@ +Fixed the cleanup of weakref objects created by +:meth:`sqlite3.Connection.blobopen`.