> On 6 Jul 2016, at 01:56, Nathaniel Smith <n...@pobox.com> wrote:
> 
> There's some more discussion, and a first sketch at conventions we
> might want to use for handling this, here:
> 
>    https://github.com/dabeaz/curio/issues/70

This feels like a problem with very few good solutions.

Finalizers (e.g. __del__ and weakref callbacks) obviously cannot await for 
anything (they have no coroutine runner to yield to, and there is currently no 
Python API for handing off to a coroutine runner to execute your finalizer). 
That strongly suggests that all cleanup inside coroutines and async generators 
must be synchronous. This is especially problematic given the existence of 
“async with”, which nominally promises to do asynchronous cleanup.

What’s not entirely clear to me is why we need __aexit__ to *actually* be an 
async function. The example in curio is socket closure, which seems to be like 
it absolutely does not need to be awaitable. Why can’t close() just tell the 
event loop (or curio kernel) that I’m done with the socket, and to clean up 
that socket on its own time (again, this is what Twisted does).

In the worst case you could rule that __aexit__ cannot use coroutines, but of 
course it may continue to use Futures and other fun things. I know that 
callbacks aren’t The Asyncio Way, but they have their uses, and this is 
probably one of them. Allowing Futures/Deferreds allows your __aexit__ to do 
some actual I/O if that’s required (e.g. send a HTTP/2 GOAWAY frame).

The only reason I can think of that __aexit__ needs to be a coroutine is to 
guarantee that the resource in question is genuinely cleaned up by the time the 
with block is exited. It is not entirely clear to me what the value of this 
guarantee is: does anyone have a good use-case for it that doesn’t seem like it 
violates the spirit of the context manager?

That leaves us with finally, and here I have no good solution except that 
finally inside generators has *always* been a problem. However, in this case, I 
think we’ve got a good analogy. In synchronous generators, if you yield inside 
a finally *and* leak your coroutine, the interpreter moans at you. I don’t see 
any reason not to treat await/yield from inside finally in exactly the same 
way: if you do it, you eventually explode.

Basically, there doesn’t seem to be an obvious way to make garbage collection 
of coroutines work while allowing them to be coroutines without having some way 
to register a coroutine runner with the garbage collector. That strikes me as a 
*terrible* idea (e.g. you may have multiple event loops, which requires that 
you register a unique coroutine runner per coroutine). Therefore, the only 
logical thing to do is to have only synchronous functions invoked in cleanup 
methods (e.g. __aexit__ and finally), and if those need to do some form of 
asynchronous I/O they need to use a Future-like construct to actually achieve 
it.

Cory

Attachment: signature.asc
Description: Message signed with OpenPGP using GPGMail

_______________________________________________
Async-sig mailing list
Async-sig@python.org
https://mail.python.org/mailman/listinfo/async-sig
Code of Conduct: https://www.python.org/psf/codeofconduct/

Reply via email to