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()