what happens to uniterated async iterators?











up vote
3
down vote

favorite
1












Say I have the following function



async def f1():
async for item in asynciterator():
return


What happens to the async iterator after



await f1()


? Should I worry about cleaning up or will the generator be somehow garbage collected when it goes out of sight?










share|improve this question
























  • A guess: f1() returns the coroutine, which is just a callable object on the heap, including the function's frame (local variables etc.) Therefore, garbage collection should clean it up just fine. In this case you don't want f1 to hold any external resources like file handles though
    – Felk
    Nov 8 at 17:46












  • how about the asynciterator. In the asynciterator I am using aiohttp session as a context manager to perform a get. after I do the get, I parse the body and yield items from the body. Should I release the aiohttp session as soon as I receive the http response and before starting to parse and yield items from it ?
    – Liviu
    Nov 8 at 18:01










  • Related: loop.shutdown_asyncgens and sys.set_asyncgen_hooks
    – Vincent
    Nov 9 at 13:18












  • More precisely, when an async generator is about to get garbage collected, asyncio schedules the agen.aclose() coroutine.
    – Vincent
    Nov 9 at 13:30















up vote
3
down vote

favorite
1












Say I have the following function



async def f1():
async for item in asynciterator():
return


What happens to the async iterator after



await f1()


? Should I worry about cleaning up or will the generator be somehow garbage collected when it goes out of sight?










share|improve this question
























  • A guess: f1() returns the coroutine, which is just a callable object on the heap, including the function's frame (local variables etc.) Therefore, garbage collection should clean it up just fine. In this case you don't want f1 to hold any external resources like file handles though
    – Felk
    Nov 8 at 17:46












  • how about the asynciterator. In the asynciterator I am using aiohttp session as a context manager to perform a get. after I do the get, I parse the body and yield items from the body. Should I release the aiohttp session as soon as I receive the http response and before starting to parse and yield items from it ?
    – Liviu
    Nov 8 at 18:01










  • Related: loop.shutdown_asyncgens and sys.set_asyncgen_hooks
    – Vincent
    Nov 9 at 13:18












  • More precisely, when an async generator is about to get garbage collected, asyncio schedules the agen.aclose() coroutine.
    – Vincent
    Nov 9 at 13:30













up vote
3
down vote

favorite
1









up vote
3
down vote

favorite
1






1





Say I have the following function



async def f1():
async for item in asynciterator():
return


What happens to the async iterator after



await f1()


? Should I worry about cleaning up or will the generator be somehow garbage collected when it goes out of sight?










share|improve this question















Say I have the following function



async def f1():
async for item in asynciterator():
return


What happens to the async iterator after



await f1()


? Should I worry about cleaning up or will the generator be somehow garbage collected when it goes out of sight?







python iterator python-asyncio async-iterator






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Nov 8 at 17:40

























asked Nov 8 at 14:48









Liviu

3451517




3451517












  • A guess: f1() returns the coroutine, which is just a callable object on the heap, including the function's frame (local variables etc.) Therefore, garbage collection should clean it up just fine. In this case you don't want f1 to hold any external resources like file handles though
    – Felk
    Nov 8 at 17:46












  • how about the asynciterator. In the asynciterator I am using aiohttp session as a context manager to perform a get. after I do the get, I parse the body and yield items from the body. Should I release the aiohttp session as soon as I receive the http response and before starting to parse and yield items from it ?
    – Liviu
    Nov 8 at 18:01










  • Related: loop.shutdown_asyncgens and sys.set_asyncgen_hooks
    – Vincent
    Nov 9 at 13:18












  • More precisely, when an async generator is about to get garbage collected, asyncio schedules the agen.aclose() coroutine.
    – Vincent
    Nov 9 at 13:30


















  • A guess: f1() returns the coroutine, which is just a callable object on the heap, including the function's frame (local variables etc.) Therefore, garbage collection should clean it up just fine. In this case you don't want f1 to hold any external resources like file handles though
    – Felk
    Nov 8 at 17:46












  • how about the asynciterator. In the asynciterator I am using aiohttp session as a context manager to perform a get. after I do the get, I parse the body and yield items from the body. Should I release the aiohttp session as soon as I receive the http response and before starting to parse and yield items from it ?
    – Liviu
    Nov 8 at 18:01










  • Related: loop.shutdown_asyncgens and sys.set_asyncgen_hooks
    – Vincent
    Nov 9 at 13:18












  • More precisely, when an async generator is about to get garbage collected, asyncio schedules the agen.aclose() coroutine.
    – Vincent
    Nov 9 at 13:30
















