Skip to content

Commit a6c4080

Browse files
gh-120198: Fix race condition when editing __class__ with an audit hook active (GH-120195)
(cherry picked from commit 203565b) Co-authored-by: Ken Jin <kenjin@python.org>
1 parent f5289c4 commit a6c4080

File tree

4 files changed

+38
-2
lines changed

4 files changed

+38
-2
lines changed

Diff for: Lib/test/test_free_threading/test_type.py

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import threading
12
import unittest
23

34
from concurrent.futures import ThreadPoolExecutor

Diff for: Lib/test/test_super.py

+34-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
"""Unit tests for zero-argument super() & related machinery."""
22

33
import textwrap
4+
import threading
45
import unittest
56
from unittest.mock import patch
6-
from test.support import import_helper
7+
from test.support import import_helper, threading_helper
78

89

910
ADAPTIVE_WARMUP_DELAY = 2
@@ -505,6 +506,38 @@ def some(cls):
505506
for _ in range(ADAPTIVE_WARMUP_DELAY):
506507
C.some(C)
507508

509+
@threading_helper.requires_working_threading()
510+
def test___class___modification_multithreaded(self):
511+
""" Note: this test isn't actually testing anything on its own.
512+
It requires a sys audithook to be set to crash on older Python.
513+
This should be the case anyways as our test suite sets
514+
an audit hook.
515+
"""
516+
class Foo:
517+
pass
518+
519+
class Bar:
520+
pass
521+
522+
thing = Foo()
523+
def work():
524+
foo = thing
525+
for _ in range(5000):
526+
foo.__class__ = Bar
527+
type(foo)
528+
foo.__class__ = Foo
529+
type(foo)
530+
531+
532+
threads = []
533+
for _ in range(6):
534+
thread = threading.Thread(target=work)
535+
thread.start()
536+
threads.append(thread)
537+
538+
for thread in threads:
539+
thread.join()
540+
508541

509542
if __name__ == "__main__":
510543
unittest.main()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix a crash when multiple threads read and write to the same ``__class__`` of an object concurrently.

Diff for: Objects/typeobject.c

+2-1
Original file line numberDiff line numberDiff line change
@@ -6363,7 +6363,6 @@ compatible_for_assignment(PyTypeObject* oldto, PyTypeObject* newto, const char*
63636363
static int
63646364
object_set_class(PyObject *self, PyObject *value, void *closure)
63656365
{
6366-
PyTypeObject *oldto = Py_TYPE(self);
63676366

63686367
if (value == NULL) {
63696368
PyErr_SetString(PyExc_TypeError,
@@ -6383,6 +6382,8 @@ object_set_class(PyObject *self, PyObject *value, void *closure)
63836382
return -1;
63846383
}
63856384

6385+
PyTypeObject *oldto = Py_TYPE(self);
6386+
63866387
/* In versions of CPython prior to 3.5, the code in
63876388
compatible_for_assignment was not set up to correctly check for memory
63886389
layout / slot / etc. compatibility for non-HEAPTYPE classes, so we just

0 commit comments

Comments
 (0)