Skip to content

Commit

Permalink
Fix bug with async_lock implementation
Browse files Browse the repository at this point in the history
- ``async_lock`` would hold onto the lock even if a task had been cancelled. This change ensures that the lock will be released if an exception is thrown during ``loop.run_in_executor()``.
  • Loading branch information
fselmo committed Oct 26, 2022
1 parent a59bcb3 commit fe78025
Show file tree
Hide file tree
Showing 3 changed files with 24 additions and 1 deletion.
1 change: 1 addition & 0 deletions newsfragments/2695.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Properly release ``async_lock`` for session requests if an exception is raised during a task.
22 changes: 22 additions & 0 deletions tests/core/utilities/test_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
)
from web3._utils.request import (
SessionCache,
_async_session_cache_lock,
async_lock,
cache_and_return_async_session,
cache_and_return_session,
)
Expand Down Expand Up @@ -323,3 +325,23 @@ def target_function(endpoint_uri):

# clear cache
request._async_session_cache.clear()


@pytest.mark.asyncio
async def test_async_lock_releases_if_a_task_is_cancelled():
# inspired by issue #2693
# Note: this test will raise a `TimeoutError` if `requests.async_lock` is not
# applied correctly

async def _utilize_async_lock():
async with async_lock(_async_session_cache_lock):
await asyncio.sleep(0.1)

asyncio.create_task(_utilize_async_lock())

inner = asyncio.create_task(_utilize_async_lock())
await asyncio.sleep(0.1)
inner.cancel()

outer = asyncio.wait_for(_utilize_async_lock(), 1)
await outer
2 changes: 1 addition & 1 deletion web3/_utils/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,8 @@ async def cache_and_return_async_session(
@contextlib.asynccontextmanager
async def async_lock(lock: threading.Lock) -> AsyncGenerator[None, None]:
loop = asyncio.get_event_loop()
await loop.run_in_executor(_pool, lock.acquire)
try:
await loop.run_in_executor(_pool, lock.acquire)
yield
finally:
lock.release()
Expand Down

0 comments on commit fe78025

Please sign in to comment.