-
Notifications
You must be signed in to change notification settings - Fork 6
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
AsyncDict vs. cancellation #4
Comments
I thought of an abstraction that could have avoided at least one issue I stumbled into: # Task 1 - making the request
async with foo.expecting(request_id) as find_reply:
conn.send(request_id, data)
return await find_reply()
# Task 2 - processing incoming replies
foo.fulfil(request_id, reply_data) i.e. the |
Exactly what I was thinking, though you probably don't need an async context manager for that. |
this is supposed to be a mere dictionary, with no policy about keys if we integrate this context manager, it would need a policy about not reusing keys-- which is strange for a dictionary I wonder if this context manager can be built on top of AsyncDictionary. |
I think there is also the case where the reply never comes, e.g. if the request was lost in transit. The worst case is where a placeholder was created, the request was issued over the network, the caller was cancelled, and then the request was dropped (perhaps due to unavailability of the server). So in a simple implementation that can still result in metadata existing indefinitely. The context manager is probably best integrated into the request client, which knows details of the request lifetime and reply state. |
If the task making the request is cancelled, then leaving the The general bit I missed when trying to do this was the 'reply slot', aka what other frameworks call a future. With that, it's easy to make on top of a plain dict. |
But even when the requesting task is cancelled, the dict needs to remember that there is say an outstanding request already on the network, and to ignore the reply. I'm suggesting it's better managed by the code that is writing to the AsyncDictionary, rather than the AsyncDictionary itself. |
Why should it? The client shall be required to use the context manager, which remembers all non-cancelled requests. Thus, any reply that arrives with an unknown key can safely be ignored. In any case, the pattern I commonly use for async clients looks like this:
(Bonus points for sending cancellations to the server when a single request is cancelled, packing results in an This code doesn't need an AsyncDictionary, and in fact cannot benefit from one that doesn't have a context manager. |
I've come around to understanding the value of it, thank you 👍 I wonder if AsyncDictionary can cover it by only adding this simple replies = AsyncDictionary()
# send request
with replies.expecting(key):
await ... # some request causing `key` to eventually appear
result = await replies.pop_wait(key)
# receive loop
async for key, result in channel:
if replies.is_waiting(key):
replies[key] = result |
So I think that works, though it would be easy to forget the |
Would it make sense, rather than adding to AsyncDictionary, to define a more restrictive 'expecting' async dictionary (ExpectationManager 😛 ), which would guarantee:
|
The same is true for the receive loop checking It's attractive because it fits within the existing AsyncDictionary API, and the use is optional. (I suspect there are valid uses of AsyncDictionary without |
There is still a race here if the send task is cancelled just as the
The request code would need to delete the entry in a manual --> rename |
I wouldn't rename that. To me the semantics of Personally I'd structure the API differently:
i.e. the async context returned by I wouldn't call this class a |
I think I'm ready to give up on AsyncDictionary:
So the next question is, is a multiplexed request/reply utility a good value for trio-util? A few points causing me to not be too thrilled about it at the moment:
|
IMHO the pattern is simple enough, but as you noted the devil is in the detail and implementation requirements differ wildly. For instance, if that request was to an SQL server the first thing I'd do is to implement sending a My simple ValueEvent has the same problem: the code responsible for generating the value might want to be cancelled when the client decides that it no longer wants the result. I tried to solve that by adding an optional scope argument. While that enables a possible workaround it's still not a good fit to Structured Concurrency patterns and doesn't even try to propagate cancellations both ways. |
|
for reference
last version of use casehandle dependencies between events that arrive in random order when a dependency is missing, the event handler waits see alsopython asyncio asynchronously fetch data by key from a dict when the key becomes available |
AsyncDict doesn't adequately cover the "send a request, wait for a reply" pattern: if the waiter is cancelled before the reply arrives, that reply will languish in the dict indefinitely.
Storing the reply can't depend on
is_waiting
either, because it could arrive before theasync request
part completes.Fixing this probably requires an async context manager.
The text was updated successfully, but these errors were encountered: