|
5 | 5 | from threading import Barrier, Thread |
6 | 6 | from unittest import TestCase |
7 | 7 | import sys |
8 | | -from test.support import import_helper, threading_helper |
| 8 | +from test.support import import_helper, threading_helper, Py_GIL_DISABLED |
9 | 9 |
|
10 | 10 | _testinternalcapi = import_helper.import_module("_testinternalcapi") |
11 | 11 |
|
@@ -401,6 +401,46 @@ class B(A): |
401 | 401 | with threading_helper.start_threads(threads): |
402 | 402 | pass |
403 | 403 |
|
| 404 | + @unittest.skipUnless(Py_GIL_DISABLED, |
| 405 | + "race only occurs on the free-threaded build") |
| 406 | + def test_dir_racing_class_dict_insert(self): |
| 407 | + # gh-157217: dir() iterated a mappingproxy of the class dict without |
| 408 | + # holding that dict's critical section. A concurrent insert into the |
| 409 | + # class dict (for example a lazy __annotations_cache__) then raised |
| 410 | + # RuntimeError: dictionary changed size during iteration. |
| 411 | + errors = [] |
| 412 | + |
| 413 | + class C: |
| 414 | + x: int |
| 415 | + |
| 416 | + for i in range(200): |
| 417 | + setattr(C, f'attr_{i}', i) |
| 418 | + |
| 419 | + def reader(): |
| 420 | + barrier.wait() |
| 421 | + for _ in range(400): |
| 422 | + try: |
| 423 | + dir(C) |
| 424 | + dict(vars(C)) |
| 425 | + {**vars(C)} |
| 426 | + except RuntimeError as exc: |
| 427 | + errors.append(exc) |
| 428 | + |
| 429 | + def writer(): |
| 430 | + barrier.wait() |
| 431 | + # First access stores __annotations_cache__ on the class. |
| 432 | + C.__annotations__ |
| 433 | + for i in range(200): |
| 434 | + setattr(C, f'extra_{i}', i) |
| 435 | + |
| 436 | + n_readers = 4 |
| 437 | + barrier = threading.Barrier(n_readers + 1) |
| 438 | + threads = [Thread(target=reader) for _ in range(n_readers)] |
| 439 | + threads.append(Thread(target=writer)) |
| 440 | + with threading_helper.start_threads(threads): |
| 441 | + pass |
| 442 | + self.assertEqual(errors, []) |
| 443 | + |
404 | 444 |
|
405 | 445 | if __name__ == "__main__": |
406 | 446 | unittest.main() |
0 commit comments