| 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 |
| |
| def test_pickle_dumps_with_concurrent_list_mutations(self): |
| # gh-149816: Pickling a list while another thread mutates it |
| # used to be a UAF in free-threaded mode. batch_list_exact() |
| # used PyList_GET_ITEM (borrowed) followed by Py_INCREF, and a |
| # concurrent replace/pop could free the item between those two |
| # operations. |
| shared = [list(range(20)) for _ in range(50)] |
| |
| def dumper(): |
| for _ in range(1000): |
| try: |
| pickle.dumps(shared) |
| except (RuntimeError, IndexError): |
| pass |
| |
| def mutator(): |
| for i in range(1000): |
| idx = i % 50 |
| shared[idx] = list(range(i % 20)) |
| if i % 10 == 0: |
| try: |
| shared.pop() |
| except IndexError: |
| pass |
| shared.append([i]) |
| |
| 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() |