RSafe
RSafe

Reputation: 73

RuntimeError: Event loop is closed after main coroutine is completed

I've checked all similar questions and suggestions, including this one, but hadn't found a working solution.

I'm developing a telegram bot. If I test it manually, it works as expected. But my code raise an exception during automated testing after main coroutine is completed.

My simplified code:

async def handle_incoming_message(event):
    # I replaced content of this coroutine with pseudocode for conciseness
    initiate database connection
    handling of event.message.message
    interacting with database and third-party API
    respond to event and logging
    close database connection


async def main():
    bot = await (TelegramClient(BOT_NAME, API_ID, API_HASH)).start(bot_token=BOT_TOKEN)
    me = TelegramClient(MY_ACCOUNT_NAME, API_ID, API_HASH)
    async with bot, me:
        bot.add_event_handler(handle_incoming_message, events.NewMessage(incoming=True))
        # It's helper to handle user messages through console
        message = await me.send_message(BOT_NAME, input(MESSAGE_PROMPT))
        logging.warning("I sent: '%s'", message.message)
        # added to escape of ConnectionError: Cannot send requests while disconnected
        await asyncio.sleep(5)


if __name__ == '__main__':
    # new_event_loop() and set_event_loop(loop) are used to prevent an error with loop = 
    # asyncio.get_event_loop()
    # RuntimeError: There is no current event loop in thread 'MainModuleExecutor test 
    #1_0'.
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)
    loop.run_until_complete(main())
    loop.close()

I'm using testing library hs-test-python.

Test output:

Start test 1
database is up-to-date
Type a message:> skjd
04/18/2022 01:04:31PM I sent: 'skjd'
04/18/2022 01:04:32PM Status: '404'
04/18/2022 01:04:33PM Bot responded: 'No definitions found for a word: 'skjd''

Start test 2
Type a message:> soul
04/18/2022 01:04:40PM I sent: 'soul'
04/18/2022 01:04:40PM Bot responded: 'noun
1. definition: The spirit or essence of a person usually thought to consist of one's thoughts and personality. Often believed to live on after the person's death.
2. definition: The spirit or essence of anything.
3. definition: Life, energy, vigor.
4. definition: Soul music.
5. definition: A person, especially as one among many.
6. definition: An individual life.
example: Fifty souls were lost when the ship sank.
7. definition: A kind of submanifold involved in the soul theorem of Riemannian geometry.'
04/18/2022 01:04:40PM Bot responded: 'verb
1. definition: To afford suitable sustenance.'
04/18/2022 01:04:41PM Bot responded: 'soul'
04/18/2022 01:04:41PM Bot responded: '/soʊl/ https://api.dictionaryapi.dev/media/pronunciations/en/soul-1-us.mp3'
04/18/2022 01:04:41PM Bot responded: '/səʊl/'

# The output above is expected behaviour of my programme.
# But instead of success message "tests passed", I receive following:

#educational_plugin FAILED + Exception in test #2
#educational_plugin 
#educational_plugin Traceback (most recent call last):
#educational_plugin   File " File "/home/roman/PycharmProjects/telegram_bot/telegram_bot/stage3/bot.py", line 120, in <module>
#educational_plugin     loop.run_until_complete(main())
#educational_plugin asyncio.exceptions.CancelledError
#educational_plugin 
#educational_plugin Please find below the output of your program during this failed test.
#educational_plugin Note that the '>' character indicates the beginning of the input line.
#educational_plugin 
#educational_plugin ---
#educational_plugin 
#educational_plugin Type a message:> soul
#educational_plugin 04/18/2022 01:04:40PM I sent: 'soul'
#educational_plugin 04/18/2022 01:04:40PM Bot responded: 'noun
#educational_plugin 1. definition: The spirit or essence of a person usually thought to consist of one's thoughts and personality. Often believed to live on after the person's death.
#educational_plugin 2. definition: The spirit or essence of anything.
#educational_plugin 3. definition: Life, energy, vigor.
#educational_plugin 4. definition: Soul music.
#educational_plugin 5. definition: A person, especially as one among many.
#educational_plugin 6. definition: An individual life.
#educational_plugin example: Fifty souls were lost when the ship sank.
#educational_plugin 7. definition: A kind of submanifold involved in the soul theorem of Riemannian geometry.'
#educational_plugin 04/18/2022 01:04:40PM Bot responded: 'verb
#educational_plugin 1. definition: To afford suitable sustenance.'
#educational_plugin 04/18/2022 01:04:41PM Bot responded: 'soul'
#educational_plugin 04/18/2022 01:04:41PM Bot responded: '/soʊl/ https://api.dictionaryapi.dev/media/pronunciations/en/soul-1-us.mp3'
#educational_plugin 04/18/2022 01:04:41PM Bot responded: '/səʊl/'
Exception ignored in: <coroutine object MTProtoSender._recv_loop at 0x7fe3e914ea40>
Traceback (most recent call last):
  File "/home/roman/PycharmProjects/telegram_bot/.idea/VirtualEnvironment/lib/python3.8/site-packages/telethon/network/mtprotosender.py", line 503, in _recv_loop
    body = await self._connection.recv()
  File "/home/roman/PycharmProjects/telegram_bot/.idea/VirtualEnvironment/lib/python3.8/site-packages/telethon/network/connection/connection.py", line 294, in recv
    result = await self._recv_queue.get()
  File "/usr/lib/python3.8/asyncio/queues.py", line 165, in get
    getter.cancel()  # Just in case getter is not done yet.
  File "/usr/lib/python3.8/asyncio/base_events.py", line 719, in call_soon
    self._check_closed()
  File "/usr/lib/python3.8/asyncio/base_events.py", line 508, in _check_closed
    raise RuntimeError('Event loop is closed')
