From 6ce10f49b53620161427a329e42b2b1d46f57e09 Mon Sep 17 00:00:00 2001 From: Zen van Riel <45333650+Zenulous@users.noreply.github.com> Date: Thu, 3 Apr 2025 08:51:08 +0000 Subject: [PATCH 1/2] Change ThreadLock to ThreadRLock to resolve rare deadlock --- httpcore/_sync/connection_pool.py | 4 ++-- httpcore/_synchronization.py | 11 ++++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/httpcore/_sync/connection_pool.py b/httpcore/_sync/connection_pool.py index 9ccfa53e..a61493cf 100644 --- a/httpcore/_sync/connection_pool.py +++ b/httpcore/_sync/connection_pool.py @@ -9,7 +9,7 @@ from .._backends.base import SOCKET_OPTION, NetworkBackend from .._exceptions import ConnectionNotAvailable, UnsupportedProtocol from .._models import Origin, Proxy, Request, Response -from .._synchronization import Event, ShieldCancellation, ThreadLock +from .._synchronization import Event, ShieldCancellation, ThreadRLock from .connection import HTTPConnection from .interfaces import ConnectionInterface, RequestInterface @@ -123,7 +123,7 @@ def __init__( # We only mutate the state of the connection pool within an 'optional_thread_lock' # context. This holds a threading lock unless we're running in async mode, # in which case it is a no-op. - self._optional_thread_lock = ThreadLock() + self._optional_thread_lock = ThreadRLock() def create_connection(self, origin: Origin) -> ConnectionInterface: if self._proxy is not None: diff --git a/httpcore/_synchronization.py b/httpcore/_synchronization.py index 2ecc9e9c..eedcd441 100644 --- a/httpcore/_synchronization.py +++ b/httpcore/_synchronization.py @@ -94,7 +94,7 @@ class AsyncThreadLock: """ This is a threading-only lock for no-I/O contexts. - In the sync case `ThreadLock` provides thread locking. + In the sync case `ThreadRLock` provides thread locking. In the async case `AsyncThreadLock` is a no-op. """ @@ -253,18 +253,19 @@ def __exit__( self._lock.release() -class ThreadLock: +class ThreadRLock: """ This is a threading-only lock for no-I/O contexts. + The lock type is a reentrant lock. - In the sync case `ThreadLock` provides thread locking. + In the sync case `ThreadRLock` provides thread locking. In the async case `AsyncThreadLock` is a no-op. """ def __init__(self) -> None: - self._lock = threading.Lock() + self._lock = threading.RLock() - def __enter__(self) -> ThreadLock: + def __enter__(self) -> ThreadRLock: self._lock.acquire() return self From 306855f15115d5a1875600f38081cf145124aeee Mon Sep 17 00:00:00 2001 From: Zen van Riel <45333650+Zenulous@users.noreply.github.com> Date: Thu, 3 Apr 2025 08:51:45 +0000 Subject: [PATCH 2/2] Rename AsyncThreadLock to match test expectation --- httpcore/_async/connection_pool.py | 4 ++-- httpcore/_synchronization.py | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/httpcore/_async/connection_pool.py b/httpcore/_async/connection_pool.py index 96e973d0..5e49e2f5 100644 --- a/httpcore/_async/connection_pool.py +++ b/httpcore/_async/connection_pool.py @@ -9,7 +9,7 @@ from .._backends.base import SOCKET_OPTION, AsyncNetworkBackend from .._exceptions import ConnectionNotAvailable, UnsupportedProtocol from .._models import Origin, Proxy, Request, Response -from .._synchronization import AsyncEvent, AsyncShieldCancellation, AsyncThreadLock +from .._synchronization import AsyncEvent, AsyncShieldCancellation, AsyncThreadRLock from .connection import AsyncHTTPConnection from .interfaces import AsyncConnectionInterface, AsyncRequestInterface @@ -123,7 +123,7 @@ def __init__( # We only mutate the state of the connection pool within an 'optional_thread_lock' # context. This holds a threading lock unless we're running in async mode, # in which case it is a no-op. - self._optional_thread_lock = AsyncThreadLock() + self._optional_thread_lock = AsyncThreadRLock() def create_connection(self, origin: Origin) -> AsyncConnectionInterface: if self._proxy is not None: diff --git a/httpcore/_synchronization.py b/httpcore/_synchronization.py index eedcd441..0c3859e5 100644 --- a/httpcore/_synchronization.py +++ b/httpcore/_synchronization.py @@ -90,15 +90,16 @@ async def __aexit__( self._anyio_lock.release() -class AsyncThreadLock: +class AsyncThreadRLock: """ This is a threading-only lock for no-I/O contexts. + The lock type is a reentrant lock. In the sync case `ThreadRLock` provides thread locking. - In the async case `AsyncThreadLock` is a no-op. + In the async case `AsyncThreadRLock` is a no-op. """ - def __enter__(self) -> AsyncThreadLock: + def __enter__(self) -> AsyncThreadRLock: return self def __exit__( @@ -259,7 +260,7 @@ class ThreadRLock: The lock type is a reentrant lock. In the sync case `ThreadRLock` provides thread locking. - In the async case `AsyncThreadLock` is a no-op. + In the async case `AsyncThreadRLock` is a no-op. """ def __init__(self) -> None: