Search code examples
pythonpython-telegram-bottelethon

How do I safely handle asyncio event loop handling with Telethon and Telegram Bot?


I'm creating a simple telegram bot in which I'm making use of the telegram bot and telethon api(as I want to retrieve all members in a chat and with the bot api, i can't do this unless all users are admins). I'm able to currently print out the users in a chat successfully.

However when I terminate the script, I keep getting RuntimeErrors and warnings on certain telethon API coroutines not being awaited and Runtime errors for closed event loops

I did do some reading up on asyncio: https://medium.com/dev-bits/a-minimalistic-guide-for-understanding-asyncio-in-python-52c436c244ea and added the following code to my main function to run the client. Here's a sample of what it looks like:

from typing import Final
from telethon import TelegramClient, events
from telegram import Update, Bot
from telegram.ext import Application, CommandHandler, MessageHandler, filters, ContextTypes

TOKEN: Final = <token>
api_id = <api_id>
api_hash = <api_hash>
BOT_USERNAME = <bot_username>
bot = Bot(token=TOKEN)

bot_client = TelegramClient('sessions/session_master', api_id, api_hash).start(bot_token=TOKEN)


async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    await update.message.reply_text('hello, bot starting..')


async def tag_all_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    await tag_all(update)


async def tag_all(update: Update):
    group_members = await bot_client.get_participants(update.effective_chat.id)
    for user in group_members:
        if user.username or user.first_name:
            print(f' {user.username or user.first_name}')


if __name__ == '__main__':
    print('Starting bot...')
    app = Application.builder().token(TOKEN).build()

    # Commands
    app.add_handler(CommandHandler('start', start_command))
    app.add_handler(CommandHandler('all', tag_all_command))

    # Messages
    app.add_handler(MessageHandler(filters.TEXT, handle_message))


    # Polling
    print('Polling...')
    app.run_polling(poll_interval=3)
    try:
        bot_client.run_until_disconnected()
    except KeyboardInterrupt:
        bot_client.disconnect()

With the code above , I thought this would safely close the event loop, however whenever I terminate the script in pycharm, I get the following errors below, could someone help me pinpoint my mistake?:

C:\Users\Jeevan Mahtani\PycharmProjects\pythonProject\venv\Lib\site-packages\telethon\client\telegrambaseclient.py:643: RuntimeWarning: coroutine 'TelegramBaseClient._disconnect_coro' was never awaited
  pass
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
Traceback (most recent call last):
  File "C:\Users\Jeevan Mahtani\PycharmProjects\pythonProject\bot.py", line 104, in <module>
    bot_client.run_until_disconnected()
  File "C:\Users\Jeevan Mahtani\PycharmProjects\pythonProject\venv\Lib\site-packages\telethon\client\updates.py", line 95, in run_until_disconnected
    return self.loop.run_until_complete(self._run_until_disconnected())
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Jeevan Mahtani\AppData\Local\Programs\Python\Python311\Lib\asyncio\base_events.py", line 628, in run_until_complete
    self._check_closed()
  File "C:\Users\Jeevan Mahtani\AppData\Local\Programs\Python\Python311\Lib\asyncio\base_events.py", line 519, in _check_closed
    raise RuntimeError('Event loop is closed')
RuntimeError: Event loop is closed
sys:1: RuntimeWarning: coroutine 'UpdateMethods._run_until_disconnected' was never awaited
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
Task was destroyed but it is pending!
task: <Task pending name='Task-3' coro=<Connection._send_loop() running at C:\Users\Jeevan Mahtani\PycharmProjects\pythonProject\venv\Lib\site-packages\telethon\network\connection\connection.py:313> wait_for=<Future pending cb=[Task.task_wakeup()]>>
Task was destroyed but it is pending!
task: <Task pending name='Task-4' coro=<Connection._recv_loop() running at C:\Users\Jeevan Mahtani\PycharmProjects\pythonProject\venv\Lib\site-packages\telethon\network\connection\connection.py:332> wait_for=<Future pending cb=[Task.task_wakeup()]>>
Task was destroyed but it is pending!
task: <Task pending name='Task-5' coro=<MTProtoSender._send_loop() running at C:\Users\Jeevan Mahtani\PycharmProjects\pythonProject\venv\Lib\site-packages\telethon\network\mtprotosender.py:462> wait_for=<Future pending cb=[Task.task_wakeup()]>>
Task was destroyed but it is pending!
task: <Task pending name='Task-6' coro=<MTProtoSender._recv_loop() running at C:\Users\Jeevan Mahtani\PycharmProjects\pythonProject\venv\Lib\site-packages\telethon\network\mtprotosender.py:505> wait_for=<Future pending cb=[Task.task_wakeup()]>>
Task was destroyed but it is pending!
task: <Task pending name='Task-7' coro=<UpdateMethods._update_loop() running at C:\Users\Jeevan Mahtani\PycharmProjects\pythonProject\venv\Lib\site-packages\telethon\client\updates.py:425> wait_for=<Future pending cb=[Task.task_wakeup()]>>
Task was destroyed but it is pending!
task: <Task pending name='Task-8' coro=<UpdateMethods._keepalive_loop() running at C:\Users\Jeevan Mahtani\PycharmProjects\pythonProject\venv\Lib\site-packages\telethon\client\updates.py:459> wait_for=<Future pending cb=[Task.task_wakeup()]>>
Unexpected exception in the send loop
Traceback (most recent call last):
  File "C:\Users\Jeevan Mahtani\AppData\Local\Programs\Python\Python311\Lib\asyncio\queues.py", line 158, in get
    await getter
GeneratorExit

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\Jeevan Mahtani\PycharmProjects\pythonProject\venv\Lib\site-packages\telethon\network\connection\connection.py", line 313, in _send_loop
    self._send(await self._send_queue.get())
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Jeevan Mahtani\AppData\Local\Programs\Python\Python311\Lib\asyncio\queues.py", line 160, in get
    getter.cancel()  # Just in case getter is not done yet.
    ^^^^^^^^^^^^^^^
  File "C:\Users\Jeevan Mahtani\AppData\Local\Programs\Python\Python311\Lib\asyncio\base_events.py", line 761, in call_soon
    self._check_closed()
  File "C:\Users\Jeevan Mahtani\AppData\Local\Programs\Python\Python311\Lib\asyncio\base_events.py", line 519, in _check_closed
    raise RuntimeError('Event loop is closed')
RuntimeError: Event loop is closed
Exception ignored in: <coroutine object Connection._send_loop at 0x000001F8E5CD8740>
RuntimeError: coroutine ignored GeneratorExit
Exception ignored in: <coroutine object Connection._recv_loop at 0x000001F8E5CDC8B0>
Traceback (most recent call last):
  File "C:\Users\Jeevan Mahtani\PycharmProjects\pythonProject\venv\Lib\site-packages\telethon\network\connection\connection.py", line 350, in _recv_loop
  File "C:\Users\Jeevan Mahtani\PycharmProjects\pythonProject\venv\Lib\site-packages\telethon\network\connection\connection.py", line 258, in disconnect
  File "C:\Users\Jeevan Mahtani\PycharmProjects\pythonProject\venv\Lib\site-packages\telethon\helpers.py", line 174, in _cancel
  File "C:\Users\Jeevan Mahtani\AppData\Local\Programs\Python\Python311\Lib\asyncio\base_events.py", line 761, in call_soon
  File "C:\Users\Jeevan Mahtani\AppData\Local\Programs\Python\Python311\Lib\asyncio\base_events.py", line 519, in _check_closed
RuntimeError: Event loop is closed
Unhandled error while receiving data
Traceback (most recent call last):
  File "C:\Users\Jeevan Mahtani\AppData\Local\Programs\Python\Python311\Lib\asyncio\queues.py", line 158, in get
    await getter
GeneratorExit

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\Jeevan Mahtani\PycharmProjects\pythonProject\venv\Lib\site-packages\telethon\network\mtprotosender.py", line 505, in _recv_loop
    body = await self._connection.recv()
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Jeevan Mahtani\PycharmProjects\pythonProject\venv\Lib\site-packages\telethon\network\connection\connection.py", line 299, in recv
    result, err = await self._recv_queue.get()
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Jeevan Mahtani\AppData\Local\Programs\Python\Python311\Lib\asyncio\queues.py", line 160, in get
    getter.cancel()  # Just in case getter is not done yet.
    ^^^^^^^^^^^^^^^
  File "C:\Users\Jeevan Mahtani\AppData\Local\Programs\Python\Python311\Lib\asyncio\base_events.py", line 761, in call_soon
    self._check_closed()
  File "C:\Users\Jeevan Mahtani\AppData\Local\Programs\Python\Python311\Lib\asyncio\base_events.py", line 519, in _check_closed
    raise RuntimeError('Event loop is closed')
RuntimeError: Event loop is closed
Exception ignored in: <coroutine object MTProtoSender._recv_loop at 0x000001F8E5CB5B40>
Traceback (most recent call last):
  File "C:\Users\Jeevan Mahtani\PycharmProjects\pythonProject\venv\Lib\site-packages\telethon\network\mtprotosender.py", line 520, in _recv_loop
  File "C:\Users\Jeevan Mahtani\PycharmProjects\pythonProject\venv\Lib\site-packages\telethon\network\mtprotosender.py", line 428, in _start_reconnect
  File "C:\Users\Jeevan Mahtani\AppData\Local\Programs\Python\Python311\Lib\asyncio\base_events.py", line 434, in create_task
  File "C:\Users\Jeevan Mahtani\AppData\Local\Programs\Python\Python311\Lib\asyncio\base_events.py", line 519, in _check_closed
RuntimeError: Event loop is closed
sys:1: RuntimeWarning: coroutine 'MTProtoSender._reconnect' was never awaited
Task was destroyed but it is pending!
task: <Task pending name='Task-20' coro=<Queue.get() running at C:\Users\Jeevan Mahtani\AppData\Local\Programs\Python\Python311\Lib\asyncio\queues.py:158> wait_for=<Future pending cb=[Task.task_wakeup()]> cb=[_release_waiter(<Future pendi...ask_wakeup()]>)() at C:\Users\Jeevan Mahtani\AppData\Local\Programs\Python\Python311\Lib\asyncio\tasks.py:421]>
Exception ignored in: <coroutine object Queue.get at 0x000001F8E5CD8E40>
Traceback (most recent call last):
  File "C:\Users\Jeevan Mahtani\AppData\Local\Programs\Python\Python311\Lib\asyncio\queues.py", line 160, in get
  File "C:\Users\Jeevan Mahtani\AppData\Local\Programs\Python\Python311\Lib\asyncio\base_events.py", line 761, in call_soon
  File "C:\Users\Jeevan Mahtani\AppData\Local\Programs\Python\Python311\Lib\asyncio\base_events.py", line 519, in _check_closed
RuntimeError: Event loop is closed

Process finished with exit code 1

Solution

  • telegram package by default is evasive from some aspects, so you need to specify some stuff manually, you have couple issues:

    • main one is run_polling' close_loop not being set to False, it will close the loop on KeyboardInterrupt too and shutdown without giving chance for telethon to disconnect since they share the same loop.

    • run_polling also handles KeyboardInterrupt for you, no need for you to have your own try except.

    • and finally, using telethon's run_until_disconnected is useless and wrong. because run_polling will lock and run the event loop for telethon too. run_until_disconnected was never reached to begin with, until you exit run_polling with a signal. only then then it will run, and lock twice, so you shouldn't use it.

    something like this should do:

        print('Polling...')
        try:
            app.run_polling(poll_interval=3, close_loop=False)
        finally:
            bot_client.disconnect()