RuntimeError: Event loop is closed
Exception ignored in: <coroutine object MTProtoSender._recv_loop at 0x7fe3e925bac0>
Traceback (most recent call last):
  File "/home/roman/PycharmProjects/telegram_bot/.idea/VirtualEnvironment/lib/python3.8/site-packages/telethon/network/mtprotosender.py", line 503, in _recv_loop
    body = await self._connection.recv()
  File "/home/roman/PycharmProjects/telegram_bot/.idea/VirtualEnvironment/lib/python3.8/site-packages/telethon/network/connection/connection.py", line 294, in recv
    result = await self._recv_queue.get()
  File "/usr/lib/python3.8/asyncio/queues.py", line 165, in get
    getter.cancel()  # Just in case getter is not done yet.
  File "/usr/lib/python3.8/asyncio/base_events.py", line 719, in call_soon
    self._check_closed()
  File "/usr/lib/python3.8/asyncio/base_events.py", line 508, in _check_closed
    raise RuntimeError('Event loop is closed')
RuntimeError: Event loop is closed
04/18/2022 01:04:45PM Task was destroyed but it is pending!
task: <Task pending name='Task-40' coro=<UpdateMethods._update_loop() running at /home/roman/PycharmProjects/telegram_bot/.idea/VirtualEnvironment/lib/python3.8/site-packages/telethon/client/updates.py:344> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x7fe3e914b6a0>()]>>
04/18/2022 01:04:45PM Task was destroyed but it is pending!
task: <Task pending name='Task-43' coro=<Connection._recv_loop() running at /home/roman/PycharmProjects/telegram_bot/.idea/VirtualEnvironment/lib/python3.8/site-packages/telethon/network/connection/connection.py:324> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x7fe3e915a790>()]>>
04/18/2022 01:04:45PM Task was destroyed but it is pending!
task: <Task pending name='Task-45' coro=<MTProtoSender._recv_loop() running at /home/roman/PycharmProjects/telegram_bot/.idea/VirtualEnvironment/lib/python3.8/site-packages/telethon/network/mtprotosender.py:503> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x7fe3e90a4f70>()]>>
04/18/2022 01:04:45PM Task was destroyed but it is pending!
task: <Task pending name='Task-46' coro=<UpdateMethods._update_loop() running at /home/roman/PycharmProjects/telegram_bot/.idea/VirtualEnvironment/lib/python3.8/site-packages/telethon/client/updates.py:344> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x7fe3e915a610>()]>>
04/18/2022 01:04:45PM Task was destroyed but it is pending!
task: <Task pending name='Task-39' coro=<MTProtoSender._recv_loop() running at /home/roman/PycharmProjects/telegram_bot/.idea/VirtualEnvironment/lib/python3.8/site-packages/telethon/network/mtprotosender.py:503> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x7fe3e915a2b0>()]>>
04/18/2022 01:04:45PM Task was destroyed but it is pending!
task: <Task pending name='Task-37' coro=<Connection._recv_loop() running at /home/roman/PycharmProjects/telegram_bot/.idea/VirtualEnvironment/lib/python3.8/site-packages/telethon/network/connection/connection.py:324> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x7fe3e914be20>()]>>

Process finished with exit code 0

Update from 24/04/2022: I believe the issue linked with event loop. If I run only one test (anyone), it passes successfully. The RuntimeError is risen only in the second test.

I'm missing something. Could you help to fix it, please?

Upvotes: 2

Views: 7062

Answers (1)

RSafe
RSafe

Reputation: 73

Eventually, I've found a solution. loop.run_until_complete(main()) and loop.close() as low-level methods didn't handle all tasks properly during run of multiple tests.

The programme after changes:

async def handle_incoming_message(event):
    # I replaced content of this coroutine with pseudocode for conciseness
    initiate database connection
    handling of event.message.message
    interacting with database and third-party API
    respond to event and logging
    close database connection


async def main():
    bot = await (TelegramClient(BOT_NAME, API_ID, API_HASH)).start(bot_token=BOT_TOKEN)
    me = TelegramClient(MY_ACCOUNT_NAME, API_ID, API_HASH)
    async with bot, me:
        bot.add_event_handler(handle_incoming_message, events.NewMessage(incoming=True))
        # It's helper to handle user messages through console
            while True:
                message = await me.send_message(BOT_NAME, input(MESSAGE_PROMPT))
                logging.warning("I sent: '%s'", message.message)
                # added to escape of ConnectionError: Cannot send requests while disconnected
                await asyncio.sleep(5)


if __name__ == '__main__':
    # new_event_loop() and set_event_loop(loop) are used to prevent an error with loop = 
    # asyncio.get_event_loop()
    # RuntimeError: There is no current event loop in thread 'MainModuleExecutor test 
    #1_0'.
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)
    asyncio.run(main(), debug=False)

Upvotes: 2

Related Questions