Skip to content

asyncio loop does not start a task while running run_forever() #137817

@zentarim

Description

@zentarim

Bug report

Bug description:

In this example, loop.run_forever() started in a thread, while new tasks are scheduled from the main thread.

I've got a piece of test code:

import asyncio
import time
from threading import Thread


def _thread(loop: asyncio.AbstractEventLoop):
    print("Thread started")
    try:
        loop.run_forever()
    except Exception as err:
        print(f"Thread exception: {err}")
        raise
    else:
        print(f"Thread is gone")

async def task(execution_time: float):
    print(f"START {execution_time}")
    await asyncio.sleep(execution_time)
    print(f"END {execution_time}")


if __name__ == '__main__':
    loop = asyncio.new_event_loop()
    thread = Thread(target=_thread, args=(loop, ), name="asyncio loop thread", daemon=True)
    thread.start()
    loop.create_task(task(execution_time=4), name="Task 1")
    time.sleep(6)
    loop.create_task(task(execution_time=5), name="Task 2")
    time.sleep(10)
    print((asyncio.all_tasks(loop), loop.is_running()))
    print((thread, thread.is_alive()))

The "Task 2" despite being successfully scheduled well after after "Task 1" completion, never gets executed:

Thread started
START 4
END 4
({<Task pending name='Task 2' coro=<task() running at /home/user/py/test/run_loop.py:16>>}, True)
(<Thread(asyncio loop thread, started daemon 135157163235008)>, True)

From the provided output it appears that the thread is running loop.run_forever() all the time and the loop is also always running.

I'm not sure if that behavior is expected or not, because with following changes the code works as it should:

if __name__ == '__main__':
    loop = asyncio.new_event_loop()
    thread = Thread(target=_thread, args=(loop, ), name="asyncio loop thread", daemon=True)
    thread.start()
    loop.call_soon_threadsafe(lambda: loop.create_task(task(execution_time=4), name="Task 1"))
    # loop.create_task(task(execution_time=4), name="Task 1")
    time.sleep(6)
    loop.call_soon_threadsafe(lambda: loop.create_task(task(execution_time=5), name="Task 2"))
    # loop.create_task(task(execution_time=5), name="Task 2")
    time.sleep(10)
    print((asyncio.all_tasks(loop), loop.is_running()))
    print((thread, thread.is_alive()))

Output:

Thread started
START 4
END 4
START 5
END 5
(set(), True)
(<Thread(asyncio loop thread, started daemon 130959561193152)>, True)

For me, the documentation appears to be not clear enough for this particular use case:

CPython versions tested on:

3.12

Operating systems tested on:

Linux

Metadata

Metadata

Assignees

No one assigned

    Labels

    pendingThe issue will be closed if no feedback is providedtopic-asynciotype-bugAn unexpected behavior, bug, or error

    Projects

    Status

    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions