Skip to content

Add lock to ReAwaitable for concurrent awaits #2109

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 17 commits into
base: master
Choose a base branch
from

Conversation

proboscis
Copy link
Contributor

@proboscis proboscis commented Apr 11, 2025

I have made things!

Fix for issue: #2108

Checklist

  • I have double checked that there are no unrelated changes in this pull request
  • I have created a test case for the changes I have made (test_reawaitable_concurrency.py)
  • I have updated the documentation for the changes I have made
  • I have added my changes to the \

Related issues

This PR addresses the issue where multiple concurrent awaits on the same ReAwaitable instance could lead to unexpected behavior. By adding a lock around the critical section in the _awaitable method, we ensure that the coroutine is only awaited once even when multiple tasks await the same ReAwaitable concurrently.

The PR includes a test case that demonstrates concurrent awaiting works correctly with the lock in place.

🙏 Please, if you or your company finds \ valuable, help us sustain the project by sponsoring it transparently on https://github.com/sponsors/dry-python.

Copy link
Member

@sobolevn sobolevn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!



async def sample_coro():
await anyio.sleep(0.1)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
await anyio.sleep(0.1)
await anyio.sleep(1)

give less chance for random failures.


@pytest.mark.anyio
async def test_concurrent_awaitable():
reawaitable = ReAwaitable(sample_coro())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, add a link to the original bug on github.

from returns.primitives.reawaitable import ReAwaitable


async def sample_coro():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
async def sample_coro():
async def sample_coro() -> str:



@pytest.mark.anyio
async def test_concurrent_awaitable():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
async def test_concurrent_awaitable():
async def test_concurrent_awaitable() -> None:

@@ -2,6 +2,8 @@
from functools import wraps
from typing import NewType, ParamSpec, TypeVar, cast, final

import anyio
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not in list of production deps:

returns/pyproject.toml

Lines 51 to 57 in bbfc3d3

[tool.poetry.dependencies]
python = "^3.10"
typing-extensions = ">=4.0,<5.0"
pytest = { version = "^8.0", optional = true }
hypothesis = { version = "^6.122", optional = true }
mypy = { version = ">=1.12,<1.16", optional = true }

Can we use ifs to find the correct lock type?

Copy link
Contributor Author

@proboscis proboscis Apr 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, but unfortunately i am not familiar with other libraries than pure asyncio.
Is it appropriate to use asyncio.Lock?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the fast review!

Copy link
Contributor Author

@proboscis proboscis Apr 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah maybe something like this?

try:
 import anyio
 Lock = anyio.Lock
except ImportError:
 import asyncio
 Lock = asyncio.Lock

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, this try is fine :)

@proboscis proboscis force-pushed the add-reawaitable-lock branch from 0c8f6b6 to 4d646e9 Compare April 11, 2025 08:46
@proboscis proboscis force-pushed the add-reawaitable-lock branch from ec52eca to 94d5b1f Compare April 11, 2025 08:48
@proboscis proboscis force-pushed the add-reawaitable-lock branch from 33f89f9 to 3e7fed1 Compare April 11, 2025 09:14
@proboscis proboscis force-pushed the add-reawaitable-lock branch from c4300c0 to 2d8ae80 Compare April 11, 2025 09:17
@proboscis proboscis force-pushed the add-reawaitable-lock branch from 1569389 to a1206af Compare April 11, 2025 13:48
Copy link
Member

@sobolevn sobolevn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Last comments

@proboscis
Copy link
Contributor Author

Thank you! I hope this works.


Lock = asyncio.Lock
else:
Lock = anyio.Lock
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can type it as a simple interface that only supports async with, probably like contextlib.AbstractAsyncContextManager

Copy link
Member

@sobolevn sobolevn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, don't forget to document that we may require anyio lib in this function for trio

proboscis and others added 2 commits April 15, 2025 16:49
- Add notes that anyio is required for proper trio support
- Add type annotation for Lock variable
- Document the asyncio.Lock fallback behavior

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
Copy link
Member

@sobolevn sobolevn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we would have to use a custom protocol :(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

2 participants