A guess: f1() returns the coroutine, which is just a callable object on the heap, including the function's frame (local variables etc.) Therefore, garbage collection should clean it up just fine. In this case you don't want f1 to hold any external resources like file handles though
– Felk
Nov 8 at 17:46






A guess: f1() returns the coroutine, which is just a callable object on the heap, including the function's frame (local variables etc.) Therefore, garbage collection should clean it up just fine. In this case you don't want f1 to hold any external resources like file handles though
– Felk
Nov 8 at 17:46














how about the asynciterator. In the asynciterator I am using aiohttp session as a context manager to perform a get. after I do the get, I parse the body and yield items from the body. Should I release the aiohttp session as soon as I receive the http response and before starting to parse and yield items from it ?
– Liviu
Nov 8 at 18:01




how about the asynciterator. In the asynciterator I am using aiohttp session as a context manager to perform a get. after I do the get, I parse the body and yield items from the body. Should I release the aiohttp session as soon as I receive the http response and before starting to parse and yield items from it ?
– Liviu
Nov 8 at 18:01












Related: loop.shutdown_asyncgens and sys.set_asyncgen_hooks
– Vincent
Nov 9 at 13:18






Related: loop.shutdown_asyncgens and sys.set_asyncgen_hooks
– Vincent
Nov 9 at 13:18














More precisely, when an async generator is about to get garbage collected, asyncio schedules the agen.aclose() coroutine.
– Vincent
Nov 9 at 13:30




More precisely, when an async generator is about to get garbage collected, asyncio schedules the agen.aclose() coroutine.
– Vincent
Nov 9 at 13:30












1 Answer
1






active

oldest

votes

















up vote
0
down vote



accepted











Should I worry about cleaning up or will the generator be somehow garbage collected when it goes out of sight?




TL;DR Python's gc and asyncio will ensure eventual cleanup of incompletely iterated async generators.



"Cleanup" here refers to running the code specified by a finally around the yield, or by the __aexit__ part of the context manager used in a with statement around the yield. For example, the print in this simple generator is invoked by the same mechanism used by a aiohttp.ClientSession to close its resources:



async def my_gen():
try:
yield 1
yield 2
yield 3
finally:
await asyncio.sleep(0.1) # make it interesting by awaiting
print('cleaned up')


If you run a coroutine that iterates through the whole generator, the cleanup will be executed immediately:



>>> async def test():
... gen = my_gen()
... async for _ in gen:
... pass
... print('test done')
...
>>> asyncio.get_event_loop().run_until_complete(test())
cleaned up
test done


Note how the cleanup is executed immediately after the loop, even though the generator was still in scope without the chance to get garbage collected. This is because the async for loop ensures the async generator cleanup on loop exhaustion.



The question is what happens when the loop is not exhausted:



>>> async def test():
... gen = my_gen()
... async for _ in gen:
... break # exit at once
... print('test done')
...
>>> asyncio.get_event_loop().run_until_complete(test())
test done


Here gen got out of scope, but the cleanup simply didn't occur. If you tried this with an ordinary generator, the cleanup would get called by the reference countered immediately (though still after the exit from test, because that's when the running generator is no longer referred to), this being possible because gen does not participate in a cycle:



>>> def my_gen():
... try:
... yield 1
... yield 2
... yield 3
... finally:
... print('cleaned up')
...
>>> def test():
... gen = my_gen()
... for _ in gen:
... break
... print('test done')
...
>>> test()
test done
cleaned up


With my_gen being an asynchronous generator, its cleanup is asynchronous as well. This means it can't just be executed by the garbage collector, it needs to be run by an event loop. To make this possible, asyncio registers the asyncgen finalizer hook, but it never gets a chance to execute because we're using run_until_complete which stops the loop immediately after executing a coroutine.



If we tried to spin the same event loop some more, we'd see the cleanup executed:



