Skip to content

Commit f7305a0

Browse files
sobolevnmpagehugovk
authored
gh-115942: Add locked to several multiprocessing locks (#115944)
Co-authored-by: mpage <[email protected]> Co-authored-by: Hugo van Kemenade <[email protected]>
1 parent 6cd1d6c commit f7305a0

File tree

10 files changed

+89
-6
lines changed

10 files changed

+89
-6
lines changed

Doc/library/multiprocessing.rst

+14
Original file line numberDiff line numberDiff line change
@@ -1421,6 +1421,13 @@ object -- see :ref:`multiprocessing-managers`.
14211421
when invoked on an unlocked lock, a :exc:`ValueError` is raised.
14221422

14231423

1424+
.. method:: locked()
1425+
1426+
Return a boolean indicating whether this object is locked right now.
1427+
1428+
.. versionadded:: next
1429+
1430+
14241431
.. class:: RLock()
14251432

14261433
A recursive lock object: a close analog of :class:`threading.RLock`. A
@@ -1481,6 +1488,13 @@ object -- see :ref:`multiprocessing-managers`.
14811488
differs from the implemented behavior in :meth:`threading.RLock.release`.
14821489

14831490

1491+
.. method:: locked()
1492+
1493+
Return a boolean indicating whether this object is locked right now.
1494+
1495+
.. versionadded:: next
1496+
1497+
14841498
.. class:: Semaphore([value])
14851499

14861500
A semaphore object: a close analog of :class:`threading.Semaphore`.

Doc/library/threading.rst

+13
Original file line numberDiff line numberDiff line change
@@ -709,6 +709,13 @@ call release as many times the lock has been acquired can lead to deadlock.
709709
There is no return value.
710710

711711

712+
.. method:: locked()
713+
714+
Return a boolean indicating whether this object is locked right now.
715+
716+
.. versionadded:: next
717+
718+
712719
.. _condition-objects:
713720

714721
Condition Objects
@@ -801,6 +808,12 @@ item to the buffer only needs to wake up one consumer thread.
801808
Release the underlying lock. This method calls the corresponding method on
802809
the underlying lock; there is no return value.
803810

811+
.. method:: locked()
812+
813+
Return a boolean indicating whether this object is locked right now.
814+
815+
.. versionadded:: next
816+
804817
.. method:: wait(timeout=None)
805818

806819
Wait until notified or until a timeout occurs. If the calling thread has

Lib/importlib/_bootstrap.py

+3
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,9 @@ def release(self):
382382
self.waiters.pop()
383383
self.wakeup.release()
384384

385+
def locked(self):
386+
return bool(self.count)
387+
385388
def __repr__(self):
386389
return f'_ModuleLock({self.name!r}) at {id(self)}'
387390

Lib/multiprocessing/managers.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -1059,20 +1059,22 @@ def close(self, *args):
10591059

10601060

10611061
class AcquirerProxy(BaseProxy):
1062-
_exposed_ = ('acquire', 'release')
1062+
_exposed_ = ('acquire', 'release', 'locked')
10631063
def acquire(self, blocking=True, timeout=None):
10641064
args = (blocking,) if timeout is None else (blocking, timeout)
10651065
return self._callmethod('acquire', args)
10661066
def release(self):
10671067
return self._callmethod('release')
1068+
def locked(self):
1069+
return self._callmethod('locked')
10681070
def __enter__(self):
10691071
return self._callmethod('acquire')
10701072
def __exit__(self, exc_type, exc_val, exc_tb):
10711073
return self._callmethod('release')
10721074

10731075

10741076
class ConditionProxy(AcquirerProxy):
1075-
_exposed_ = ('acquire', 'release', 'wait', 'notify', 'notify_all')
1077+
_exposed_ = ('acquire', 'release', 'locked', 'wait', 'notify', 'notify_all')
10761078
def wait(self, timeout=None):
10771079
return self._callmethod('wait', (timeout,))
10781080
def notify(self, n=1):

Lib/multiprocessing/synchronize.py

+3
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ def _make_methods(self):
9090
self.acquire = self._semlock.acquire
9191
self.release = self._semlock.release
9292

93+
def locked(self):
94+
return self._semlock._count() != 0
95+
9396
def __enter__(self):
9497
return self._semlock.__enter__()
9598

Lib/test/_test_multiprocessing.py

+14-3
Original file line numberDiff line numberDiff line change
@@ -1486,8 +1486,10 @@ def test_repr_lock(self):
14861486
def test_lock(self):
14871487
lock = self.Lock()
14881488
self.assertEqual(lock.acquire(), True)
1489+
self.assertTrue(lock.locked())
14891490
self.assertEqual(lock.acquire(False), False)
14901491
self.assertEqual(lock.release(), None)
1492+
self.assertFalse(lock.locked())
14911493
self.assertRaises((ValueError, threading.ThreadError), lock.release)
14921494

14931495
@staticmethod
@@ -1549,16 +1551,23 @@ def test_repr_rlock(self):
15491551
def test_rlock(self):
15501552
lock = self.RLock()
15511553
self.assertEqual(lock.acquire(), True)
1554+
self.assertTrue(lock.locked())
15521555
self.assertEqual(lock.acquire(), True)
15531556
self.assertEqual(lock.acquire(), True)
15541557
self.assertEqual(lock.release(), None)
1558+
self.assertTrue(lock.locked())
15551559
self.assertEqual(lock.release(), None)
15561560
self.assertEqual(lock.release(), None)
1561+
self.assertFalse(lock.locked())
15571562
self.assertRaises((AssertionError, RuntimeError), lock.release)
15581563

15591564
def test_lock_context(self):
1560-
with self.Lock():
1561-
pass
1565+
with self.Lock() as locked:
1566+
self.assertTrue(locked)
1567+
1568+
def test_rlock_context(self):
1569+
with self.RLock() as locked:
1570+
self.assertTrue(locked)
15621571

15631572

15641573
class _TestSemaphore(BaseTestCase):
@@ -6254,6 +6263,7 @@ def test_event(self):
62546263
@classmethod
62556264
def _test_lock(cls, obj):
62566265
obj.acquire()
6266+
obj.locked()
62576267

62586268
def test_lock(self, lname="Lock"):
62596269
o = getattr(self.manager, lname)()
@@ -6265,8 +6275,9 @@ def test_lock(self, lname="Lock"):
62656275
def _test_rlock(cls, obj):
62666276
obj.acquire()
62676277
obj.release()
6278+
obj.locked()
62686279

6269-
def test_rlock(self, lname="Lock"):
6280+
def test_rlock(self, lname="RLock"):
62706281
o = getattr(self.manager, lname)()
62716282
self.run_worker(self._test_rlock, o)
62726283

Lib/test/lock_tests.py

+12
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,18 @@ def test_release_unacquired(self):
353353
lock.release()
354354
self.assertRaises(RuntimeError, lock.release)
355355

356+
def test_locked(self):
357+
lock = self.locktype()
358+
self.assertFalse(lock.locked())
359+
lock.acquire()
360+
self.assertTrue(lock.locked())
361+
lock.acquire()
362+
self.assertTrue(lock.locked())
363+
lock.release()
364+
self.assertTrue(lock.locked())
365+
lock.release()
366+
self.assertFalse(lock.locked())
367+
356368
def test_release_save_unacquired(self):
357369
# Cannot _release_save an unacquired lock
358370
lock = self.locktype()

Lib/threading.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,10 @@ def release(self):
241241
def __exit__(self, t, v, tb):
242242
self.release()
243243

244+
def locked(self):
245+
"""Return whether this object is locked."""
246+
return self._count > 0
247+
244248
# Internal methods used by condition variables
245249

246250
def _acquire_restore(self, state):
@@ -286,9 +290,10 @@ def __init__(self, lock=None):
286290
if lock is None:
287291
lock = RLock()
288292
self._lock = lock
289-
# Export the lock's acquire() and release() methods
293+
# Export the lock's acquire(), release(), and locked() methods
290294
self.acquire = lock.acquire
291295
self.release = lock.release
296+
self.locked = lock.locked
292297
# If the lock defines _release_save() and/or _acquire_restore(),
293298
# these override the default implementations (which just call
294299
# release() and acquire() on the lock). Ditto for _is_owned().
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Add :meth:`threading.RLock.locked`,
2+
:meth:`multiprocessing.Lock.locked`,
3+
:meth:`multiprocessing.RLock.locked`,
4+
and allow :meth:`multiprocessing.managers.SyncManager.Lock` and
5+
:meth:`multiprocessing.managers.SyncManager.RLock` to proxy ``locked()`` call.

Modules/_threadmodule.c

+15
Original file line numberDiff line numberDiff line change
@@ -1086,6 +1086,19 @@ PyDoc_STRVAR(rlock_exit_doc,
10861086
\n\
10871087
Release the lock.");
10881088

1089+
static PyObject *
1090+
rlock_locked(PyObject *op, PyObject *Py_UNUSED(ignored))
1091+
{
1092+
rlockobject *self = rlockobject_CAST(op);
1093+
int is_locked = _PyRecursiveMutex_IsLockedByCurrentThread(&self->lock);
1094+
return PyBool_FromLong(is_locked);
1095+
}
1096+
1097+
PyDoc_STRVAR(rlock_locked_doc,
1098+
"locked()\n\
1099+
\n\
1100+
Return a boolean indicating whether this object is locked right now.");
1101+
10891102
static PyObject *
10901103
rlock_acquire_restore(PyObject *op, PyObject *args)
10911104
{
@@ -1204,6 +1217,8 @@ static PyMethodDef rlock_methods[] = {
12041217
METH_VARARGS | METH_KEYWORDS, rlock_acquire_doc},
12051218
{"release", rlock_release,
12061219
METH_NOARGS, rlock_release_doc},
1220+
{"locked", rlock_locked,
1221+
METH_NOARGS, rlock_locked_doc},
12071222
{"_is_owned", rlock_is_owned,
12081223
METH_NOARGS, rlock_is_owned_doc},
12091224
{"_acquire_restore", rlock_acquire_restore,

0 commit comments

Comments
 (0)