-
-
Notifications
You must be signed in to change notification settings - Fork 354
add @as_safe_channel
#3197
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
base: main
Are you sure you want to change the base?
add @as_safe_channel
#3197
Conversation
I'm not too sold on how some of this is done, but at least now it shouldn't fail CI. edit: I'm also pretty sure the more correct way to fix the race condition would be using |
I tracked down the race condition to We still want to use The reason it works to move the |
…add buffer_size tests, remove unused code
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3197 +/- ##
====================================================
- Coverage 100.00000% 99.97897% -0.02103%
====================================================
Files 124 124
Lines 18844 19017 +173
Branches 1277 1287 +10
====================================================
+ Hits 18844 19013 +169
- Misses 0 3 +3
- Partials 0 1 +1
🚀 New features to boost your workflow:
|
@Zac-HD if you can show why the inner loop is necessary it'd be great, but I'm kinda suspecting it's a remnant of previous implementations or something - because I can't come up with anything that would hit that code path. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this'll help? But haven't run many tests yet...
add buffer_size note to docstring Co-authored-by: Zac Hatfield-Dodds <zac.hatfield.dodds@gmail.com>
for more information, see https://pre-commit.ci
…syncContextManager, I have no clue why
I have no clue why sphinx fails to link the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good and useful.
This is unrelated, but while we're editing the async-gen docs, we might want to swap out the async_generator.aclosing
reference to point to contextlib
first, with async_generator
as a backport.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code itself looks good!
does anybody have any idea about the RTD fail?
|
With a buffer size > 0 I don't even think it's all that rare to get multiple errors. But unless we want to remove that functionality I think it's correct to stick to groups. This might make people hesitant to use it as a drop-in-replacement and if that's explicitly what we're targeting we could maybe add a kwarg or an additional decorator that handles it. (and, uh, given how messy it is to convert exceptiongroups into single exceptions maybe that's a thing we want separately?)
good catch, done! I thought this was gonna be complicated, but turned out not too bad with the addition of a wrapper + semaphore.
I'm not even sure if it's possible to run in the same task and do it correctly? We could in theory make it look like it from the traceback, but not sure that's worth it |
Co-authored-by: A5rocks <git@helvetica.moe>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd be happy to merge this now, or after making one medium-sized design change.
Specifically, I think @A5rocks has a good point about raising ExceptionGroup
. If we've got max_buffer_size=0
(which is the default!), then our semaphore ensures that we can't be running user code in the generator concurrently with that in the body of the context manager. I think this is a rare good reason to unwrap the knowably-just-one exception in a group raised by the inner nursery - and that the smaller change relative to raw generators in the default case is a larger benefit than the cost of raising groups or not based on the max_buffer_size argument.
Thoughts?
I think if we're going towards single exceptions we should maybe never raise exceptiongroups, and either do Actually, I think the cleanest is to expose two different decorators: def background_with_channel():
...
def background_with_buffer_channel(buffer: int|None):
"""[...] raises ExceptionGroup [...]"""
... This also allows us to make interleave vs not much more explicit, as opposed to suggesting that anybody can start buffering any generator without any change in behavior around |
Let's merge as-is then. I don't want to split into multiple functions (linter recs etc get harder), we've already disabled |
sorry, with throwing I meant #3197 (comment) for linter recs we'd just always recommend |
#3197 (comment) doesn't bother me, because cancellation is always a possibility you have to handle. It "just so happens" that we always cancel "right before" the In practice most users are never going to touch the default, so maybe we should only supply the unbuffered version, and let power-users add their own buffering if desired? We could even share a recipe for the " |
Okay yeah this seems fine. The only time this would be insufficient is if they're relying on an external asyncgen they can't modify and they want buffering, but if so they can still just write an equivalent version of But god I am not looking forward to another go at the "oh just unwrap the exception from inside the group"; I'm tempted to go create a helper for that in a new PR, which would be pretty great for trio-websocket and probably others as well: https://github.com/python-trio/trio-websocket/blob/bec4232178700e53dccb887d028997f6746e91de/trio_websocket/_impl.py#L218 |
OK, I think this is the last round of comments from me:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
false alarm, the simple idea leaks cancellations from the generator into the caller (cf PEP-789) 😞
Could we add a test for that in this PR too at least |
@generator_as_channel
async def agenfn():
with trio.CancelScope() as cscope:
cscope.cancel()
yield
async def test_doesn't_leak_cancellation():
with pytest.raises(AssertionError):
async with agenfn() as recv_chan:
async for _ in recv_chan:
pass
raise AssertionError("should be reachable") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Three things below - I'm very keen to merge and thus deferred .clone()
, but I also think we need to get the core interface right the first time 😅
- The big-but-easy one is renaming the function
@as_safe_channel
; see below for why - We can drop some docs now that the interface is simpler 🎉
- Two small but important correctness changes.
(once more into the diff, dear friends!)
src/trio/_channel.py
Outdated
# TODO: should this allow clones? We'd signal that by inheriting from | ||
# MemoryReceiveChannel. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest we ship an initial version without .clone()
support, and come back later if anyone asks for it. This will already be super valuable, and I'd rather not wait any longer!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was mostly thinking if we have any strong reasons not to offer it, implementing it should be straightforward.
Though looking at the interface of MemoryReceiveChannel
we also don't have receive_nowait
on the wrapper and I'm not seeing how that one would make sense to offer - so let's stick to the interface of ReceiveChannel
…'t unwrap user exception groups, add test for multiple receivers, clean up docs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great to me!
- tiny tweaks for changelog and coverage below
- ✅ Switch from
PurePath#as_uri
toPurePath#joinpath
for our test that PurePath methods get inherited #3249 has fixed the unrelated test failures
then let's ship it!
Mostly just shepherding @Zac-HD's implementation from #638 (comment)
I don't understand all the details of it, esp some of the code paths I'm completely failing how to cover, so gonna need some help. And I'm getting an exception that sometimes disappear.. which seems bad?
Feel free to add code suggestions and/or commit directly to the branch.
I don't know if this fully resolves #638, or if there's docs and/or other stuff that should be added.
I will make https://flake8-async.readthedocs.io/en/latest/rules.html#async900 suggest using this, and perhaps make it enabled by default, once released.