>>> asyncio.get_event_loop().run_until_complete(asyncio.sleep(0))
cleaned up


In a normal asyncio application this does not lead to problems because the event loop typically runs as long as the application. If there is no event loop to clean up the async generators, it likely means the process is exiting anyway.






share|improve this answer























    Your Answer






    StackExchange.ifUsing("editor", function () {
    StackExchange.using("externalEditor", function () {
    StackExchange.using("snippets", function () {
    StackExchange.snippets.init();
    });
    });
    }, "code-snippets");

    StackExchange.ready(function() {
    var channelOptions = {
    tags: "".split(" "),
    id: "1"
    };
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function() {
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled) {
    StackExchange.using("snippets", function() {
    createEditor();
    });
    }
    else {
    createEditor();
    }
    });

    function createEditor() {
    StackExchange.prepareEditor({
    heartbeatType: 'answer',
    convertImagesToLinks: true,
    noModals: true,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: 10,
    bindNavPrevention: true,
    postfix: "",
    imageUploader: {
    brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
    contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
    allowUrls: true
    },
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    });


    }
    });














     

    draft saved


    draft discarded


















    StackExchange.ready(
    function () {
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53210140%2fwhat-happens-to-uniterated-async-iterators%23new-answer', 'question_page');
    }
    );

    Post as a guest















    Required, but never shown

























    1 Answer
    1






    active

    oldest

    votes








    1 Answer
    1






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes








    up vote
    0
    down vote



    accepted











    Should I worry about cleaning up or will the generator be somehow garbage collected when it goes out of sight?




    TL;DR Python's gc and asyncio will ensure eventual cleanup of incompletely iterated async generators.



    "Cleanup" here refers to running the code specified by a finally around the yield, or by the __aexit__ part of the context manager used in a with statement around the yield. For example, the print in this simple generator is invoked by the same mechanism used by a aiohttp.ClientSession to close its resources:



    async def my_gen():
    try:
    yield 1
    yield 2
    yield 3
    finally:
    await asyncio.sleep(0.1) # make it interesting by awaiting
    print('cleaned up')


    If you run a coroutine that iterates through the whole generator, the cleanup will be executed immediately:



    >>> async def test():
    ... gen = my_gen()
    ... async for _ in gen:
    ... pass
    ... print('test done')
    ...
    >>> asyncio.get_event_loop().run_until_complete(test())
    cleaned up
    test done


    Note how the cleanup is executed immediately after the loop, even though the generator was still in scope without the chance to get garbage collected. This is because the async for loop ensures the async generator cleanup on loop exhaustion.



    The question is what happens when the loop is not exhausted:



    >>> async def test():
    ... gen = my_gen()
    ... async for _ in gen:
    ... break # exit at once
    ... print('test done')
    ...
    >>> asyncio.get_event_loop().run_until_complete(test())
    test done


    Here gen got out of scope, but the cleanup simply didn't occur. If you tried this with an ordinary generator, the cleanup would get called by the reference countered immediately (though still after the exit from test, because that's when the running generator is no longer referred to), this being possible because gen does not participate in a cycle:



    >>> def my_gen():
    ... try:
    ... yield 1
    ... yield 2
    ... yield 3
    ... finally:
    ... print('cleaned up')
    ...
    >>> def test():
    ... gen = my_gen()
    ... for _ in gen:
    ... break
    ... print('test done')
    ...
    >>> test()
    test done
    cleaned up


    With my_gen being an asynchronous generator, its cleanup is asynchronous as well. This means it can't just be executed by the garbage collector, it needs to be run by an event loop. To make this possible, asyncio registers the asyncgen finalizer hook, but it never gets a chance to execute because we're using run_until_complete which stops the loop immediately after executing a coroutine.



    If we tried to spin the same event loop some more, we'd see the cleanup executed:



    >>> asyncio.get_event_loop().run_until_complete(asyncio.sleep(0))
    cleaned up


    In a normal asyncio application this does not lead to problems because the event loop typically runs as long as the application. If there is no event loop to clean up the async generators, it likely means the process is exiting anyway.






    share|improve this answer



























      up vote
      0
      down vote



      accepted











      Should I worry about cleaning up or will the generator be somehow garbage collected when it goes out of sight?




      TL;DR Python's gc and asyncio will ensure eventual cleanup of incompletely iterated async generators.



      "Cleanup" here refers to running the code specified by a finally around the yield, or by the __aexit__ part of the context manager used in a with statement around the yield. For example, the print in this simple generator is invoked by the same mechanism used by a aiohttp.ClientSession to close its resources:



      async def my_gen():
      try:
      yield 1
      yield 2
      yield 3
      finally:
      await asyncio.sleep(0.1) # make it interesting by awaiting
      print('cleaned up')


      If you run a coroutine that iterates through the whole generator, the cleanup will be executed immediately:



      >>> async def test():
      ... gen = my_gen()
      ... async for _ in gen:
      ... pass
      ... print('test done')
      ...
      >>> asyncio.get_event_loop().run_until_complete(test())
      cleaned up
      test done


      Note how the cleanup is executed immediately after the loop, even though the generator was still in scope without the chance to get garbage collected. This is because the async for loop ensures the async generator cleanup on loop exhaustion.



      The question is what happens when the loop is not exhausted:



      >>> async def test():
      ... gen = my_gen()
      ... async for _ in gen:
      ... break # exit at once
      ... print('test done')
      ...
      >>> asyncio.get_event_loop().run_until_complete(test())
      test done


      Here gen got out of scope, but the cleanup simply didn't occur. If you tried this with an ordinary generator, the cleanup would get called by the reference countered immediately (though still after the exit from test, because that's when the running generator is no longer referred to), this being possible because gen does not participate in a cycle:



      >>> def my_gen():
      ... try:
      ... yield 1
      ... yield 2
      ... yield 3
      ... finally:
      ... print('cleaned up')
      ...
      >>> def test():
      ... gen = my_gen()
      ... for _ in gen:
      ... break
      ... print('test done')
      ...
      >>> test()
      test done
      cleaned up


      With my_gen being an asynchronous generator, its cleanup is asynchronous as well. This means it can't just be executed by the garbage collector, it needs to be run by an event loop. To make this possible, asyncio registers the asyncgen finalizer hook, but it never gets a chance to execute because we're using run_until_complete which stops the loop immediately after executing a coroutine.



      If we tried to spin the same event loop some more, we'd see the cleanup executed:



      >>> asyncio.get_event_loop().run_until_complete(asyncio.sleep(0))
      cleaned up


      In a normal asyncio application this does not lead to problems because the event loop typically runs as long as the application. If there is no event loop to clean up the async generators, it likely means the process is exiting anyway.






      share|improve this answer

























        up vote
        0
        down vote



        accepted







        up vote
        0
        down vote



        accepted







        Should I worry about cleaning up or will the generator be somehow garbage collected when it goes out of sight?




        TL;DR Python's gc and asyncio will ensure eventual cleanup of incompletely iterated async generators.



        "Cleanup" here refers to running the code specified by a finally around the yield, or by the __aexit__ part of the context manager used in a with statement around the yield. For example, the print in this simple generator is invoked by the same mechanism used by a aiohttp.ClientSession to close its resources:



        async def my_gen():
        try:
        yield 1
        yield 2
        yield 3
        finally:
        await asyncio.sleep(0.1) # make it interesting by awaiting
        print('cleaned up')


        If you run a coroutine that iterates through the whole generator, the cleanup will be executed immediately:



        >>> async def test():
        ... gen = my_gen()
        ... async for _ in gen:
        ... pass
        ... print('test done')
        ...
        >>> asyncio.get_event_loop().run_until_complete(test())
        cleaned up
        test done


        Note how the cleanup is executed immediately after the loop, even though the generator was still in scope without the chance to get garbage collected. This is because the async for loop ensures the async generator cleanup on loop exhaustion.



        The question is what happens when the loop is not exhausted:



        >>> async def test():
        ... gen = my_gen()
        ... async for _ in gen:
        ... break # exit at once
        ... print('test done')
        ...
        >>> asyncio.get_event_loop().run_until_complete(test())
        test done


        Here gen got out of scope, but the cleanup simply didn't occur. If you tried this with an ordinary generator, the cleanup would get called by the reference countered immediately (though still after the exit from test, because that's when the running generator is no longer referred to), this being possible because gen does not participate in a cycle:



        >>> def my_gen():
        ... try:
        ... yield 1
        ... yield 2
        ... yield 3
        ... finally:
        ... print('cleaned up')
        ...
        >>> def test():
        ... gen = my_gen()
        ... for _ in gen:
        ... break
        ... print('test done')
        ...
        >>> test()
        test done
        cleaned up


        With my_gen being an asynchronous generator, its cleanup is asynchronous as well. This means it can't just be executed by the garbage collector, it needs to be run by an event loop. To make this possible, asyncio registers the asyncgen finalizer hook, but it never gets a chance to execute because we're using run_until_complete which stops the loop immediately after executing a coroutine.



        If we tried to spin the same event loop some more, we'd see the cleanup executed:



        >>> asyncio.get_event_loop().run_until_complete(asyncio.sleep(0))
        cleaned up


        In a normal asyncio application this does not lead to problems because the event loop typically runs as long as the application. If there is no event loop to clean up the async generators, it likely means the process is exiting anyway.






        share|improve this answer















        Should I worry about cleaning up or will the generator be somehow garbage collected when it goes out of sight?




        TL;DR Python's gc and asyncio will ensure eventual cleanup of incompletely iterated async generators.



        "Cleanup" here refers to running the code specified by a finally around the yield, or by the __aexit__ part of the context manager used in a with statement around the yield. For example, the print in this simple generator is invoked by the same mechanism used by a aiohttp.ClientSession to close its resources:



        async def my_gen():
        try:
        yield 1
        yield 2
        yield 3
        finally:
        await asyncio.sleep(0.1) # make it interesting by awaiting
        print('cleaned up')


        If you run a coroutine that iterates through the whole generator, the cleanup will be executed immediately:



        >>> async def test():
        ... gen = my_gen()
        ... async for _ in gen:
        ... pass
        ... print('test done')
        ...
        >>> asyncio.get_event_loop().run_until_complete(test())
        cleaned up
        test done


        Note how the cleanup is executed immediately after the loop, even though the generator was still in scope without the chance to get garbage collected. This is because the async for loop ensures the async generator cleanup on loop exhaustion.



        The question is what happens when the loop is not exhausted:



        >>> async def test():
        ... gen = my_gen()
        ... async for _ in gen:
        ... break # exit at once
        ... print('test done')
        ...
        >>> asyncio.get_event_loop().run_until_complete(test())
        test done


        Here gen got out of scope, but the cleanup simply didn't occur. If you tried this with an ordinary generator, the cleanup would get called by the reference countered immediately (though still after the exit from test, because that's when the running generator is no longer referred to), this being possible because gen does not participate in a cycle:



        >>> def my_gen():
        ... try:
        ... yield 1
        ... yield 2
        ... yield 3
        ... finally:
        ... print('cleaned up')
        ...
        >>> def test():
        ... gen = my_gen()
        ... for _ in gen:
        ... break
        ... print('test done')
        ...
        >>> test()
        test done
        cleaned up


        With my_gen being an asynchronous generator, its cleanup is asynchronous as well. This means it can't just be executed by the garbage collector, it needs to be run by an event loop. To make this possible, asyncio registers the asyncgen finalizer hook, but it never gets a chance to execute because we're using run_until_complete which stops the loop immediately after executing a coroutine.



        If we tried to spin the same event loop some more, we'd see the cleanup executed:



        >>> asyncio.get_event_loop().run_until_complete(asyncio.sleep(0))
        cleaned up


        In a normal asyncio application this does not lead to problems because the event loop typically runs as long as the application. If there is no event loop to clean up the async generators, it likely means the process is exiting anyway.







        share|improve this answer














        share|improve this answer



        share|improve this answer








        edited Nov 10 at 20:41

























        answered Nov 10 at 16:57









        user4815162342

        58.6k488138




        58.6k488138






























             

            draft saved


            draft discarded



















































             


            draft saved


            draft discarded














            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53210140%2fwhat-happens-to-uniterated-async-iterators%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown





















































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown

































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown







            Popular posts from this blog

            Florida Star v. B. J. F.

            Danny Elfman

            Lugert, Oklahoma