-
-
Notifications
You must be signed in to change notification settings - Fork 33.7k
Description
This test case checks that freeing a code object on a different thread then where the co_extra was set is safe. However, the test make some assumptions about when destructors are called that aren't always true in the free-threaded build:
Lines 855 to 875 in fa58e75
| @threading_helper.requires_working_threading() | |
| def test_free_different_thread(self): | |
| # Freeing a code object on a different thread then | |
| # where the co_extra was set should be safe. | |
| f = self.get_func() | |
| class ThreadTest(threading.Thread): | |
| def __init__(self, f, test): | |
| super().__init__() | |
| self.f = f | |
| self.test = test | |
| def run(self): | |
| del self.f | |
| gc_collect() | |
| self.test.assertEqual(LAST_FREED, 500) | |
| SetExtra(f.__code__, FREE_INDEX, ctypes.c_voidp(500)) | |
| tt = ThreadTest(f, self) | |
| del f | |
| tt.start() | |
| tt.join() | |
| self.assertEqual(LAST_FREED, 500) |
In particular, the assertion self.test.assertEqual(LAST_FREED, 500) in the ThreadTest occasionally fails because LAST_FREED is still None. The underlying issue is that biased reference counting can delay the calling of the code object's destructor.
Normally, the gc_collect() calls are sufficient to ensure that the code object is collected. They sort of are -- the code object is being freed -- but it happens concurrently in the main thread and may not be finished by the time ThreadTest calls the self.test.assertEqual(LAST_FREED, 500).
The timeline I've seen when debugging this is:
- The main thread starts
ThreadTest ThreadTestdeletes the final reference tof. The total reference count is now zero, but it's represented asob_ref_local=1,ob_ref_shared=-1, soTestThreadenqueues it to be merged by the main thread.- The main thread merges the reference count fields and starts to call the code object's destructor
ThreadTestcallsgc_collect()and thenself.test.assertEqual(LAST_FREED, 500), which fails
...- The main thread finishes calling the code object's destructor, which sets
LAST_FREEDto 500.