How to cleanup asyncio.sleep() in a coro when te coro finishes before the sleep timeout
Question: I'am not able to cleanup the count_timer (sleep) below, when this counter is
interrupted and finished by another timer event (the stop_future).

    
    
    import asyncio
    import datetime
    from concurrent.futures import FIRST_COMPLETED
    
    DISPLAY_DT_TIMEOUT = 7
    MAX_DISPLAY_COUNT = 3
    COUNT_TIMEOUT = 4
    
    
    def stop(stop_future, display_count):
    
        print('stop', display_count, datetime.datetime.now())
        stop_future.set_result('Done!')
    
    async def display_dt1():
    
        display_count = 0
        stop_future = asyncio.Future()
        stop_handle = loop.call_later(DISPLAY_DT_TIMEOUT, stop, stop_future, display_count)
    
        # loop until
        while not stop_future.done() and display_count < MAX_DISPLAY_COUNT:
            print('dt1-1', display_count, datetime.datetime.now())
            count_timer = asyncio.sleep(COUNT_TIMEOUT)            # cleanup ??
            await asyncio.wait([count_timer, stop_future], return_when=FIRST_COMPLETED)
            print('dt-2', display_count, datetime.datetime.now())
            display_count += 1
    
        if not stop_future.done():
            # cleanup stop future
            stop_handle.cancel()
            stop_future.cancel()
    
    async def wait_before_loop_close():
    
        await asyncio.sleep(10)
    
    
    loop = asyncio.get_event_loop()
    coro = display_dt1()
    loop.run_until_complete(coro)
    
    # this print shows the count_timer is still pending
    print('tasks-1', asyncio.Task.all_tasks())
    
    # wait for the count_timer to finish     
    loop.run_until_complete(wait_before_loop_close())
    
    loop.close()
    print('tasks-2', asyncio.Task.all_tasks())
    
    print('finished', datetime.datetime.now())
    

Result:

    
    
    dt1-1 0 2015-11-10 15:20:58.200107
    dt-2 0 2015-11-10 15:21:02.201654
    dt1-1 1 2015-11-10 15:21:02.201654
    stop 0 2015-11-10 15:21:05.186800      # stop interrupt
    dt-2 1 2015-11-10 15:21:05.186800      # results in stop counting
    tasks-1 {<Task pending coro=<sleep() running at D:\Python\Python35-32\lib\asyncio\tasks.py:495> wait_for=<Future pending cb=[Task._wakeup()]>>}
    >>> tasks-2 set()
    finished 2015-11-10 15:21:15.193669
    

**Update-1: cancel count_timer (wrapped in a future)**

    
    
    async def display_dt1():
    
        count_timer_task = None
        display_count = 0
        stop_future = asyncio.Future()
        stop_handle = loop.call_later(DISPLAY_DT_TIMEOUT, stop, stop_future, display_count)
    
        while not stop_future.done() and display_count < MAX_DISPLAY_COUNT:
            print('dt1-1', display_count, datetime.datetime.now())
            count_timer = asyncio.sleep(COUNT_TIMEOUT)            # cleanup ??
            count_timer_task = asyncio.ensure_future(count_timer)
            await asyncio.wait([count_timer_task, stop_future], return_when=FIRST_COMPLETED)
            print('dt-2', display_count, datetime.datetime.now())
            display_count += 1
    
        if count_timer_task and not count_timer_task.cancelled():
            count_timer_task.cancel()
            print('check-1', datetime.datetime.now(), count_timer_task)
    
        if not stop_future.done():
            stop_handle.cancel()
            stop_future.cancel()
            print('check-2', datetime.datetime.now(), stop_future)
    

Result:

    
    
    dt1-1 0 2015-11-10 16:44:29.180372
    dt-2 0 2015-11-10 16:44:33.180908
    dt1-1 1 2015-11-10 16:44:33.180908
    stop 0 2015-11-10 16:44:36.181062
    dt-2 1 2015-11-10 16:44:36.181062
    check-1 2015-11-10 16:44:36.181062 <Task pending coro=<sleep() running at D:\Python\Python35-32\lib\asyncio\tasks.py:495> wait_for=<Future cancelled>>
    tasks-1 set()
    >>> tasks-2 set()
    finished 2015-11-10 16:44:46.181965
    


Answer: [`asyncio.wait_for`](https://docs.python.org/3/library/asyncio-
task.html#asyncio.wait_for) implements much of what you are trying to do.
Combine it with [`asyncio.shield`](https://docs.python.org/3/library/asyncio-
task.html#asyncio.shield) to prevent your Future from being cancelled when the
timeout expires:

    
    
    await asyncio.wait_for(asyncio.shield(stop_future), COUNT_TIMEOUT)
    

Edit: In case it wasn't clear, this goes in the `while` loop, replacing the
call to `asyncio.wait`.

**Update** by voscausa: showing the code

    
    
    async def display_dt1():
    
        display_count = 0
        stop_future = asyncio.Future()
        stop_handle = loop.call_later(DISPLAY_DT_TIMEOUT, stop, stop_future, display_count)
    
        while not stop_future.done() and display_count < MAX_DISPLAY_COUNT:
            print('dt-1', display_count, datetime.datetime.now())
            try:
                await asyncio.wait_for(asyncio.shield(stop_future), COUNT_TIMEOUT)
            except asyncio.TimeoutError:
                print('timeout', datetime.datetime.now())
            print('dt-2', display_count, datetime.datetime.now())
            display_count += 1
    
        if not stop_future.done():
            stop_handle.cancel()
            stop_future.cancel()
            print('check-2', datetime.datetime.now(), stop_future)
    

With:

    
    
    DISPLAY_DT_TIMEOUT = 10
    MAX_DISPLAY_COUNT = 3
    COUNT_TIMEOUT = 4
    

Result:

    
    
    dt-1 0 2015-11-10 21:43:04.549782
    timeout 2015-11-10 21:43:08.551319   # count timeout
    dt-2 0 2015-11-10 21:43:08.551319
    dt-1 1 2015-11-10 21:43:08.551319
    timeout 2015-11-10 21:43:12.552880   # count timeout
    dt-2 1 2015-11-10 21:43:12.552880
    dt-1 2 2015-11-10 21:43:12.552880
    stop 0 2015-11-10 21:43:14.554649    # stop timeout
    dt-2 2 2015-11-10 21:43:14.555650
    tasks-1 set()
    tasks-2 set()
    finished 2015-11-10 21:43:24.558510
    

