how to gracefully close task loops in discord.py - python-3.x

I'm currently creating a discord bot that contains two task loops called check_members and check_music.
When a user enters the offline command, I'd like to gracefully stop these loops. I wrote this piece of code in my Cog class:
class MusicBot(commands.Cog):
# function called when bot is closing.
See [here](https://discordpy.readthedocs.io/en/stable/ext/commands/api.html?highlight=cog_unload#discord.ext.commands.Cog.cog_unload)
def cog_unload(self):
print("Debug")
self.check_members.cancel()
self.check_music.cancel()
print(self.check_members.is_running())
print(self.check_music.is_running())
# example of a task loop I have:
#tasks.loop(seconds=5)
async def check_members(self):
[code...]
In another script, I call the bot.close() function as follows:
await ctx.send("Going offline! See ya later.")
if self.voice is not None:
await self.disconnect()
await self.bot.close()
sys.exit(0)
When a user calls the offline command, that's what the bot prints out:
Debug
True
Task exception was never retrieved
future: <Task finished name='discord.py: on_message' coro=<Client._run_event() done, defined at /home/liuk23/.local/lib/python3.10/site-packages/discord/client.py:401> exception=SystemExit(0)>
Traceback (most recent call last):
File "/home/liuk23/Desktop/coding/Discord-bot-3/main.py", line 67, in <module>
loop.run_forever()
File "/usr/lib/python3.10/asyncio/base_events.py", line 600, in run_forever
self._run_once()
File "/usr/lib/python3.10/asyncio/base_events.py", line 1896, in _run_once
handle._run()
File "/usr/lib/python3.10/asyncio/events.py", line 80, in _run
self._context.run(self._callback, *self._args)
File "/home/liuk23/.local/lib/python3.10/site-packages/discord/client.py", line 409, in _run_event
await coro(*args, **kwargs)
File "/home/liuk23/.local/lib/python3.10/site-packages/discord/ext/commands/bot.py", line 1392, in on_message
await self.process_commands(message)
File "/home/liuk23/.local/lib/python3.10/site-packages/discord/ext/commands/bot.py", line 1389, in process_commands
await self.invoke(ctx) # type: ignore
File "/home/liuk23/.local/lib/python3.10/site-packages/discord/ext/commands/bot.py", line 1347, in invoke
await ctx.command.invoke(ctx)
File "/home/liuk23/.local/lib/python3.10/site-packages/discord/ext/commands/core.py", line 986, in invoke
await injected(*ctx.args, **ctx.kwargs) # type: ignore
File "/home/liuk23/.local/lib/python3.10/site-packages/discord/ext/commands/core.py", line 190, in wrapped
ret = await coro(*args, **kwargs)
File "/home/liuk23/Desktop/coding/Discord-bot-3/music.py", line 223, in offline
await self.functions.offline(ctx)
File "/home/liuk23/Desktop/coding/Discord-bot-3/funcitons.py", line 241, in offline
sys.exit(0)
SystemExit: 0
As you can notice, the Debug text gets printed out, so the cog_unload function get successfully called.
Although I am closing the loops, I get the Task was never retrieved error. Am I misunderstanding the error?

From sys.exit documentation:
Raise a SystemExit exception, signaling an intention to exit the interpreter.
What is happening is that the offline task, by throwing SystemExit, stops, but since no other task is awaiting on it, that exception is never retrieved.
The underlying problem is that, to quit the application, rather than throwing through sys.exit, it would be better to stop the running loop cleanly. For example, if the loop was run with loop.run_until_complete(some_future), it's necessary to set that future with some_future.set_result(some_result).

I fixed it by stopping the main loop that holds all the bot.
In my main script I instantiate the bot in the following manner:
try:
# loop runs until stop is called
loop.run_forever()
except KeyboardInterrupt:
pass
# finally block is called when loop is being stopped
finally:
# stop and close loop
loop.stop()
sys.exit(0)
By calling loop.stop() in another script, the finally block in main.py will be called and will successfully close the loop and the whole script.
Thanks to #Ulisse Bordignon for your answer

Related

how to run pyrogram userbot with multiple user accounts using thread

I am coding a userbot in pyrogram but I want to run multiple Clients with different telegram account but I am stuck here.
I want to run userbot on multiple accounts using one script if I will run it separately then I have to host it so many time I want to host it for one time and run for every account I have.
I think this will help to understand what I am saying.
from pyrogram import Client, filters, handlers, idle
import threading
from pyrogram.handlers import MessageHandler
app1 = Client(
session, api_hash, api_id)
app2 = Client(session,
api_hash, api_id)
accounts = [app1, app2]
async def handlngmessage(client, message):
print(message)
print("\nstarted ")
await client.send_message("me", "recived")
def runner(c):
c.run()
for ac in accounts:
ac.add_handler(handlers.MessageHandler(unmutedtest))
t = threading.Thread(target=runner, args=(ac,))
t.start()
t.join()
When I run this I am just getting error
Output:
Exception in thread Thread-1:
Traceback (most recent call last):
File "/usr/lib/python3.9/threading.py", line 954, in _bootstrap_inner
self.run()
File "/usr/lib/python3.9/threading.py", line 892, in run
self._target(*self._args, **self._kwargs)
File "/home/ak/Desktop/development/bots/pyrogramplugins/userbot/main.py", line 30, in runner
c.run()
File "/home/ak/.local/lib/python3.9/site-packages/pyrogram/methods/utilities/run.py", line 50, in run
loop = asyncio.get_event_loop()
File "/usr/lib/python3.9/asyncio/events.py", line 642, in get_event_loop
raise RuntimeError('There is no current event loop in thread %r.'
RuntimeError: There is no current event loop in thread 'Thread-1'.
Exception in thread Thread-2:
Traceback (most recent call last):
File "/usr/lib/python3.9/threading.py", line 954, in _bootstrap_inner
self.run()
File "/usr/lib/python3.9/threading.py", line 892, in run
self._target(*self._args, **self._kwargs)
File "/home/ak/Desktop/development/bots/pyrogramplugins/userbot/main.py", line 30, in runner
c.run()
File "/home/ak/.local/lib/python3.9/site-packages/pyrogram/methods/utilities/run.py", line 50, in run
loop = asyncio.get_event_loop()
File "/usr/lib/python3.9/asyncio/events.py", line 642, in get_event_loop
raise RuntimeError('There is no current event loop in thread %r.'
RuntimeError: There is no current event loop in thread 'Thread-2'.
With Pyrogram you don't need to use Threading. The internal code is already entirely asynchronous and you can just start the clients one after another, then call Client.idle() to keep them all "alive".
from pyrogram import Client
app1 = Client("first account")
app2 = Client("second account")
# You can either stack decorators ...
#app1.on_message()
#app2.on_message()
async def m_func(_, message):
pass
# ... or use multiple add_handler calls
app1.add_handler(MessageHandler(m_func))
app2.add_handler(MessageHandler(m_func))
# Then start all Clients and call idle() to keep them running
app1.start()
app2.start()
Client.idle()
app1.stop()
app2.stop()
Alternatively, here's a Gist with some more explanation.
https://gist.github.com/pokurt/96afa69e86725850b2101099461609ed

How to start websocket server in thread using python?

I want to start websocket server in separate thread. I have tried to implement as below but
getting Runtime error as it says attached to different loop
Code:
#!/usr/bin/env python
# WS server example
import asyncio
import websockets
import threading
import time
async def hello(websocket, path):
name = await websocket.recv()
print(name)
greeting = "Hello " + name +"!"
await websocket.send(greeting)
print(greeting)
start_server = websockets.serve(hello, "localhost", 8765)
eventLoop = asyncio.new_event_loop()
time.sleep(2)
def startWebSocket(loop, server):
print("WS: thread started")
asyncio.set_event_loop(eventLoop)
asyncio.get_event_loop().run_until_complete(start_server)
eventLoop.run_forever()
print("Run web socket in threaded env")
TH = threading.Thread(target=startWebSocket, args=[eventLoop, start_server,])
TH.start()
# then do some other work after this
Output:
Run web socket in threaded env
WS: thread started
Exception in thread Thread-1:
Traceback (most recent call last):
File "/usr/lib/python3.5/threading.py", line 914, in _bootstrap_inner
self.run()
File "/usr/lib/python3.5/threading.py", line 862, in run
self._target(*self._args, **self._kwargs)
File "threadWithWs.py", line 26, in startWebSocket
asyncio.get_event_loop().run_until_complete(start_server)
File "/usr/lib/python3.5/asyncio/base_events.py", line 387, in run_until_complete
return future.result()
File "/usr/lib/python3.5/asyncio/futures.py", line 274, in result
raise self._exception
File "/usr/lib/python3.5/asyncio/tasks.py", line 241, in _step
result = coro.throw(exc)
File "/usr/lib/python3.5/asyncio/tasks.py", line 564, in _wrap_awaitable
return (yield from awaitable.__await__())
File "/home/krunal/.local/lib/python3.5/site-packages/websockets/py35/server.py", line 13, in __await_impl__
server = await self._creating_server
File "/usr/lib/python3.5/asyncio/base_events.py", line 923, in create_server
infos = yield from tasks.gather(*fs, loop=self)
File "/usr/lib/python3.5/asyncio/futures.py", line 361, in __iter__
yield self # This tells Task to wait for completion.
RuntimeError: Task <Task pending coro=<_wrap_awaitable() running at /usr/lib/python3.5/asyncio/tasks.py:564> cb=[_run_until_complete_cb() at /usr/lib/python3.5/asyncio/base_events.py:164]> got Future <_GatheringFuture pending> attached to a different loop
Code exists with this error.
How to setup loop for websocket to start websocket server in thread?
I have followed this answer but no luck.
You don't have to use threads with asyncio. It's redundant.
Do something like this (I actually don't know what websocket is, I assume start_server is a coroutine \ an awaitable).
Create another async function, deputed to keep the loop alive. Before going into a while loop, spawn your task.
You may want to add an health check, using a global variable or wrapping all into a class.
async def run():
eventLoop.create_task(start_server())
while 1:
# if not health_check():
# exit()
await asyncio.sleep(1)
eventLoop.run_until_complete(run())
Do the other stuff by spawning more tasks.
To run blocking code into an asyncio loop, use eventLoop.run_in_executor(None, blocking_code): it's basically a friendly interface to threads.
As with threads, you live into the GIL.
Embrace the way asyncio do things.

server in a thread (Python3.9.0+aiohttp) : RuntimeError: can't register atexit after shutdown

This snippet of code (a minimal server running in a thread, code taken from here) works fine with Python3.8.3 but raises an error message with Python3.9.0:
import asyncio
import threading
from aiohttp import web
def aiohttp_server():
def say_hello(request):
return web.Response(text='Hello, world')
app = web.Application()
app.add_routes([web.get('/', say_hello)])
runner = web.AppRunner(app)
return runner
def run_server(runner):
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(runner.setup())
site = web.TCPSite(runner, 'localhost', 8080)
loop.run_until_complete(site.start())
loop.run_forever()
t = threading.Thread(target=run_server, args=(aiohttp_server(),))
t.start()
The error message:
Exception in thread Thread-1:
Traceback (most recent call last):
File "/usr/lib/python3.9/threading.py", line 954, in _bootstrap_inner
self.run()
File "/usr/lib/python3.9/threading.py", line 892, in run
self._target(*self._args, **self._kwargs)
File "/home/alkhinoos/nikw/nikw/z2.py", line 21, in run_server
loop.run_until_complete(site.start())
File "/usr/lib/python3.9/asyncio/base_events.py", line 642, in run_until_complete
return future.result()
File "/usr/lib/python3.9/site-packages/aiohttp/web_runner.py", line 121, in start
self._server = await loop.create_server(
File "/usr/lib/python3.9/asyncio/base_events.py", line 1460, in create_server
infos = await tasks.gather(*fs, loop=self)
File "/usr/lib/python3.9/asyncio/base_events.py", line 1400, in _create_server_getaddrinfo
infos = await self._ensure_resolved((host, port), family=family,
File "/usr/lib/python3.9/asyncio/base_events.py", line 1396, in _ensure_resolved
return await loop.getaddrinfo(host, port, family=family, type=type,
File "/usr/lib/python3.9/asyncio/base_events.py", line 856, in getaddrinfo
return await self.run_in_executor(
File "/usr/lib/python3.9/asyncio/base_events.py", line 809, in run_in_executor
executor = concurrent.futures.ThreadPoolExecutor(
File "/usr/lib/python3.9/concurrent/futures/__init__.py", line 49, in __getattr__
from .thread import ThreadPoolExecutor as te
File "/usr/lib/python3.9/concurrent/futures/thread.py", line 37, in <module>
threading._register_atexit(_python_exit)
File "/usr/lib/python3.9/threading.py", line 1374, in _register_atexit
raise RuntimeError("can't register atexit after shutdown")
RuntimeError: can't register atexit after shutdown
What's going on ? The same problem appears with Python 3.9.1. Is this problem solved with Python 3.9.2 ? Maybe a relative issue here.
As mentioned in Python manual - Thread Objects
Other threads can call a thread’s join() method. This blocks the calling thread until the thread whose join() method is called is terminated.
After calling t.start() in main thread, the main thread will exit. Then the process is ended.
If you want to run the child thread forever or until it exits, you should call t.join() in main thread after t.start().
Not sure what you did, but I used 127.0.0.1 instead of localhost and the error is resolved!
I managed to get this sorted by importing the ThreadPoolExecutor module, before calling any of my main application code - so at the top of my main.py start-up script.
thread_pool_ref = concurrent.futures.ThreadPoolExecutor
Just the act of importing the module early (before any threads are initialised) was enough to fix the error. There is an on-going issue around how this module must be present in the main thread, before any child threads import or use any related threading library code.
The inspiration for this fix came from this post on the Python bugs site. My issue was specifically around boto3 library, but the fix is applicable across the board.

ValueError("I/O operation on closed pipe") when using asyncio with subprocess

A simplified version of my code looks like this:
def run_async(spawn_coro, timeout):
async def _read_print_line(process):
line = await process.stdout.readline()
line = line.decode('utf-8', errors='replace')
sys.stdout.write(line)
process = await spawn_coro
try:
while not process.stdout.at_eof():
# We should cancel if no output has been received for a certain timeout
await asyncio.wait_for(_read_print_line(process), timeout)
except asyncio.TimeoutError:
print('Timed out executing "{}". No output received for {} seconds'.format(cmd, timeout))
process.kill()
await process.wait()
processes = [asyncio.create_subprocess_shell(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) for cmd in cmds]
loop = asyncio.ProactorEventLoop() # This is windows
asyncio.set_event_loop(loop)
all_jobs = asyncio.gather(
*(run_async(process, timeout) for process in processes)
)
loop.run_until_complete(all_jobs)
loop.close()
When the process exits, I get a bunch of error output like this:
Exception ignored in: <function _ProactorBasePipeTransport.__del__ at 0x000001CD9B582AF8>
Traceback (most recent call last):
File "C:\Users\divis\AppData\Local\Programs\Python\Python37\lib\asyncio\proactor_events.py", line 93, in __del__
warnings.warn(f"unclosed transport {self!r}", ResourceWarning,
File "C:\Users\divis\AppData\Local\Programs\Python\Python37\lib\asyncio\proactor_events.py", line 57, in __repr__
info.append(f'fd={self._sock.fileno()}')
File "C:\Users\divis\AppData\Local\Programs\Python\Python37\lib\asyncio\windows_utils.py", line 102, in fileno
raise ValueError("I/O operation on closed pipe")
ValueError: I/O operation on closed pipe
Exception ignored in: <function BaseSubprocessTransport.__del__ at 0x000001CD9B571DC8>
Traceback (most recent call last):
warnings.warn(f"unclosed transport {self!r}", ResourceWarning,
File "C:\Users\divis\AppData\Local\Programs\Python\Python37\lib\asyncio\base_subprocess.py", line 78, in __repr__
info.append(f'stdout={stdout.pipe}')
File "C:\Users\divis\AppData\Local\Programs\Python\Python37\lib\asyncio\proactor_events.py", line 57, in __repr__
info.append(f'fd={self._sock.fileno()}')
File "C:\Users\divis\AppData\Local\Programs\Python\Python37\lib\asyncio\windows_utils.py", line 102, in fileno
raise ValueError("I/O operation on closed pipe")
ValueError: I/O operation on closed pipe
If I remove the loop.close(), then instead of errors the process just hangs. I'm properly awaiting every subprocess before I close the loop, so I'm not sure what the problem is here.
I know this question was asked a while ago, but I just had the same problem, and I was able to solve it like this:
try:
process = asyncio.create_subprocess_exec(cmd)
...
await process.wait()
finally:
process._transport.close()
Taking a look at the code, this seems to close the piped file handles from the child process. I don't know why asyncio doesn't expose this as part of the returned Process object.

Random connection errors with aio_pika after 2 days of running

I have asyncio script which connects to rabbitmq with aio_pika library every 40 seconds and checks if there are any messages and prints them out which then repeats forever. However, usually, after 2 or so days of running, I will start receiving endless connection exception errors which will only be solved by restarting the script. Perhaps there are some obvious mistakes in the logic of my asyncio script which I am missing?
#!/usr/bin/python3
import time
import async_timeout
import asyncio
import aio_pika
async def got_message(message: aio_pika.IncomingMessage):
with message.process():
print(message.body.decode())
async def main(loop):
try:
with async_timeout.timeout(10):
connection = await aio_pika.connect_robust(
host='#',
virtualhost='#',
login='#',
password='#',
port=5671,
loop=loop,
ssl=True
)
channel = await connection.channel()
await channel.set_qos(prefetch_count=100)
queue_name='mm_message'
queue = await channel.declare_queue(auto_delete=False, name=queue_name)
routing_key='mm_msg'
await queue.bind("amq.topic", routing_key)
que_len = queue.declaration_result.message_count
if(que_len > 0):
await queue.consume(got_message)
except:
print("connection problems..")
if __name__ == "__main__":
loop = asyncio.get_event_loop()
while(True):
time.sleep(40)
loop.run_until_complete(main(loop))
This is the error I endlessly receive after some time:
Traceback (most recent call last):
File "/usr/lib/python3.5/asyncio/events.py", line 125, in _run
self._callback(*self._args)
File "/usr/local/lib/python3.5/dist-packages/aio_pika/pika/adapters/base_connection.py", line 364, in _handle_events
self._handle_read()
File "/usr/local/lib/python3.5/dist-packages/aio_pika/pika/adapters/base_connection.py", line 415, in _handle_read
self._on_data_available(data)
File "/usr/local/lib/python3.5/dist-packages/aio_pika/pika/connection.py", line 1347, in _on_data_available
self._process_frame(frame_value)
File "/usr/local/lib/python3.5/dist-packages/aio_pika/pika/connection.py", line 1414, in _process_frame
if self._process_callbacks(frame_value):
File "/usr/local/lib/python3.5/dist-packages/aio_pika/pika/connection.py", line 1384, in _process_callbacks
frame_value) # Args
File "/usr/local/lib/python3.5/dist-packages/aio_pika/pika/callback.py", line 60, in wrapper
return function(*tuple(args), **kwargs)
File "/usr/local/lib/python3.5/dist-packages/aio_pika/pika/callback.py", line 92, in wrapper
return function(*args, **kwargs)
File "/usr/local/lib/python3.5/dist-packages/aio_pika/pika/callback.py", line 236, in process
callback(*args, **keywords)
File "/usr/local/lib/python3.5/dist-packages/aio_pika/pika/connection.py", line 1332, in _on_connection_tune
self._send_connection_open()
File "/usr/local/lib/python3.5/dist-packages/aio_pika/pika/connection.py", line 1517, in _send_connection_open
self._on_connection_open, [spec.Connection.OpenOk])
File "/usr/local/lib/python3.5/dist-packages/aio_pika/pika/connection.py", line 1501, in _rpc
self._send_method(channel_number, method_frame)
File "/usr/local/lib/python3.5/dist-packages/aio_pika/pika/connection.py", line 1569, in _send_method
self._send_frame(frame.Method(channel_number, method_frame))
File "/usr/local/lib/python3.5/dist-packages/aio_pika/pika/connection.py", line 1548, in _send_frame
raise exceptions.ConnectionClosed
aio_pika.pika.exceptions.ConnectionClosed
except:
print("connection problems..")
This will catch service Exceptions like KeyboardInterrupt, SystemExit. You should never do such thing if you're not going to reraise exception. At very lest you should write:
except Exception:
print("connection problems..")
However in context of asyncio snippet above will break mechanism of cancellation. To avoid it as explained here you should write:
try:
await operation
except asyncio.CancelledError:
raise
except Exception:
log.log('an error has occurred')
Not less important is to understand that connection should not only be opened, but closed also (regardless of what happened between opening and closing). To achieve that people usually use context managers (and in asyncio - asynchronous context managers).
aio_pika doesn't seem to be exception. As example shows you should use async with when dealing with connection:
connection = await aio_pika.connect_robust(
"amqp://guest:guest#127.0.0.1/", loop=loop
)
async with connection:
# ...

Resources