From fe7f1e3224bda568bf4eb5926a3999ab62b43ae2 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 ede996b0598ee7..5e2127d0e1d35b 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 @@ -148,6 +149,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, @@ -305,6 +307,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; @@ -670,6 +673,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: @@ -1095,6 +1102,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 b8f604eeb12ae7ba37e7259db22e9a2200e07a53 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 5e2127d0e1d35b..b89cf261767b41 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -1122,7 +1122,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 1f39cb21209b5e92857399ed5076e3f7267523ee 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 b89cf261767b41..c777d50cd7e3ef 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -1106,7 +1106,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 860764a143bdc58f156a611738c691ce08f2ed86 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`.