From 825efeed468afc6b6be3bc20abea7955847e36a8 Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Sat, 15 Jun 2024 16:57:58 +0800 Subject: [PATCH 1/5] add test --- Lib/test/test_free_threading/test_frame.py | 31 ++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 Lib/test/test_free_threading/test_frame.py 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..34a6e8e4b6d27c --- /dev/null +++ b/Lib/test/test_free_threading/test_frame.py @@ -0,0 +1,31 @@ +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 gen(): + for _ in range(10000): + return sys._getframe() + + foo = gen() + def work(): + for _ in range(4000): + frame1 = foo + frame1.clear() + + + threads = [] + for i in range(NTHREADS): + thread = threading.Thread(target=work) + thread.start() + threads.append(thread) + + for thread in threads: + thread.join() \ No newline at end of file From c4966e26e620fcd4b5ee3e5df17d7c71671ad072 Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Sat, 15 Jun 2024 16:59:25 +0800 Subject: [PATCH 2/5] Make frame clearing thread safe --- Objects/frameobject.c | 15 +++++++++++++-- Objects/genobject.c | 12 +++++++++++- 2 files changed, 24 insertions(+), 3 deletions(-) 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"); diff --git a/Objects/genobject.c b/Objects/genobject.c index 92cd8c61e7e9ca..2a59e9451d4e53 100644 --- a/Objects/genobject.c +++ b/Objects/genobject.c @@ -14,6 +14,7 @@ #include "pycore_pyatomic_ft_wrappers.h" // FT_ATOMIC_* #include "pycore_pyerrors.h" // _PyErr_ClearExcState() #include "pycore_pystate.h" // _PyThreadState_GET() +#include "pycore_critical_section.h" // Py_BEGIN_CRITICAL_SECTION #include "pystats.h" @@ -119,7 +120,7 @@ _PyGen_Finalize(PyObject *self) } static void -gen_dealloc(PyGenObject *gen) +gen_dealloc_no_lock(PyGenObject *gen) { PyObject *self = (PyObject *) gen; @@ -158,6 +159,15 @@ gen_dealloc(PyGenObject *gen) PyObject_GC_Del(gen); } +static void +gen_dealloc(PyGenObject *gen) +{ + // Another generator's finalizer might race on clearing the frame. + Py_BEGIN_CRITICAL_SECTION(gen); + gen_dealloc_no_lock(gen); + Py_END_CRITICAL_SECTION(); +} + static PySendResult gen_send_ex2(PyGenObject *gen, PyObject *arg, PyObject **presult, int exc, int closing) From fc7510b1417f09e335799df52fb67cb7f4327ca3 Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Sat, 15 Jun 2024 17:03:29 +0800 Subject: [PATCH 3/5] Fix EOF --- Lib/test/test_free_threading/test_frame.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_free_threading/test_frame.py b/Lib/test/test_free_threading/test_frame.py index 34a6e8e4b6d27c..d7d1a13955eb70 100644 --- a/Lib/test/test_free_threading/test_frame.py +++ b/Lib/test/test_free_threading/test_frame.py @@ -28,4 +28,4 @@ def work(): threads.append(thread) for thread in threads: - thread.join() \ No newline at end of file + thread.join() From 21da242b5812929e259f0910f2f5a53ff43ce0cd Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Wed, 19 Jun 2024 12:51:21 +0800 Subject: [PATCH 4/5] Cleanup test --- Lib/test/test_free_threading/test_frame.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_free_threading/test_frame.py b/Lib/test/test_free_threading/test_frame.py index d7d1a13955eb70..7ddd8c6b253a62 100644 --- a/Lib/test/test_free_threading/test_frame.py +++ b/Lib/test/test_free_threading/test_frame.py @@ -10,15 +10,13 @@ class TestFrame(unittest.TestCase): def test_frame_clear_simultaneous(self): - def gen(): - for _ in range(10000): - return sys._getframe() + def getframe(): + return sys._getframe() - foo = gen() + foo = getframe() def work(): for _ in range(4000): - frame1 = foo - frame1.clear() + foo.clear() threads = [] From acc46444a5a949e004bc956fbb1d3ba4ee748970 Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Fri, 21 Jun 2024 18:40:02 +0800 Subject: [PATCH 5/5] Revert generator fixes --- Objects/genobject.c | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/Objects/genobject.c b/Objects/genobject.c index 2a59e9451d4e53..92cd8c61e7e9ca 100644 --- a/Objects/genobject.c +++ b/Objects/genobject.c @@ -14,7 +14,6 @@ #include "pycore_pyatomic_ft_wrappers.h" // FT_ATOMIC_* #include "pycore_pyerrors.h" // _PyErr_ClearExcState() #include "pycore_pystate.h" // _PyThreadState_GET() -#include "pycore_critical_section.h" // Py_BEGIN_CRITICAL_SECTION #include "pystats.h" @@ -120,7 +119,7 @@ _PyGen_Finalize(PyObject *self) } static void -gen_dealloc_no_lock(PyGenObject *gen) +gen_dealloc(PyGenObject *gen) { PyObject *self = (PyObject *) gen; @@ -159,15 +158,6 @@ gen_dealloc_no_lock(PyGenObject *gen) PyObject_GC_Del(gen); } -static void -gen_dealloc(PyGenObject *gen) -{ - // Another generator's finalizer might race on clearing the frame. - Py_BEGIN_CRITICAL_SECTION(gen); - gen_dealloc_no_lock(gen); - Py_END_CRITICAL_SECTION(); -} - static PySendResult gen_send_ex2(PyGenObject *gen, PyObject *arg, PyObject **presult, int exc, int closing)