Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions Lib/test/test_descr.py
Original file line number Diff line number Diff line change
Expand Up @@ -5084,6 +5084,35 @@ class X(object):
with self.assertRaises(AttributeError):
del X.__abstractmethods__

def test_abstractmethods_flag(self):
IS_ABSTRACT = 1 << 20

class X:
pass

def check(abstract):
self.assertEqual(bool(X.__flags__ & IS_ABSTRACT), abstract)
if abstract:
self.assertRaises(TypeError, X)
else:
X()

check(False)
X.__abstractmethods__ = frozenset()
self.assertEqual(X.__abstractmethods__, frozenset())
check(False)
X.__abstractmethods__ = frozenset({"f"})
check(True)
X.__abstractmethods__ = frozenset({"f", "g"})
self.assertEqual(X.__abstractmethods__, frozenset({"f", "g"}))
check(True)
X.__abstractmethods__ = frozenset()
check(False)
X.__abstractmethods__ = frozenset({"f"})
check(True)
del X.__abstractmethods__
check(False)

def test_gh55664(self):
# gh-55664: issue a warning when the
# __dict__ of a class contains non-string keys
Expand Down
36 changes: 35 additions & 1 deletion Lib/test/test_free_threading/test_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,9 +266,12 @@ class Dummy:
pass

Dummy()
abstract = frozenset({"x"})
concrete = frozenset()
while not stop_all[0]:
try:
Dummy.__abstractmethods__ = frozenset()
Dummy.__abstractmethods__ = abstract
Dummy.__abstractmethods__ = concrete
except Exception:
pass

Expand Down Expand Up @@ -637,6 +640,37 @@ def creator():
pass
self.assertEqual(mismatches, [])

def test_concurrent_abc_subclass_creation(self):
from abc import ABC, abstractmethod
from collections.abc import Sized

N = 200
errors = []

def create_concrete():
for _ in range(N):
class C(Sized):
def __len__(self):
return 0
if not isinstance(C(), Sized):
errors.append("concrete")

def create_abstract():
for _ in range(N):
class D(ABC):
@abstractmethod
def f(self): ...
try:
D()
except TypeError:
pass
else:
errors.append("abstract")

threading_helper.run_concurrently(
[create_concrete, create_concrete, create_abstract])
self.assertEqual(errors, [])


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Creating a concrete subclass of an :term:`abstract base class` no longer
stops the world in the :term:`free-threaded build`. Setting
``__abstractmethods__`` now only does so when the new value changes
whether the class is abstract.
14 changes: 8 additions & 6 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1679,12 +1679,14 @@ type_set_abstractmethods(PyObject *tp, PyObject *value, void *Py_UNUSED(closure)
}

BEGIN_TYPE_LOCK();
types_stop_world();
if (abstract)
type_add_flags(type, Py_TPFLAGS_IS_ABSTRACT);
else
type_clear_flags(type, Py_TPFLAGS_IS_ABSTRACT);
types_start_world();
if (abstract != ((type->tp_flags & Py_TPFLAGS_IS_ABSTRACT) != 0)) {
types_stop_world();
if (abstract)
type_add_flags(type, Py_TPFLAGS_IS_ABSTRACT);
else
type_clear_flags(type, Py_TPFLAGS_IS_ABSTRACT);
types_start_world();
}
// Invalidate the version tag after update, while TYPE_LOCK is
// still held.
ASSERT_TYPE_LOCK_HELD();
Expand Down
Loading