Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

gh-144446: Fix some frame object thread-safety issues#144479

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
colesbury wants to merge2 commits intopython:main
base:main
Choose a base branch
Loading
fromcolesbury:gh-144446-frame-free-threading
Open
Show file tree
Hide file tree
Changes fromall 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
153 changes: 153 additions & 0 deletionsLib/test/test_free_threading/test_frame.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
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):
"""Test writing to f_trace of a live frame."""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Please use comments instead of docstrings because unittest renders docs string poorly when test fails.

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):
"""Test writing to f_trace_opcodes of a live frame."""
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()
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
Fix data races in the free-threaded build when reading frame object attributes
while another thread is executing the frame.
10 changes: 7 additions & 3 deletionsObjects/frameobject.c
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -1049,11 +1049,11 @@ static PyObject *
frame_lasti_get_impl(PyFrameObject *self)
/*[clinic end generated code: output=03275b4f0327d1a2 input=0225ed49cb1fbeeb]*/
{
int lasti =_PyInterpreterFrame_LASTI(self->f_frame);
int lasti =PyUnstable_InterpreterFrame_GetLasti(self->f_frame);
if (lasti < 0) {
return PyLong_FromLong(-1);
}
return PyLong_FromLong(lasti * sizeof(_Py_CODEUNIT));
return PyLong_FromLong(lasti);
}

/*[clinic input]
Expand DownExpand Up@@ -2053,11 +2053,15 @@ static PyObject *
frame_repr(PyObject *op)
{
PyFrameObject *f = PyFrameObject_CAST(op);
PyObject *result;
Py_BEGIN_CRITICAL_SECTION(f);
int lineno = PyFrame_GetLineNumber(f);
PyCodeObject *code = _PyFrame_GetCode(f->f_frame);
return PyUnicode_FromFormat(
result = PyUnicode_FromFormat(
"<frame at %p, file %R, line %d, code %S>",
f, code->co_filename, lineno, code->co_name);
Py_END_CRITICAL_SECTION();
return result;
}

static PyMethodDef frame_methods[] = {
Expand Down
8 changes: 4 additions & 4 deletionsPython/frame.c
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -54,7 +54,7 @@ take_ownership(PyFrameObject *f, _PyInterpreterFrame *frame)
_PyFrame_Copy(frame, new_frame);
// _PyFrame_Copy takes the reference to the executable,
// so we need to restore it.
frame->f_executable = PyStackRef_DUP(new_frame->f_executable);
new_frame->f_executable = PyStackRef_DUP(new_frame->f_executable);
f->f_frame = new_frame;
new_frame->owner = FRAME_OWNED_BY_FRAME_OBJECT;
if (_PyFrame_IsIncomplete(new_frame)) {
Expand DownExpand Up@@ -135,14 +135,14 @@ PyUnstable_InterpreterFrame_GetCode(struct _PyInterpreterFrame *frame)
return PyStackRef_AsPyObjectNew(frame->f_executable);
}

int
// NOTE: We allow racy accesses to the instruction pointer from other threads
// for sys._current_frames() and similar APIs.
int _Py_NO_SANITIZE_THREAD
PyUnstable_InterpreterFrame_GetLasti(struct _PyInterpreterFrame *frame)
{
return _PyInterpreterFrame_LASTI(frame) * sizeof(_Py_CODEUNIT);
}

// NOTE: We allow racy accesses to the instruction pointer from other threads
// for sys._current_frames() and similar APIs.
int _Py_NO_SANITIZE_THREAD
PyUnstable_InterpreterFrame_GetLine(_PyInterpreterFrame *frame)
{
Expand Down
Loading

[8]ページ先頭

©2009-2026 Movatter.jp