mirror of
https://github.com/python/cpython.git
synced 2026-02-22 15:10:47 +00:00
Fix thread-safety issues when accessing frame attributes while another thread is executing the frame: - Add critical section to frame_repr() to prevent races when accessing the frame's code object and line number - Add _Py_NO_SANITIZE_THREAD to PyUnstable_InterpreterFrame_GetLasti() to allow intentional racy reads of instr_ptr. - Fix take_ownership() to not write to the original frame's f_executable
151 lines
4.2 KiB
Python
151 lines
4.2 KiB
Python
import functools
|
|
import sys
|
|
import threading
|
|
import unittest
|
|
|
|
from test.support import threading_helper
|
|
|
|
threading_helper.requires_working_threading(module=True)
|
|
|
|
|
|
def run_with_frame(funcs, runner=None, iters=10):
|
|
"""Run funcs with a frame from another thread that is currently executing.
|
|
|
|
Args:
|
|
funcs: A function or list of functions that take a frame argument
|
|
runner: Optional function to run in the executor thread. If provided,
|
|
it will be called and should return eventually. The frame
|
|
passed to funcs will be the runner's frame.
|
|
iters: Number of iterations each func should run
|
|
"""
|
|
if not isinstance(funcs, list):
|
|
funcs = [funcs]
|
|
|
|
frame_var = None
|
|
e = threading.Event()
|
|
b = threading.Barrier(len(funcs) + 1)
|
|
|
|
if runner is None:
|
|
def runner():
|
|
j = 0
|
|
for i in range(100):
|
|
j += i
|
|
|
|
def executor():
|
|
nonlocal frame_var
|
|
frame_var = sys._getframe()
|
|
e.set()
|
|
b.wait()
|
|
runner()
|
|
|
|
def func_wrapper(func):
|
|
e.wait()
|
|
frame = frame_var
|
|
b.wait()
|
|
for _ in range(iters):
|
|
func(frame)
|
|
|
|
test_funcs = [functools.partial(func_wrapper, f) for f in funcs]
|
|
threading_helper.run_concurrently([executor] + test_funcs)
|
|
|
|
|
|
class TestFrameRaces(unittest.TestCase):
|
|
def test_concurrent_f_lasti(self):
|
|
run_with_frame(lambda frame: frame.f_lasti)
|
|
|
|
def test_concurrent_f_lineno(self):
|
|
run_with_frame(lambda frame: frame.f_lineno)
|
|
|
|
def test_concurrent_f_code(self):
|
|
run_with_frame(lambda frame: frame.f_code)
|
|
|
|
def test_concurrent_f_back(self):
|
|
run_with_frame(lambda frame: frame.f_back)
|
|
|
|
def test_concurrent_f_globals(self):
|
|
run_with_frame(lambda frame: frame.f_globals)
|
|
|
|
def test_concurrent_f_builtins(self):
|
|
run_with_frame(lambda frame: frame.f_builtins)
|
|
|
|
def test_concurrent_f_locals(self):
|
|
run_with_frame(lambda frame: frame.f_locals)
|
|
|
|
def test_concurrent_f_trace_read(self):
|
|
run_with_frame(lambda frame: frame.f_trace)
|
|
|
|
def test_concurrent_f_trace_opcodes_read(self):
|
|
run_with_frame(lambda frame: frame.f_trace_opcodes)
|
|
|
|
def test_concurrent_repr(self):
|
|
run_with_frame(lambda frame: repr(frame))
|
|
|
|
def test_concurrent_f_trace_write(self):
|
|
def trace_func(frame, event, arg):
|
|
return trace_func
|
|
|
|
def writer(frame):
|
|
frame.f_trace = trace_func
|
|
frame.f_trace = None
|
|
|
|
run_with_frame(writer)
|
|
|
|
def test_concurrent_f_trace_read_write(self):
|
|
# Test concurrent reads and writes of f_trace on a live frame.
|
|
def trace_func(frame, event, arg):
|
|
return trace_func
|
|
|
|
def reader(frame):
|
|
_ = frame.f_trace
|
|
|
|
def writer(frame):
|
|
frame.f_trace = trace_func
|
|
frame.f_trace = None
|
|
|
|
run_with_frame([reader, writer, reader, writer])
|
|
|
|
def test_concurrent_f_trace_opcodes_write(self):
|
|
def writer(frame):
|
|
frame.f_trace_opcodes = True
|
|
frame.f_trace_opcodes = False
|
|
|
|
run_with_frame(writer)
|
|
|
|
def test_concurrent_f_trace_opcodes_read_write(self):
|
|
# Test concurrent reads and writes of f_trace_opcodes on a live frame.
|
|
def reader(frame):
|
|
_ = frame.f_trace_opcodes
|
|
|
|
def writer(frame):
|
|
frame.f_trace_opcodes = True
|
|
frame.f_trace_opcodes = False
|
|
|
|
run_with_frame([reader, writer, reader, writer])
|
|
|
|
def test_concurrent_frame_clear(self):
|
|
# Test race between frame.clear() and attribute reads.
|
|
def create_frame():
|
|
x = 1
|
|
y = 2
|
|
return sys._getframe()
|
|
|
|
frame = create_frame()
|
|
|
|
def reader():
|
|
for _ in range(10):
|
|
try:
|
|
_ = frame.f_locals
|
|
_ = frame.f_code
|
|
_ = frame.f_lineno
|
|
except ValueError:
|
|
# Frame may be cleared
|
|
pass
|
|
|
|
def clearer():
|
|
frame.clear()
|
|
|
|
threading_helper.run_concurrently([reader, reader, clearer])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|