diff --git a/Lib/test/test_free_threading/test_frame.py b/Lib/test/test_free_threading/test_frame.py new file mode 100644 index 00000000000000..7ddd8c6b253a62 --- /dev/null +++ b/Lib/test/test_free_threading/test_frame.py @@ -0,0 +1,29 @@ +import sys +import unittest +import threading + +from test.support import threading_helper + +NTHREADS = 6 + +@threading_helper.requires_working_threading() +class TestFrame(unittest.TestCase): + def test_frame_clear_simultaneous(self): + + def getframe(): + return sys._getframe() + + foo = getframe() + def work(): + for _ in range(4000): + foo.clear() + + + threads = [] + for i in range(NTHREADS): + thread = threading.Thread(target=work) + thread.start() + threads.append(thread) + + for thread in threads: + thread.join() diff --git a/Objects/frameobject.c b/Objects/frameobject.c index 5c65007dae46d2..74d2f5d2411661 100644 --- a/Objects/frameobject.c +++ b/Objects/frameobject.c @@ -7,7 +7,7 @@ #include "pycore_moduleobject.h" // _PyModule_GetDict() #include "pycore_object.h" // _PyObject_GC_UNTRACK() #include "pycore_opcode_metadata.h" // _PyOpcode_Deopt, _PyOpcode_Caches - +#include "pycore_critical_section.h" // Py_BEGIN_CRITICAL_SECTION #include "frameobject.h" // PyFrameObject #include "pycore_frame.h" @@ -1662,7 +1662,7 @@ frame_tp_clear(PyFrameObject *f) } static PyObject * -frame_clear(PyFrameObject *f, PyObject *Py_UNUSED(ignored)) +frame_clear_unlocked(PyFrameObject *f, PyObject *Py_UNUSED(ignored)) { if (f->f_frame->owner == FRAME_OWNED_BY_GENERATOR) { PyGenObject *gen = _PyFrame_GetGenerator(f->f_frame); @@ -1692,6 +1692,17 @@ frame_clear(PyFrameObject *f, PyObject *Py_UNUSED(ignored)) return NULL; } +static PyObject * +frame_clear(PyFrameObject *f, PyObject *Py_UNUSED(ignored)) +{ + PyObject *res; + // Another materialized frame might be racing on clearing the frame. + Py_BEGIN_CRITICAL_SECTION(f); + res = frame_clear_unlocked(f, NULL); + Py_END_CRITICAL_SECTION(); + return res; +} + PyDoc_STRVAR(clear__doc__, "F.clear(): clear most references held by the frame");