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