From a73b47f93612c602cfcdf105b7d67188cd9dbf46 Mon Sep 17 00:00:00 2001 From: Chao Peng Date: Wed, 17 Dec 2025 01:40:13 -0500 Subject: [PATCH 1/3] fix issue 141460 --- Doc/library/threading.rst | 7 +- .../test_free_threading/test_monitoring.py | 4 +- Lib/test/test_threading.py | 8 +- Lib/threading.py | 149 +----------------- ...-12-16-14-46-14.gh-issue-141460._44DYc.rst | 1 + 5 files changed, 10 insertions(+), 159 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-12-16-14-46-14.gh-issue-141460._44DYc.rst diff --git a/Doc/library/threading.rst b/Doc/library/threading.rst index 19cc4f191dff8d..90d7c9a24638dd 100644 --- a/Doc/library/threading.rst +++ b/Doc/library/threading.rst @@ -848,9 +848,10 @@ call release as many times the lock has been acquired can lead to deadlock. reentrant lock, the same thread may acquire it again without blocking; the thread must release it once for each time it has acquired it. - Note that ``RLock`` is actually a factory function which returns an instance - of the most efficient version of the concrete RLock class that is supported - by the platform. + .. versionchanged:: 3.15 + ``RLock`` is now a class. In earlier Pythons, ``RLock`` was a factory + function which returned an instance of the underlying private lock + type. .. method:: acquire(blocking=True, timeout=-1) diff --git a/Lib/test/test_free_threading/test_monitoring.py b/Lib/test/test_free_threading/test_monitoring.py index 2cd6e7b035ecb4..925a4370a04fcb 100644 --- a/Lib/test/test_free_threading/test_monitoring.py +++ b/Lib/test/test_free_threading/test_monitoring.py @@ -10,7 +10,7 @@ from contextlib import contextmanager from sys import monitoring from test.support import threading_helper -from threading import Thread, _PyRLock, Barrier +from threading import Thread, RLock, Barrier from unittest import TestCase @@ -287,7 +287,7 @@ def trace(frame, event, arg): sys.settrace(trace) try: - l = _PyRLock() + l = RLock() def f(): for i in range(loops): diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index efd69a1f4fe468..b13dbf3b24c446 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -2264,12 +2264,8 @@ def _callback_spy(self, *args, **kwargs): class LockTests(lock_tests.LockTests): locktype = staticmethod(threading.Lock) -class PyRLockTests(lock_tests.RLockTests): - locktype = staticmethod(threading._PyRLock) - -@unittest.skipIf(threading._CRLock is None, 'RLock not implemented in C') -class CRLockTests(lock_tests.RLockTests): - locktype = staticmethod(threading._CRLock) +class RLockTests(lock_tests.RLockTests): + locktype = staticmethod(threading.RLock) def test_signature(self): # gh-102029 with warnings.catch_warnings(record=True) as warnings_log: diff --git a/Lib/threading.py b/Lib/threading.py index 4ebceae7029870..1ba455c056ce10 100644 --- a/Lib/threading.py +++ b/Lib/threading.py @@ -53,10 +53,7 @@ except AttributeError: _set_name = None ThreadError = _thread.error -try: - _CRLock = _thread.RLock -except AttributeError: - _CRLock = None +RLock = _thread.RLock TIMEOUT_MAX = _thread.TIMEOUT_MAX del _thread @@ -123,150 +120,6 @@ def gettrace(): Lock = _LockType -def RLock(): - """Factory function that returns a new reentrant lock. - - A reentrant lock must be released by the thread that acquired it. Once a - thread has acquired a reentrant lock, the same thread may acquire it again - without blocking; the thread must release it once for each time it has - acquired it. - - """ - if _CRLock is None: - return _PyRLock() - return _CRLock() - -class _RLock: - """This class implements reentrant lock objects. - - A reentrant lock must be released by the thread that acquired it. Once a - thread has acquired a reentrant lock, the same thread may acquire it - again without blocking; the thread must release it once for each time it - has acquired it. - - """ - - def __init__(self): - self._block = _allocate_lock() - self._owner = None - self._count = 0 - - def __repr__(self): - owner = self._owner - try: - owner = _active[owner].name - except KeyError: - pass - return "<%s %s.%s object owner=%r count=%d at %s>" % ( - "locked" if self.locked() else "unlocked", - self.__class__.__module__, - self.__class__.__qualname__, - owner, - self._count, - hex(id(self)) - ) - - def _at_fork_reinit(self): - self._block._at_fork_reinit() - self._owner = None - self._count = 0 - - def acquire(self, blocking=True, timeout=-1): - """Acquire a lock, blocking or non-blocking. - - When invoked without arguments: if this thread already owns the lock, - increment the recursion level by one, and return immediately. Otherwise, - if another thread owns the lock, block until the lock is unlocked. Once - the lock is unlocked (not owned by any thread), then grab ownership, set - the recursion level to one, and return. If more than one thread is - blocked waiting until the lock is unlocked, only one at a time will be - able to grab ownership of the lock. There is no return value in this - case. - - When invoked with the blocking argument set to true, do the same thing - as when called without arguments, and return true. - - When invoked with the blocking argument set to false, do not block. If a - call without an argument would block, return false immediately; - otherwise, do the same thing as when called without arguments, and - return true. - - When invoked with the floating-point timeout argument set to a positive - value, block for at most the number of seconds specified by timeout - and as long as the lock cannot be acquired. Return true if the lock has - been acquired, false if the timeout has elapsed. - - """ - me = get_ident() - if self._owner == me: - self._count += 1 - return 1 - rc = self._block.acquire(blocking, timeout) - if rc: - self._owner = me - self._count = 1 - return rc - - __enter__ = acquire - - def release(self): - """Release a lock, decrementing the recursion level. - - If after the decrement it is zero, reset the lock to unlocked (not owned - by any thread), and if any other threads are blocked waiting for the - lock to become unlocked, allow exactly one of them to proceed. If after - the decrement the recursion level is still nonzero, the lock remains - locked and owned by the calling thread. - - Only call this method when the calling thread owns the lock. A - RuntimeError is raised if this method is called when the lock is - unlocked. - - There is no return value. - - """ - if self._owner != get_ident(): - raise RuntimeError("cannot release un-acquired lock") - self._count = count = self._count - 1 - if not count: - self._owner = None - self._block.release() - - def __exit__(self, t, v, tb): - self.release() - - def locked(self): - """Return whether this object is locked.""" - return self._block.locked() - - # Internal methods used by condition variables - - def _acquire_restore(self, state): - self._block.acquire() - self._count, self._owner = state - - def _release_save(self): - if self._count == 0: - raise RuntimeError("cannot release un-acquired lock") - count = self._count - self._count = 0 - owner = self._owner - self._owner = None - self._block.release() - return (count, owner) - - def _is_owned(self): - return self._owner == get_ident() - - # Internal method used for reentrancy checks - - def _recursion_count(self): - if self._owner != get_ident(): - return 0 - return self._count - -_PyRLock = _RLock - class Condition: """Class that implements a condition variable. diff --git a/Misc/NEWS.d/next/Library/2025-12-16-14-46-14.gh-issue-141460._44DYc.rst b/Misc/NEWS.d/next/Library/2025-12-16-14-46-14.gh-issue-141460._44DYc.rst new file mode 100644 index 00000000000000..8b2300c188fc72 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-12-16-14-46-14.gh-issue-141460._44DYc.rst @@ -0,0 +1 @@ +make threading.RLock a real class, not a factory function From fb5d60b8122129b1f115db98985366aefe617594 Mon Sep 17 00:00:00 2001 From: Chao Peng Date: Fri, 19 Dec 2025 07:26:24 -0500 Subject: [PATCH 2/3] exclude RLock from test_threading_module_has_signatures following test_thread_module_has_signatures --- Lib/test/test_inspect/test_inspect.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 075e1802bebc3e..d1b159ae43bed7 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -6280,7 +6280,8 @@ def test_sysconfig_module_has_signatures(self): def test_threading_module_has_signatures(self): import threading - self._test_module_has_signatures(threading) + no_signature = {'RLock'} + self._test_module_has_signatures(threading, no_signature) self.assertIsNotNone(inspect.signature(threading.__excepthook__)) def test_thread_module_has_signatures(self): From 56852b7930fdfe32937b301e868b4fb3a513aeb8 Mon Sep 17 00:00:00 2001 From: chaope Date: Fri, 19 Dec 2025 16:26:55 -0500 Subject: [PATCH 3/3] Update Misc/NEWS.d/next/Library/2025-12-16-14-46-14.gh-issue-141460._44DYc.rst Co-authored-by: AN Long --- .../next/Library/2025-12-16-14-46-14.gh-issue-141460._44DYc.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2025-12-16-14-46-14.gh-issue-141460._44DYc.rst b/Misc/NEWS.d/next/Library/2025-12-16-14-46-14.gh-issue-141460._44DYc.rst index 8b2300c188fc72..91f1d4ed141bc8 100644 --- a/Misc/NEWS.d/next/Library/2025-12-16-14-46-14.gh-issue-141460._44DYc.rst +++ b/Misc/NEWS.d/next/Library/2025-12-16-14-46-14.gh-issue-141460._44DYc.rst @@ -1 +1 @@ -make threading.RLock a real class, not a factory function +Make :class:`threading.RLock` a real class instead of a factory function.