[Async-sig] Asynchronous cleanup is a problem

Nathaniel Smith njs at pobox.com
Tue Jul 5 20:56:50 EDT 2016


So here's an interesting issue I just discovered while experimenting
with async generators. It caught me by surprise, anyway. Maybe this
was already obvious to everyone else. But I wanted to get some more
perspectives.

Starting axiom: async functions / async generators need to be prepared
for the case where they get garbage collected before being run to
completion, because, well... this is a thing that can happen.

Currently, the way garbage collection is handled is that their __del__
method calls their .close() method, which does something like:

class GeneratorType:
    ...
    def close(self):
        try:
            self.throw(GeneratorExit)
        except GeneratorExit, StopIteration:
            return  # it worked, all is good
        except:
            raise  # double-fault, propagate
        else:
            raise RuntimeError("generator ignored GeneratorExit")

(see PEP 342).

So far, so obvious -- an async function that gets a GeneratorExit has
to propagate that exception immediately. This is a regular method, not
an async method, because it

Dismaying conclusion: inside an async function / async generator,
finally: blocks must never yield (and neither can __aexit__
callbacks), because they might be invoked during garbage collection.

For async functions this is... arguably a problem but not super
urgent, because async functions rarely get garbage collected without
being run to completion. For async generators it's a much bigger
problem.

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

-n

-- 
Nathaniel J. Smith -- https://vorpus.org


More information about the Async-sig mailing list