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]