mirror of https://github.com/python/cpython.git
291 lines
7.0 KiB
Python
291 lines
7.0 KiB
Python
# It's most useful to run these tests with ThreadSanitizer enabled.
|
|
import sys
|
|
import functools
|
|
import threading
|
|
import time
|
|
import unittest
|
|
import _testinternalcapi
|
|
|
|
from test.support import threading_helper
|
|
|
|
|
|
class TestBase(unittest.TestCase):
|
|
pass
|
|
|
|
|
|
def do_race(func1, func2):
|
|
"""Run func1() and func2() repeatedly in separate threads."""
|
|
n = 1000
|
|
|
|
barrier = threading.Barrier(2)
|
|
|
|
def repeat(func):
|
|
barrier.wait()
|
|
for _i in range(n):
|
|
func()
|
|
|
|
threads = [
|
|
threading.Thread(target=functools.partial(repeat, func1)),
|
|
threading.Thread(target=functools.partial(repeat, func2)),
|
|
]
|
|
for thread in threads:
|
|
thread.start()
|
|
for thread in threads:
|
|
thread.join()
|
|
|
|
|
|
@threading_helper.requires_working_threading()
|
|
class TestRaces(TestBase):
|
|
def test_racing_cell_set(self):
|
|
"""Test cell object gettr/settr properties."""
|
|
|
|
def nested_func():
|
|
x = 0
|
|
|
|
def inner():
|
|
nonlocal x
|
|
x += 1
|
|
|
|
# This doesn't race because LOAD_DEREF and STORE_DEREF on the
|
|
# cell object use critical sections.
|
|
do_race(nested_func, nested_func)
|
|
|
|
def nested_func2():
|
|
x = 0
|
|
|
|
def inner():
|
|
y = x
|
|
frame = sys._getframe(1)
|
|
frame.f_locals["x"] = 2
|
|
|
|
return inner
|
|
|
|
def mutate_func2():
|
|
inner = nested_func2()
|
|
cell = inner.__closure__[0]
|
|
old_value = cell.cell_contents
|
|
cell.cell_contents = 1000
|
|
time.sleep(0)
|
|
cell.cell_contents = old_value
|
|
time.sleep(0)
|
|
|
|
# This revealed a race with cell_set_contents() since it was missing
|
|
# the critical section.
|
|
do_race(nested_func2, mutate_func2)
|
|
|
|
def test_racing_cell_cmp_repr(self):
|
|
"""Test cell object compare and repr methods."""
|
|
|
|
def nested_func():
|
|
x = 0
|
|
y = 0
|
|
|
|
def inner():
|
|
return x + y
|
|
|
|
return inner.__closure__
|
|
|
|
cell_a, cell_b = nested_func()
|
|
|
|
def mutate():
|
|
cell_a.cell_contents += 1
|
|
|
|
def access():
|
|
cell_a == cell_b
|
|
s = repr(cell_a)
|
|
|
|
# cell_richcompare() and cell_repr used to have data races
|
|
do_race(mutate, access)
|
|
|
|
def test_racing_load_super_attr(self):
|
|
"""Test (un)specialization of LOAD_SUPER_ATTR opcode."""
|
|
|
|
class C:
|
|
def __init__(self):
|
|
try:
|
|
super().__init__
|
|
super().__init__()
|
|
except RuntimeError:
|
|
pass # happens if __class__ is replaced with non-type
|
|
|
|
def access():
|
|
C()
|
|
|
|
def mutate():
|
|
# Swap out the super() global with a different one
|
|
real_super = super
|
|
globals()["super"] = lambda s=1: s
|
|
time.sleep(0)
|
|
globals()["super"] = real_super
|
|
time.sleep(0)
|
|
# Swap out the __class__ closure value with a non-type
|
|
cell = C.__init__.__closure__[0]
|
|
real_class = cell.cell_contents
|
|
cell.cell_contents = 99
|
|
time.sleep(0)
|
|
cell.cell_contents = real_class
|
|
|
|
# The initial PR adding specialized opcodes for LOAD_SUPER_ATTR
|
|
# had some races (one with the super() global changing and one
|
|
# with the cell binding being changed).
|
|
do_race(access, mutate)
|
|
|
|
def test_racing_to_bool(self):
|
|
|
|
seq = [1]
|
|
|
|
class C:
|
|
def __bool__(self):
|
|
return False
|
|
|
|
def access():
|
|
if seq:
|
|
return 1
|
|
else:
|
|
return 2
|
|
|
|
def mutate():
|
|
nonlocal seq
|
|
seq = [1]
|
|
time.sleep(0)
|
|
seq = C()
|
|
time.sleep(0)
|
|
|
|
do_race(access, mutate)
|
|
|
|
def test_racing_store_attr_slot(self):
|
|
class C:
|
|
__slots__ = ['x', '__dict__']
|
|
|
|
c = C()
|
|
|
|
def set_slot():
|
|
for i in range(10):
|
|
c.x = i
|
|
time.sleep(0)
|
|
|
|
def change_type():
|
|
def set_x(self, x):
|
|
pass
|
|
|
|
def get_x(self):
|
|
pass
|
|
|
|
C.x = property(get_x, set_x)
|
|
time.sleep(0)
|
|
del C.x
|
|
time.sleep(0)
|
|
|
|
do_race(set_slot, change_type)
|
|
|
|
def set_getattribute():
|
|
C.__getattribute__ = lambda self, x: x
|
|
time.sleep(0)
|
|
del C.__getattribute__
|
|
time.sleep(0)
|
|
|
|
do_race(set_slot, set_getattribute)
|
|
|
|
def test_racing_store_attr_instance_value(self):
|
|
class C:
|
|
pass
|
|
|
|
c = C()
|
|
|
|
def set_value():
|
|
for i in range(100):
|
|
c.x = i
|
|
|
|
set_value()
|
|
|
|
def read():
|
|
x = c.x
|
|
|
|
def mutate():
|
|
# Adding a property for 'x' should unspecialize it.
|
|
C.x = property(lambda self: None, lambda self, x: None)
|
|
time.sleep(0)
|
|
del C.x
|
|
time.sleep(0)
|
|
|
|
do_race(read, mutate)
|
|
|
|
def test_racing_store_attr_with_hint(self):
|
|
class C:
|
|
pass
|
|
|
|
c = C()
|
|
for i in range(29):
|
|
setattr(c, f"_{i}", None)
|
|
|
|
def set_value():
|
|
for i in range(100):
|
|
c.x = i
|
|
|
|
set_value()
|
|
|
|
def read():
|
|
x = c.x
|
|
|
|
def mutate():
|
|
# Adding a property for 'x' should unspecialize it.
|
|
C.x = property(lambda self: None, lambda self, x: None)
|
|
time.sleep(0)
|
|
del C.x
|
|
time.sleep(0)
|
|
|
|
do_race(read, mutate)
|
|
|
|
def make_shared_key_dict(self):
|
|
class C:
|
|
pass
|
|
|
|
a = C()
|
|
a.x = 1
|
|
return a.__dict__
|
|
|
|
def test_racing_store_attr_dict(self):
|
|
"""Test STORE_ATTR with various dictionary types."""
|
|
class C:
|
|
pass
|
|
|
|
c = C()
|
|
|
|
def set_value():
|
|
for i in range(20):
|
|
c.x = i
|
|
|
|
def mutate():
|
|
nonlocal c
|
|
c.x = 1
|
|
self.assertTrue(_testinternalcapi.has_inline_values(c))
|
|
for i in range(30):
|
|
setattr(c, f"_{i}", None)
|
|
self.assertFalse(_testinternalcapi.has_inline_values(c.__dict__))
|
|
c.__dict__ = self.make_shared_key_dict()
|
|
self.assertTrue(_testinternalcapi.has_split_table(c.__dict__))
|
|
c.__dict__[1] = None
|
|
self.assertFalse(_testinternalcapi.has_split_table(c.__dict__))
|
|
c = C()
|
|
|
|
do_race(set_value, mutate)
|
|
|
|
def test_racing_recursion_limit(self):
|
|
def something_recursive():
|
|
def count(n):
|
|
if n > 0:
|
|
return count(n - 1) + 1
|
|
return 0
|
|
|
|
count(50)
|
|
|
|
def set_recursion_limit():
|
|
for limit in range(100, 200):
|
|
sys.setrecursionlimit(limit)
|
|
|
|
do_race(something_recursive, set_recursion_limit)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|