gh-155781: Hold strong references to sqlite3 converters (#155784)

kumaraditya303 <[email protected]>
Newsgroups gmane.comp.python.cvs
Message-ID <[email protected]>
https://github.com/python/cpython/commit/65e149a15f31def0b7c44bf087b69428b615bcc3
commit: 65e149a15f31def0b7c44bf087b69428b615bcc3
branch: main
author: cui fliter <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2026-08-21T20:42:29+05:30
summary:

gh-155781: Hold strong references to sqlite3 converters (#155784)

files:
A Lib/test/test_free_threading/test_sqlite3.py
A Misc/NEWS.d/next/Library/2026-08-14-11-12-18.gh-issue-155781.sqlite-converter.rst
M Modules/_sqlite/cursor.c

diff --git a/Lib/test/test_free_threading/test_sqlite3.py b/Lib/test/test_free_threading/test_sqlite3.py
new file mode 100644
index 00000000000000..456771726f7a8f
--- /dev/null
+++ b/Lib/test/test_free_threading/test_sqlite3.py
@@ -0,0 +1,54 @@
+import unittest
+
+from test import support
+from test.support import import_helper, threading_helper
+from test.support.threading_helper import run_concurrently
+
+
+sqlite3 = import_helper.import_module("sqlite3")
+
+
+NAME = "FREE_THREADING_RACE"
+NCOLUMNS = 64
+NITER = 3000
+QUERY = "select " + ", ".join(
+    f"'value' as 'c{i} [{NAME}]'" for i in range(NCOLUMNS)
+)
+
+
+@threading_helper.requires_working_threading()
+class TestSQLite3(unittest.TestCase):
+    def test_concurrent_converter_replacement(self):
+        # gh-155781: Converter lookups must retain a strong reference while
+        # another thread updates the public converter registry.
+        class Converter:
+            __slots__ = ("value",)
+
+            def __init__(self, value):
+                self.value = value
+
+            def __call__(self, value):
+                return self.value
+
+        def reader():
+            con = sqlite3.connect(":memory:",
+                                  detect_types=sqlite3.PARSE_COLNAMES)
+            try:
+                for _ in range(NITER):
+                    row = con.execute(QUERY).fetchone()
+                    self.assertEqual(len(row), NCOLUMNS)
+            finally:
+                con.close()
+
+        def mutator():
+            for i in range(NITER * NCOLUMNS):
+                sqlite3.register_converter(NAME, Converter(i))
+                if i % 3 == 0:
+                    sqlite3.converters.pop(NAME, None)
+
+        with support.swap_item(sqlite3.converters, NAME, Converter(0)):
+            run_concurrently([reader] * 8 + [mutator] * 2)
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/Misc/NEWS.d/next/Library/2026-08-14-11-12-18.gh-issue-155781.sqlite-converter.rst b/Misc/NEWS.d/next/Library/2026-08-14-11-12-18.gh-issue-155781.sqlite-converter.rst
new file mode 100644
index 00000000000000..cd6655899d5955
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-08-14-11-12-18.gh-issue-155781.sqlite-converter.rst
@@ -0,0 +1,3 @@
+Fix a possible crash in free-threaded builds when the :mod:`sqlite3`
+converter registry is modified concurrently while a cursor builds its row
+cast map.
diff --git a/Modules/_sqlite/cursor.c b/Modules/_sqlite/cursor.c
index 96fb3dc9e42d3a..dd4204ff267d0a 100644
--- a/Modules/_sqlite/cursor.c
+++ b/Modules/_sqlite/cursor.c
@@ -223,8 +223,11 @@ _pysqlite_get_converter(pysqlite_state *state, const char *keystr,
         return NULL;
     }
 
-    retval = PyDict_GetItemWithError(state->converters, upcase_key);
+    int rc = PyDict_GetItemRef(state->converters, upcase_key, &retval);
     Py_DECREF(upcase_key);
+    if (rc < 0) {
+        return NULL;
+    }
 
     return retval;
 }
@@ -296,11 +299,10 @@ pysqlite_build_row_cast_map(pysqlite_Cursor* self)
             }
         }
 
-        if (!converter) {
-            converter = Py_None;
-        }
-
-        if (PyList_Append(self->row_cast_map, converter) != 0) {
+        int rc = PyList_Append(self->row_cast_map,
+                               converter ? converter : Py_None);
+        Py_XDECREF(converter);
+        if (rc != 0) {
             Py_CLEAR(self->row_cast_map);
             return -1;
         }

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.