gh-141460: make RLock a class instead of a factory function#142977
gh-141460: make RLock a class instead of a factory function#142977chaope wants to merge 4 commits intopython:mainfrom
Conversation
…t_thread_module_has_signatures
Misc/NEWS.d/next/Library/2025-12-16-14-46-14.gh-issue-141460._44DYc.rst
Outdated
Show resolved
Hide resolved
…44DYc.rst Co-authored-by: AN Long <aisk@users.noreply.github.com>
vstinner
left a comment
There was a problem hiding this comment.
LGTM. It makes sense to remove the Python implementation and declare threading.RLock as a class.
| @@ -0,0 +1 @@ | |||
| Make :class:`threading.RLock` a real class instead of a factory function. | |||
There was a problem hiding this comment.
Please mention also that the Python implementation has been removed.
| @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): |
There was a problem hiding this comment.
According to PEP 8, there should be two blank lines between classes. Other parts of this file may not follow PEP 8, but since we touched this part, let's update it (and keep the rest as it is).
| class RLockTests(lock_tests.RLockTests): | |
| class RLockTests(lock_tests.RLockTests): |
|
|
| return _PyRLock() | ||
| return _CRLock() | ||
|
|
||
| class _RLock: |
There was a problem hiding this comment.
For backwards compatibility with people using threading._RLock for their types, could we keep _RLock = RLock? It doesn't cost us much and won't be an issue but at least it won't be cause breakage downstream where people assume _RLock to be a class.
Same with keeping _CRLock and _PyRLock just for backward compatibility.
I know that we don't need to do it, but it's preferrable that we don't remove symbols like that sitting for the past decades.
There was a problem hiding this comment.
A code search on PyPI top 15,000 projects for threading._RLock found 6 matching projects:
PYPI-2026-01-07/astroid-4.0.3.tar.gz: astroid-4.0.3/tests/brain/test_multiprocessing.py: rlock_name = "threading._RLock"
PYPI-2026-01-07/django_sendgrid_v5-1.3.1.tar.gz: django_sendgrid_v5-1.3.1/sendgrid_backend/mail.py: self._lock = None # type: Optional[threading._RLock]
PYPI-2026-01-07/gevent-25.9.1.tar.gz: gevent-25.9.1/src/gevent/threading.py: # Previously it was a factory function around threading._RLock
PYPI-2026-01-07/prefect-3.6.9.tar.gz: prefect-3.6.9/src/prefect/_internal/concurrency/cancellation.py: self._lock = threading._RLock() # type: ignore # yes, we want the private version
PYPI-2026-01-07/prefect_client-3.6.9.tar.gz: prefect_client-3.6.9/src/prefect/_internal/concurrency/cancellation.py: self._lock = threading._RLock() # type: ignore # yes, we want the private version
PYPI-2026-01-07/pylint-4.0.4.tar.gz: pylint-4.0.4/pylint/checkers/refactoring/refactoring_checker.py: "threading._RLock.acquire",
I also found 7 projects embedding a copy of typeshed annotations (multiprocessing/managers.pyi) which also refers to threading._RLock. Example:
PYPI-2026-01-07/mypy-1.19.1.tar.gz: mypy-1.19.1/mypy/typeshed/stdlib/multiprocessing/managers.pyi: def Condition(self, lock: threading.Lock | threading._RLock | None = None) -> threading.Condition: ...
A code search on PyPI top 15,000 projects for threading._(CRLock|PyRLock) found 2 matching projects: eventlet and gevent.
PYPI-2026-01-07/eventlet-0.40.4.tar.gz: eventlet-0.40.4/eventlet/patcher.py: threading.RLock = threading._PyRLock
PYPI-2026-01-07/eventlet-0.40.4.tar.gz: eventlet-0.40.4/eventlet/patcher.py: new = threading._PyRLock()
PYPI-2026-01-07/eventlet-0.40.4.tar.gz: eventlet-0.40.4/tests/isolated/patcher_existing_locks_preexisting.py: python_lock = threading._PyRLock
PYPI-2026-01-07/eventlet-0.40.4.tar.gz: eventlet-0.40.4/tests/isolated/patcher_existing_logging_module_lock.py: assert not isinstance(logging._lock, threading._PyRLock)
PYPI-2026-01-07/eventlet-0.40.4.tar.gz: eventlet-0.40.4/tests/isolated/patcher_existing_logging_module_lock.py: assert not isinstance(handler.lock, threading._PyRLock)
PYPI-2026-01-07/eventlet-0.40.4.tar.gz: eventlet-0.40.4/tests/isolated/patcher_existing_logging_module_lock.py: assert isinstance(logging._lock, threading._PyRLock)
PYPI-2026-01-07/eventlet-0.40.4.tar.gz: eventlet-0.40.4/tests/isolated/patcher_existing_logging_module_lock.py: assert isinstance(handler.lock, threading._PyRLock)
PYPI-2026-01-07/gevent-25.9.1.tar.gz: gevent-25.9.1/src/gevent/threading.py: # threading._CRLock, which is imported from _thread.RLock and as such
PYPI-2026-01-07/gevent-25.9.1.tar.gz: gevent-25.9.1/src/greentest/3.10/test_threading.py: locktype = staticmethod(threading._PyRLock)
PYPI-2026-01-07/gevent-25.9.1.tar.gz: gevent-25.9.1/src/greentest/3.10/test_threading.py: @unittest.skipIf(threading._CRLock is None, 'RLock not implemented in C')
PYPI-2026-01-07/gevent-25.9.1.tar.gz: gevent-25.9.1/src/greentest/3.10/test_threading.py: locktype = staticmethod(threading._CRLock)
PYPI-2026-01-07/gevent-25.9.1.tar.gz: gevent-25.9.1/src/greentest/3.11/test_threading.py: locktype = staticmethod(threading._PyRLock)
PYPI-2026-01-07/gevent-25.9.1.tar.gz: gevent-25.9.1/src/greentest/3.11/test_threading.py: @unittest.skipIf(threading._CRLock is None, 'RLock not implemented in C')
PYPI-2026-01-07/gevent-25.9.1.tar.gz: gevent-25.9.1/src/greentest/3.11/test_threading.py: locktype = staticmethod(threading._CRLock)
PYPI-2026-01-07/gevent-25.9.1.tar.gz: gevent-25.9.1/src/greentest/3.12/test_threading.py: locktype = staticmethod(threading._PyRLock)
PYPI-2026-01-07/gevent-25.9.1.tar.gz: gevent-25.9.1/src/greentest/3.12/test_threading.py: @unittest.skipIf(threading._CRLock is None, 'RLock not implemented in C')
PYPI-2026-01-07/gevent-25.9.1.tar.gz: gevent-25.9.1/src/greentest/3.12/test_threading.py: locktype = staticmethod(threading._CRLock)
PYPI-2026-01-07/gevent-25.9.1.tar.gz: gevent-25.9.1/src/greentest/3.13/test_threading.py: locktype = staticmethod(threading._PyRLock)
PYPI-2026-01-07/gevent-25.9.1.tar.gz: gevent-25.9.1/src/greentest/3.13/test_threading.py: @unittest.skipIf(threading._CRLock is None, 'RLock not implemented in C')
PYPI-2026-01-07/gevent-25.9.1.tar.gz: gevent-25.9.1/src/greentest/3.13/test_threading.py: locktype = staticmethod(threading._CRLock)
PYPI-2026-01-07/gevent-25.9.1.tar.gz: gevent-25.9.1/src/greentest/3.14/test_threading.py: locktype = staticmethod(threading._PyRLock)
PYPI-2026-01-07/gevent-25.9.1.tar.gz: gevent-25.9.1/src/greentest/3.14/test_threading.py: @unittest.skipIf(threading._CRLock is None, 'RLock not implemented in C')
PYPI-2026-01-07/gevent-25.9.1.tar.gz: gevent-25.9.1/src/greentest/3.14/test_threading.py: locktype = staticmethod(threading._CRLock)
PYPI-2026-01-07/gevent-25.9.1.tar.gz: gevent-25.9.1/src/greentest/3.9/test_threading.py: locktype = staticmethod(threading._PyRLock)
PYPI-2026-01-07/gevent-25.9.1.tar.gz: gevent-25.9.1/src/greentest/3.9/test_threading.py: @unittest.skipIf(threading._CRLock is None, 'RLock not implemented in C')
PYPI-2026-01-07/gevent-25.9.1.tar.gz: gevent-25.9.1/src/greentest/3.9/test_threading.py: locktype = staticmethod(threading._CRLock)
There was a problem hiding this comment.
Would it be possible to deprecate threading._RLock if we decide to keep it?
For _CRLock and _PyRLock, I don't think that we need to keep them. Impacted projects (eventlet, gevent) should be updated anyway for this change.
There was a problem hiding this comment.
So, I think we need to contact those projects or make the change louder. This typically requires something in the What's New page. However, there might be other private projects that could be broken. I really don't want to remove this class like that. However, making the attribute raise a deprecation warning could be better. Yes, it will pollute outputs but people know that the implementation will really be different (that is, it will be using the C implementation).
| __enter__ = acquire | ||
|
|
||
| def release(self): | ||
| """Release a lock, decrementing the recursion level. |
There was a problem hiding this comment.
Can you quickly check that the docstrings match the C docstrings as well please?
Oh, it's a good idea to remove the |
gh-141460 make RLock a class instead of a factory function
📚 Documentation preview 📚: https://cpython-previews--142977.org.readthedocs.build/