(self)
| 466 | |
| 467 | @threading_helper.requires_working_threading() |
| 468 | def test_trashcan_threads(self): |
| 469 | # Issue #13992: trashcan mechanism should be thread-safe |
| 470 | NESTING = 60 |
| 471 | N_THREADS = 2 |
| 472 | |
| 473 | def sleeper_gen(): |
| 474 | """A generator that releases the GIL when closed or dealloc'ed.""" |
| 475 | try: |
| 476 | yield |
| 477 | finally: |
| 478 | time.sleep(0.000001) |
| 479 | |
| 480 | class C(list): |
| 481 | # Appending to a list is atomic, which avoids the use of a lock. |
| 482 | inits = [] |
| 483 | dels = [] |
| 484 | def __init__(self, alist): |
| 485 | self[:] = alist |
| 486 | C.inits.append(None) |
| 487 | def __del__(self): |
| 488 | # This __del__ is called by subtype_dealloc(). |
| 489 | C.dels.append(None) |
| 490 | # `g` will release the GIL when garbage-collected. This |
| 491 | # helps assert subtype_dealloc's behaviour when threads |
| 492 | # switch in the middle of it. |
| 493 | g = sleeper_gen() |
| 494 | next(g) |
| 495 | # Now that __del__ is finished, subtype_dealloc will proceed |
| 496 | # to call list_dealloc, which also uses the trashcan mechanism. |
| 497 | |
| 498 | def make_nested(): |
| 499 | """Create a sufficiently nested container object so that the |
| 500 | trashcan mechanism is invoked when deallocating it.""" |
| 501 | x = C([]) |
| 502 | for i in range(NESTING): |
| 503 | x = [C([x])] |
| 504 | del x |
| 505 | |
| 506 | def run_thread(): |
| 507 | """Exercise make_nested() in a loop.""" |
| 508 | while not exit: |
| 509 | make_nested() |
| 510 | |
| 511 | old_switchinterval = sys.getswitchinterval() |
| 512 | support.setswitchinterval(1e-5) |
| 513 | try: |
| 514 | exit = [] |
| 515 | threads = [] |
| 516 | for i in range(N_THREADS): |
| 517 | t = threading.Thread(target=run_thread) |
| 518 | threads.append(t) |
| 519 | with threading_helper.start_threads(threads, lambda: exit.append(1)): |
| 520 | time.sleep(1.0) |
| 521 | finally: |
| 522 | sys.setswitchinterval(old_switchinterval) |
| 523 | gc.collect() |
| 524 | self.assertEqual(len(C.inits), len(C.dels)) |
| 525 |
nothing calls this directly
no test coverage detected