import pickle
import threading
import unittest

from test.support import threading_helper


@threading_helper.requires_working_threading()
class TestPickleFreeThreading(unittest.TestCase):

    def test_pickle_dumps_with_concurrent_dict_mutation(self):
        # gh-146452: Pickling a dict while another thread mutates it
        # used to segfault. batch_dict_exact() iterated dict items via
        # PyDict_Next() which returns borrowed references, and a
        # concurrent pop/replace could free the value before Py_INCREF
        # got to it.
        shared = {str(i): list(range(20)) for i in range(50)}

        def dumper():
            for _ in range(1000):
                try:
                    pickle.dumps(shared)
                except RuntimeError:
                    # "dictionary changed size during iteration" is expected
                    pass

        def mutator():
            for j in range(1000):
                key = str(j % 50)
                shared[key] = list(range(j % 20))
                if j % 10 == 0:
                    shared.pop(key, None)
                    shared[key] = [j]

        threads = []
        for _ in range(10):
            threads.append(threading.Thread(target=dumper))
        threads.append(threading.Thread(target=mutator))

        with threading_helper.start_threads(threads):
            pass

if __name__ == "__main__":
    unittest.main()
