summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsobolevn <mail@sobolevn.me>2025-04-08 08:14:12 (GMT)
committerGitHub <noreply@github.com>2025-04-08 08:14:12 (GMT)
commitf7305a06c7a322d23b39ad9d16af814d467624c6 (patch)
tree8a25dede6c059e01ae98c63d737ac0d0c1987b58
parent6cd1d6c6b142697fb72f422b7b448c27ebc30534 (diff)
downloadcpython-f7305a06c7a322d23b39ad9d16af814d467624c6.zip
cpython-f7305a06c7a322d23b39ad9d16af814d467624c6.tar.gz
cpython-f7305a06c7a322d23b39ad9d16af814d467624c6.tar.bz2
gh-115942: Add `locked` to several multiprocessing locks (#115944)
Co-authored-by: mpage <mpage@cs.stanford.edu> Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
-rw-r--r--Doc/library/multiprocessing.rst14
-rw-r--r--Doc/library/threading.rst13
-rw-r--r--Lib/importlib/_bootstrap.py3
-rw-r--r--Lib/multiprocessing/managers.py6
-rw-r--r--Lib/multiprocessing/synchronize.py3
-rw-r--r--Lib/test/_test_multiprocessing.py17
-rw-r--r--Lib/test/lock_tests.py12
-rw-r--r--Lib/threading.py7
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-01-11-16-22.gh-issue-115942.4W3hNx.rst5
-rw-r--r--Modules/_threadmodule.c15
10 files changed, 89 insertions, 6 deletions
diff --git a/Doc/library/multiprocessing.rst b/Doc/library/multiprocessing.rst
index 9f98703..9603698 100644
--- a/Doc/library/multiprocessing.rst
+++ b/Doc/library/multiprocessing.rst
@@ -1421,6 +1421,13 @@ object -- see :ref:`multiprocessing-managers`.
when invoked on an unlocked lock, a :exc:`ValueError` is raised.
+ .. method:: locked()
+
+ Return a boolean indicating whether this object is locked right now.
+
+ .. versionadded:: next
+
+
.. class:: RLock()
A recursive lock object: a close analog of :class:`threading.RLock`. A
@@ -1481,6 +1488,13 @@ object -- see :ref:`multiprocessing-managers`.
differs from the implemented behavior in :meth:`threading.RLock.release`.
+ .. method:: locked()
+
+ Return a boolean indicating whether this object is locked right now.
+
+ .. versionadded:: next
+
+
.. class:: Semaphore([value])
A semaphore object: a close analog of :class:`threading.Semaphore`.
diff --git a/Doc/library/threading.rst b/Doc/library/threading.rst
index 00511df..d205e17 100644
--- a/Doc/library/threading.rst
+++ b/Doc/library/threading.rst
@@ -709,6 +709,13 @@ call release as many times the lock has been acquired can lead to deadlock.
There is no return value.
+ .. method:: locked()
+
+ Return a boolean indicating whether this object is locked right now.
+
+ .. versionadded:: next
+
+
.. _condition-objects:
Condition Objects
@@ -801,6 +808,12 @@ item to the buffer only needs to wake up one consumer thread.
Release the underlying lock. This method calls the corresponding method on
the underlying lock; there is no return value.
+ .. method:: locked()
+
+ Return a boolean indicating whether this object is locked right now.
+
+ .. versionadded:: next
+
.. method:: wait(timeout=None)
Wait until notified or until a timeout occurs. If the calling thread has
diff --git a/Lib/importlib/_bootstrap.py b/Lib/importlib/_bootstrap.py
index f563526..499da1e 100644
--- a/Lib/importlib/_bootstrap.py
+++ b/Lib/importlib/_bootstrap.py
@@ -382,6 +382,9 @@ class _ModuleLock:
self.waiters.pop()
self.wakeup.release()
+ def locked(self):
+ return bool(self.count)
+
def __repr__(self):
return f'_ModuleLock({self.name!r}) at {id(self)}'
diff --git a/Lib/multiprocessing/managers.py b/Lib/multiprocessing/managers.py
index c1f09d2..91bcf24 100644
--- a/Lib/multiprocessing/managers.py
+++ b/Lib/multiprocessing/managers.py
@@ -1059,12 +1059,14 @@ class IteratorProxy(BaseProxy):
class AcquirerProxy(BaseProxy):
- _exposed_ = ('acquire', 'release')
+ _exposed_ = ('acquire', 'release', 'locked')
def acquire(self, blocking=True, timeout=None):
args = (blocking,) if timeout is None else (blocking, timeout)
return self._callmethod('acquire', args)
def release(self):
return self._callmethod('release')
+ def locked(self):
+ return self._callmethod('locked')
def __enter__(self):
return self._callmethod('acquire')
def __exit__(self, exc_type, exc_val, exc_tb):
@@ -1072,7 +1074,7 @@ class AcquirerProxy(BaseProxy):
class ConditionProxy(AcquirerProxy):
- _exposed_ = ('acquire', 'release', 'wait', 'notify', 'notify_all')
+ _exposed_ = ('acquire', 'release', 'locked', 'wait', 'notify', 'notify_all')
def wait(self, timeout=None):
return self._callmethod('wait', (timeout,))
def notify(self, n=1):
diff --git a/Lib/multiprocessing/synchronize.py b/Lib/multiprocessing/synchronize.py
index edd6c25..771f1db 100644
--- a/Lib/multiprocessing/synchronize.py
+++ b/Lib/multiprocessing/synchronize.py
@@ -90,6 +90,9 @@ class SemLock(object):
self.acquire = self._semlock.acquire
self.release = self._semlock.release
+ def locked(self):
+ return self._semlock._count() != 0
+
def __enter__(self):
return self._semlock.__enter__()
diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py
index dcce576..1cd5704 100644
--- a/Lib/test/_test_multiprocessing.py
+++ b/Lib/test/_test_multiprocessing.py
@@ -1486,8 +1486,10 @@ class _TestLock(BaseTestCase):
def test_lock(self):
lock = self.Lock()
self.assertEqual(lock.acquire(), True)
+ self.assertTrue(lock.locked())
self.assertEqual(lock.acquire(False), False)
self.assertEqual(lock.release(), None)
+ self.assertFalse(lock.locked())
self.assertRaises((ValueError, threading.ThreadError), lock.release)
@staticmethod
@@ -1549,16 +1551,23 @@ class _TestLock(BaseTestCase):
def test_rlock(self):
lock = self.RLock()
self.assertEqual(lock.acquire(), True)
+ self.assertTrue(lock.locked())
self.assertEqual(lock.acquire(), True)
self.assertEqual(lock.acquire(), True)
self.assertEqual(lock.release(), None)
+ self.assertTrue(lock.locked())
self.assertEqual(lock.release(), None)
self.assertEqual(lock.release(), None)
+ self.assertFalse(lock.locked())
self.assertRaises((AssertionError, RuntimeError), lock.release)
def test_lock_context(self):
- with self.Lock():
- pass
+ with self.Lock() as locked:
+ self.assertTrue(locked)
+
+ def test_rlock_context(self):
+ with self.RLock() as locked:
+ self.assertTrue(locked)
class _TestSemaphore(BaseTestCase):
@@ -6254,6 +6263,7 @@ class TestSyncManagerTypes(unittest.TestCase):
@classmethod
def _test_lock(cls, obj):
obj.acquire()
+ obj.locked()
def test_lock(self, lname="Lock"):
o = getattr(self.manager, lname)()
@@ -6265,8 +6275,9 @@ class TestSyncManagerTypes(unittest.TestCase):
def _test_rlock(cls, obj):
obj.acquire()
obj.release()
+ obj.locked()
- def test_rlock(self, lname="Lock"):
+ def test_rlock(self, lname="RLock"):
o = getattr(self.manager, lname)()
self.run_worker(self._test_rlock, o)
diff --git a/Lib/test/lock_tests.py b/Lib/test/lock_tests.py
index 8c8f890..009e04e 100644
--- a/Lib/test/lock_tests.py
+++ b/Lib/test/lock_tests.py
@@ -353,6 +353,18 @@ class RLockTests(BaseLockTests):
lock.release()
self.assertRaises(RuntimeError, lock.release)
+ def test_locked(self):
+ lock = self.locktype()
+ self.assertFalse(lock.locked())
+ lock.acquire()
+ self.assertTrue(lock.locked())
+ lock.acquire()
+ self.assertTrue(lock.locked())
+ lock.release()
+ self.assertTrue(lock.locked())
+ lock.release()
+ self.assertFalse(lock.locked())
+
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 da9cdf0..0dc1d32 100644
--- a/Lib/threading.py
+++ b/Lib/threading.py
@@ -241,6 +241,10 @@ class _RLock:
def __exit__(self, t, v, tb):
self.release()
+ def locked(self):
+ """Return whether this object is locked."""
+ return self._count > 0
+
# Internal methods used by condition variables
def _acquire_restore(self, state):
@@ -286,9 +290,10 @@ class Condition:
if lock is None:
lock = RLock()
self._lock = lock
- # Export the lock's acquire() and release() methods
+ # Export the lock's acquire(), release(), and locked() methods
self.acquire = lock.acquire
self.release = lock.release
+ self.locked = lock.locked
# If the lock defines _release_save() and/or _acquire_restore(),
# these override the default implementations (which just call
# release() and acquire() on the lock). Ditto for _is_owned().
diff --git a/Misc/NEWS.d/next/Library/2025-04-01-11-16-22.gh-issue-115942.4W3hNx.rst b/Misc/NEWS.d/next/Library/2025-04-01-11-16-22.gh-issue-115942.4W3hNx.rst
new file mode 100644
index 0000000..8c3538c
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-04-01-11-16-22.gh-issue-115942.4W3hNx.rst
@@ -0,0 +1,5 @@
+Add :meth:`threading.RLock.locked`,
+:meth:`multiprocessing.Lock.locked`,
+:meth:`multiprocessing.RLock.locked`,
+and allow :meth:`multiprocessing.managers.SyncManager.Lock` and
+:meth:`multiprocessing.managers.SyncManager.RLock` to proxy ``locked()`` call.
diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c
index f4c98ca..9f6ac21 100644
--- a/Modules/_threadmodule.c
+++ b/Modules/_threadmodule.c
@@ -1087,6 +1087,19 @@ PyDoc_STRVAR(rlock_exit_doc,
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);
+ return PyBool_FromLong(is_locked);
+}
+
+PyDoc_STRVAR(rlock_locked_doc,
+"locked()\n\
+\n\
+Return a boolean indicating whether this object is locked right now.");
+
+static PyObject *
rlock_acquire_restore(PyObject *op, PyObject *args)
{
rlockobject *self = rlockobject_CAST(op);
@@ -1204,6 +1217,8 @@ static PyMethodDef rlock_methods[] = {
METH_VARARGS | METH_KEYWORDS, rlock_acquire_doc},
{"release", rlock_release,
METH_NOARGS, rlock_release_doc},
+ {"locked", rlock_locked,
+ METH_NOARGS, rlock_locked_doc},
{"_is_owned", rlock_is_owned,
METH_NOARGS, rlock_is_owned_doc},
{"_acquire_restore", rlock_acquire_restore,