#!/usr/bin/env python """ An exemple demostrating issues with garbage collection & asyncio in Python 3.4.1 """ import asyncio import gc @asyncio.coroutine def producer(expected, do_gc): for i in range(5): yield from asyncio.sleep(0.5) print("living tasks: {} expected: {}".format( len(asyncio.Task.all_tasks()), expected)) # 1. gc collection when consumers are waiting on unreachable # futures works as expected if do_gc: gc.collect() @asyncio.coroutine def consumer(use_sleep): while True: if use_sleep: # 2. Yielding from sleep keeps the task alive: it creates a strong # ref to the from loop._scheduled` to `future.set_result`. Future # holds a strong ref to the task in its callbacks. yield from asyncio.sleep(0.1) else: # 3. Yielding from an unreachable future makes the task collectable yield from asyncio.Event().wait() # 4. ... but there is a referencee cycle here so it is not collected # right away def main(n, *, do_gc, use_sleep, initial_loops, strong_refs): # 5. Strong refs *always* keep tasks alive (of course!) tasks = [] for i in range(n): task = asyncio.async(consumer(use_sleep)) if strong_refs: tasks.append(task) # 6. gc collection at this point has no effect on sheduled tasks: their ref # is held by the loop._ready deque until they are first executed. # It only moves forward in time the automatic gc phase. if do_gc: gc.collect() # Meaningless loop to move even funrther in time the automatic gc. for i in range(initial_loops): pass print("spawned tasks: {} expected: {}".format( len(asyncio.Task.all_tasks()), n)) expected = n + 1 asyncio.get_event_loop().run_until_complete(producer(expected, do_gc)) asyncio.get_event_loop().close() # 7. No error is logged when tasks are garbage collected without ever being # completed even with PYTHONASYNCIODEBUG=1 if __name__ == '__main__': import argparse parser = argparse.ArgumentParser() parser.add_argument('-n', type=int, default=100000) parser.add_argument('--gc', action='store_true') parser.add_argument('--sleep', action='store_true') parser.add_argument('--strong', action='store_true') parser.add_argument('--initial_loops', type=int, default=0) args = parser.parse_args() main(args.n, do_gc=args.gc, use_sleep=args.sleep, initial_loops=args.initial_loops, strong_refs=args.strong)