Uh oh!
There was an error while loading. Please reload this page.
Fix response clean up - #704
Conversation
f27697c to
40c126cComparenotonamap
commented
Jun 1, 2023
Stumbled across this too today. Would be great to get this fixed! |
karpetrosyan
commented
Jun 7, 2023
Our problemhttpcore/_async/http11.py asyncdef_response_closed(self) ->None:
asyncwithself._state_lock:
if (
self._h11_state.our_stateish11.DONEandself._h11_state.their_stateish11.DONE
):
self._state=HTTPConnectionState.IDLEself._h11_state.start_next_cycle()
ifself._keepalive_expiryisnotNone:
now=time.monotonic()
self._expire_at=now+self._keepalive_expiryelse:
awaitself.aclose()Let's cancel this coroutine in the middle of its execution. Oops, now we have a problem: our connection is in the ACTIVE state when it should be in the IDLE or CLOSED state, and our file descriptor was not closed because But wait, we were smart enough and put finally block for those cases, now the finally block should be executed in asyncdefaclose(self) ->None:
try:
ifhasattr(self._stream, "aclose"):
awaitself._stream.aclose()
finally:
awaitself._pool.response_closed(self._status)Unfortunately, our Simple example importtrioasyncdefcheckpoint():
awaittrio.sleep(0)
asyncdefclean_up():
awaitcheckpoint() # in our case: async with self._pool_lock:print('done')
asyncdefmain():
withtrio.move_on_after(1):
try:
awaittrio.sleep(1.1)
finally:
awaitclean_up()
trio.run(main)In this case, the word 'done' will never be printed. This code will function properly. asyncdefmain():
withtrio.move_on_after(1):
try:
awaittrio.sleep(1.1)
finally:
withtrio.CancelScope(shield=True):
awaitclean_up()The same solution was used to solve our issue; I simply wrapped the clean up function in "CancelScope(shielf=True)" |
karpetrosyan
commented
Jun 7, 2023
What should we pay attention to?If the clean up function, which we call in the cancel isolated environment, hangs, that's bad; nursery also hangs forever, so we need to avoid that. How?
I chose the second option in this solution because we know our clean up function will work as expected. What are the guarantees?Let's take a look at our cleanup function, which consists of three lines. ifcancelledandhasattr(self._stream, "_connection"): # pragma: no coverawaitself._stream._connection.aclose()
awaitself._pool.response_closed(self._status)Because we know that synchronous "if" statements do not block, let's look at What asyncdefaclose(self) ->None:
# Note that this method unilaterally closes the connection, and does# not have any kind of locking in place around it.self._state=HTTPConnectionState.CLOSEDawaitself._network_stream.aclose()So it simply sets the connection state to CLOSED and attempts to close the network stream. According to https://trio.readthedocs.io/en/stable/reference-io.html?highlight=AsyncResource#trio.abc.AsyncResource, we can be certain that What |
karpetrosyan
commented
Jun 7, 2023
This PR also resolves the issue described in #658 (comment). |
lovelydinosaur
commented
Jun 7, 2023
I'm starting to understand this more clearly, thanks. Let's get this worked through. 💪 Rather than adding API to the network backends interface, I think we want to treat this in the same kind of way that we handle async locking and semaphores. Currently we have the following primitives available to us there...
As well as the corresponding...
It seems to me that we ought to mirror the Does that seem like a reasonable suggestion to you? |
karpetrosyan
commented
Jun 7, 2023
Is it necessary to create a |
karpetrosyan
commented
Jun 7, 2023
I agree with you; I believe it is more intuitive to combine this with synchronizations. |
lovelydinosaur
commented
Jun 7, 2023
I'm not sure if we'd be able to do that easily because of the indentation. A different way to approach this would be to...
Once we've review that we think we have the correct set of changes in place based on that, then we can work out the implementation details of how to make that happen. |
karpetrosyan
commented
Jun 7, 2023
We can simply encapsulate the cleanup logic in the special function "X" that should be marked with the decorator, and then write a regex to remove that decorator. possible regex (r"\s*AsyncShield", '') |
lovelydinosaur
commented
Jun 7, 2023
Are we able to resolve this with a decorator syntax, or do we need a context-manager syntax? |
karpetrosyan
commented
Jun 7, 2023
I am offer to use decorator, because it's easy to remove with the regex, which we can not say about context managers. |
lovelydinosaur
commented
Jun 7, 2023
Okay, so supposing we have a |
lovelydinosaur
commented
Jun 7, 2023
(I'm not sure this is viable, since the function call would need to be async, which makes it a potential switchpoint?) |
karpetrosyan
commented
Jun 7, 2023
Is it enough to be a switchpoint? Can't we rely on that function? |
karpetrosyan
commented
Jun 7, 2023
I misunderstood how 'aclose' for 'trio.abc.AsyncResource' works; we should most likely use timeouts. |
lovelydinosaur
commented
Jun 7, 2023
I don't think? we can take that approach, because... https://trio.readthedocs.io/en/stable/reference-core.html#checkpoints "Third-party async functions / iterators / context managers can act as checkpoints" So if we were calling into a decorated My go-to with something like this would typically be "trio gets this just right. how would this look if we're using trio" and to work from there. |
karpetrosyan
commented
Jun 7, 2023
I think I'm missing something; isn't this decorator safe? @staticmethoddefshield_cancellation(
fnc: typing.Callable[..., typing.Awaitable[_R]]
) ->typing.Callable[..., typing.Awaitable[_R]]:
# Makes an async function that runs in a cancellation-isolated environment.@wraps(fnc)asyncdefinner(*args: typing.Any, **kwargs: typing.Any) ->_R:
withanyio.CancelScope(shield=True):
returnawaitfnc(*args, **kwargs)
returninner |
| from .connection import AsyncHTTPConnection | ||
| from .interfaces import AsyncConnectionInterface, AsyncRequestInterface | ||
| GRACEFUL_CLOSE_TIMEOUT = 2 |
There was a problem hiding this comment.
Instead of two, we should probably think about which timeout to use here.
Lets just avoid removing the decorator from the synchronous interface because it does not interfrare us. |
lovelydinosaur
commented
Jun 12, 2023
manager hat on Okay, lovely stuff tho let's slow this down... You've given a great example over here. I think that's the first time I've seen a nice simple "look, we're not supporting cancellation at the moment". My suggestion would be...
|
karpetrosyan
commented
Jun 12, 2023
Do we need to open another PR and close this one? |
Up to you. (🤷♂️ Might be a little cleaner that way???) |
karpetrosyan
commented
Jun 12, 2023
Okay, I'll do it for clarity. |
karpetrosyan
commented
Jun 12, 2023
Was re-opened in #719 |
closes#642 and #658