From ab32409e7b41f6121943e8526f8adddd4417e058 Mon Sep 17 00:00:00 2001 From: "Yves.Duprat" Date: Tue, 20 May 2025 16:49:54 +0200 Subject: [PATCH 1/7] Initial commit --- Lib/test/lock_tests.py | 13 +++++++++++++ Lib/threading.py | 2 +- Modules/_threadmodule.c | 8 ++++++-- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/Lib/test/lock_tests.py b/Lib/test/lock_tests.py index d64b2b9fe28694..6da98ed2fdc320 100644 --- a/Lib/test/lock_tests.py +++ b/Lib/test/lock_tests.py @@ -370,6 +370,19 @@ def test_locked(self): lock.release() self.assertFalse(lock.locked()) + def test_locked_2threads(self): + l = [] + rlock = self.locktype() + def acquire(): + rlock.acquire() + l.append(rlock.locked()) + + with Bunch(acquire, 1): + pass + self.assertTrue(rlock.locked()) + self.assertTrue(l[0]) + del rlock + def test_release_save_unacquired(self): # Cannot _release_save an unacquired lock lock = self.locktype() diff --git a/Lib/threading.py b/Lib/threading.py index 9feada3b8bb15b..e64f014fd7d51e 100644 --- a/Lib/threading.py +++ b/Lib/threading.py @@ -237,7 +237,7 @@ def __exit__(self, t, v, tb): def locked(self): """Return whether this object is locked.""" - return self._count > 0 + return self._block.locked() # Internal methods used by condition variables diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 77286ed2a74669..ffb5a383ec5618 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -1110,8 +1110,12 @@ Release the lock."); static PyObject * rlock_locked(PyObject *op, PyObject *Py_UNUSED(ignored)) { - rlockobject *self = rlockobject_CAST(op); - int is_locked = _PyRecursiveMutex_IsLockedByCurrentThread(&self->lock); + /* + see gh-134323: the `_PyRecursiveMutex_IsLocked` function does not exist, so we cast the `op` + to `lockobject` in order to call `PyMutex_IsLocked`. + */ + lockobject *self = lockobject_CAST(op); + int is_locked = PyMutex_IsLocked(&self->lock); return PyBool_FromLong(is_locked); } From 8ae2fc201ce0cec09a969a446aa3a552c9c823ff Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Tue, 20 May 2025 19:16:37 +0000 Subject: [PATCH 2/7] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2025-05-20-19-16-30.gh-issue-134323.ZQZGvw.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2025-05-20-19-16-30.gh-issue-134323.ZQZGvw.rst diff --git a/Misc/NEWS.d/next/Library/2025-05-20-19-16-30.gh-issue-134323.ZQZGvw.rst b/Misc/NEWS.d/next/Library/2025-05-20-19-16-30.gh-issue-134323.ZQZGvw.rst new file mode 100644 index 00000000000000..7110a076bd2ecb --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-05-20-19-16-30.gh-issue-134323.ZQZGvw.rst @@ -0,0 +1,2 @@ +Fix the public :meth:`locked` and its implemntation in the ``_thread``module. +Also adding a unit test in ``lock_test.py`` file. From 9b26b7be814bf33d7dc8055514c44705056efae1 Mon Sep 17 00:00:00 2001 From: Duprat Date: Tue, 20 May 2025 21:17:40 +0200 Subject: [PATCH 3/7] Fix nit --- .../Library/2025-05-20-19-16-30.gh-issue-134323.ZQZGvw.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2025-05-20-19-16-30.gh-issue-134323.ZQZGvw.rst b/Misc/NEWS.d/next/Library/2025-05-20-19-16-30.gh-issue-134323.ZQZGvw.rst index 7110a076bd2ecb..e8e1a91bcd4045 100644 --- a/Misc/NEWS.d/next/Library/2025-05-20-19-16-30.gh-issue-134323.ZQZGvw.rst +++ b/Misc/NEWS.d/next/Library/2025-05-20-19-16-30.gh-issue-134323.ZQZGvw.rst @@ -1,2 +1,2 @@ -Fix the public :meth:`locked` and its implemntation in the ``_thread``module. -Also adding a unit test in ``lock_test.py`` file. +Fix the public :meth:`locked` and its implementation in the ``_thread``module. +Also adding a unit test in ``lock_test.py`` file. From b448d4b15c63cb917dd8406499259778f4272aa1 Mon Sep 17 00:00:00 2001 From: Duprat Date: Tue, 20 May 2025 21:19:19 +0200 Subject: [PATCH 4/7] add a space --- .../next/Library/2025-05-20-19-16-30.gh-issue-134323.ZQZGvw.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2025-05-20-19-16-30.gh-issue-134323.ZQZGvw.rst b/Misc/NEWS.d/next/Library/2025-05-20-19-16-30.gh-issue-134323.ZQZGvw.rst index e8e1a91bcd4045..de4fc70c730c80 100644 --- a/Misc/NEWS.d/next/Library/2025-05-20-19-16-30.gh-issue-134323.ZQZGvw.rst +++ b/Misc/NEWS.d/next/Library/2025-05-20-19-16-30.gh-issue-134323.ZQZGvw.rst @@ -1,2 +1,2 @@ -Fix the public :meth:`locked` and its implementation in the ``_thread``module. +Fix the public :meth:`locked` and its implementation in the ``_thread`` module. Also adding a unit test in ``lock_test.py`` file. From 26d4bf485cff2f9a7d8ffa901656dcc48cd93418 Mon Sep 17 00:00:00 2001 From: "Yves.Duprat" Date: Wed, 21 May 2025 17:09:18 +0200 Subject: [PATCH 5/7] Apply suggestions from code review --- Lib/test/lock_tests.py | 8 +++++++- .../2025-05-20-19-16-30.gh-issue-134323.ZQZGvw.rst | 3 +-- Modules/_threadmodule.c | 11 +++++++++-- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/Lib/test/lock_tests.py b/Lib/test/lock_tests.py index 6da98ed2fdc320..ea211b8eb61276 100644 --- a/Lib/test/lock_tests.py +++ b/Lib/test/lock_tests.py @@ -371,16 +371,22 @@ def test_locked(self): self.assertFalse(lock.locked()) def test_locked_2threads(self): + # see gh-134323: check that a rlock which + # is acquired in a secaondary thread, + # is still locked in the main thread. l = [] rlock = self.locktype() + self.assertFalse(rlock.locked()) def acquire(): + l.append(rlock.locked()) rlock.acquire() l.append(rlock.locked()) with Bunch(acquire, 1): pass self.assertTrue(rlock.locked()) - self.assertTrue(l[0]) + self.assertFalse(l[0]) + self.assertTrue(l[1]) del rlock def test_release_save_unacquired(self): diff --git a/Misc/NEWS.d/next/Library/2025-05-20-19-16-30.gh-issue-134323.ZQZGvw.rst b/Misc/NEWS.d/next/Library/2025-05-20-19-16-30.gh-issue-134323.ZQZGvw.rst index de4fc70c730c80..7982b52f77a172 100644 --- a/Misc/NEWS.d/next/Library/2025-05-20-19-16-30.gh-issue-134323.ZQZGvw.rst +++ b/Misc/NEWS.d/next/Library/2025-05-20-19-16-30.gh-issue-134323.ZQZGvw.rst @@ -1,2 +1 @@ -Fix the public :meth:`locked` and its implementation in the ``_thread`` module. -Also adding a unit test in ``lock_test.py`` file. +Fix the :meth:`threading.RLock.locked` method. diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index ffb5a383ec5618..83026f36c1c8c1 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -1022,6 +1022,13 @@ rlock_traverse(PyObject *self, visitproc visit, void *arg) return 0; } +/* +helper function used by rlock_locked and rlock_repr. +*/ +static int +rlock_locked_impl(rlockobject *self) { + return PyMutex_IsLocked(&self->lock.mutex); +} static void rlock_dealloc(PyObject *self) @@ -1114,8 +1121,8 @@ rlock_locked(PyObject *op, PyObject *Py_UNUSED(ignored)) see gh-134323: the `_PyRecursiveMutex_IsLocked` function does not exist, so we cast the `op` to `lockobject` in order to call `PyMutex_IsLocked`. */ - lockobject *self = lockobject_CAST(op); - int is_locked = PyMutex_IsLocked(&self->lock); + rlockobject *self = rlockobject_CAST(op); + int is_locked = rlock_locked_impl(self); return PyBool_FromLong(is_locked); } From a0082a3fad1f22d721a009d4a32025fea9776c0f Mon Sep 17 00:00:00 2001 From: Duprat Date: Wed, 21 May 2025 18:30:20 +0200 Subject: [PATCH 6/7] Fix nit Co-authored-by: Kumar Aditya --- Lib/test/lock_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/lock_tests.py b/Lib/test/lock_tests.py index ea211b8eb61276..6db85f3d91939d 100644 --- a/Lib/test/lock_tests.py +++ b/Lib/test/lock_tests.py @@ -372,7 +372,7 @@ def test_locked(self): def test_locked_2threads(self): # see gh-134323: check that a rlock which - # is acquired in a secaondary thread, + # is acquired in a different thread, # is still locked in the main thread. l = [] rlock = self.locktype() From 9e1bf5f768ab27a3831dc1d1ff2f4cf3662980d0 Mon Sep 17 00:00:00 2001 From: "Yves.Duprat" Date: Thu, 22 May 2025 15:21:48 +0200 Subject: [PATCH 7/7] Apply last suggestions --- Lib/test/lock_tests.py | 13 ++++++------- Lib/threading.py | 2 +- Modules/_threadmodule.c | 13 ++++--------- 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/Lib/test/lock_tests.py b/Lib/test/lock_tests.py index 6db85f3d91939d..850450c1e81a16 100644 --- a/Lib/test/lock_tests.py +++ b/Lib/test/lock_tests.py @@ -370,24 +370,23 @@ def test_locked(self): lock.release() self.assertFalse(lock.locked()) - def test_locked_2threads(self): + def test_locked_with_2threads(self): # see gh-134323: check that a rlock which # is acquired in a different thread, # is still locked in the main thread. - l = [] + result = [] rlock = self.locktype() self.assertFalse(rlock.locked()) def acquire(): - l.append(rlock.locked()) + result.append(rlock.locked()) rlock.acquire() - l.append(rlock.locked()) + result.append(rlock.locked()) with Bunch(acquire, 1): pass self.assertTrue(rlock.locked()) - self.assertFalse(l[0]) - self.assertTrue(l[1]) - del rlock + self.assertFalse(result[0]) + self.assertTrue(result[1]) def test_release_save_unacquired(self): # Cannot _release_save an unacquired lock diff --git a/Lib/threading.py b/Lib/threading.py index 70d37fe5f77129..b6c451d1fbaabd 100644 --- a/Lib/threading.py +++ b/Lib/threading.py @@ -158,7 +158,7 @@ def __repr__(self): except KeyError: pass return "<%s %s.%s object owner=%r count=%d at %s>" % ( - "locked" if self._block.locked() else "unlocked", + "locked" if self.locked() else "unlocked", self.__class__.__module__, self.__class__.__qualname__, owner, diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 83026f36c1c8c1..10123700f90f32 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -1022,11 +1022,9 @@ rlock_traverse(PyObject *self, visitproc visit, void *arg) return 0; } -/* -helper function used by rlock_locked and rlock_repr. -*/ static int -rlock_locked_impl(rlockobject *self) { +rlock_locked_impl(rlockobject *self) +{ return PyMutex_IsLocked(&self->lock.mutex); } @@ -1117,10 +1115,6 @@ Release the lock."); static PyObject * rlock_locked(PyObject *op, PyObject *Py_UNUSED(ignored)) { - /* - see gh-134323: the `_PyRecursiveMutex_IsLocked` function does not exist, so we cast the `op` - to `lockobject` in order to call `PyMutex_IsLocked`. - */ rlockobject *self = rlockobject_CAST(op); int is_locked = rlock_locked_impl(self); return PyBool_FromLong(is_locked); @@ -1230,10 +1224,11 @@ rlock_repr(PyObject *op) { rlockobject *self = rlockobject_CAST(op); PyThread_ident_t owner = self->lock.thread; + int locked = rlock_locked_impl(self); size_t count = self->lock.level + 1; return PyUnicode_FromFormat( "<%s %s object owner=%" PY_FORMAT_THREAD_IDENT_T " count=%zu at %p>", - owner ? "locked" : "unlocked", + locked ? "locked" : "unlocked", Py_TYPE(self)->tp_name, owner, count, self); }