Unable to catch RuntimeError raised due to closing asyncio event loop - python-asyncio

I am writing a python script that uses signal_handler to capture SIGINT, SIGTSTP signals. The signal handler closes the event loop, which raises a RuntimeError. I'm trying to catch this error and display something else instead. My code is as follows:
async def signal_handler(self):
try :
#perform some cleanup by executing some coroutines
self.loop.stop()
except RuntimeError as err:
print('SIGINT or SIGTSTP raised')
print("cleaning and exiting")
sys.exit(0)
The output is as follows:
^CTraceback (most recent call last):
File "pipeline.py", line 69, in <module>
pipeline(sys.argv[1], (lambda : sys.argv[2] if len(sys.argv)==3 else os.getcwd())())
File "pipeline.py", line 9, in __init__
self.main(link)
File "pipeline.py", line 52, in main
asyncio.run(video.get_file(obj.metadata['name']+"_v."+obj.default_video_stream[1], obj.default_video_stream[0]))
File "/usr/lib/python3.8/asyncio/runners.py", line 44, in run
return loop.run_until_complete(main)
File "/usr/lib/python3.8/asyncio/base_events.py", line 614, in run_until_complete
raise RuntimeError('Event loop stopped before Future completed.')
RuntimeError: Event loop stopped before Future completed.
From the output I can infer that
self.loop.stop()
raised the RuntimeError.
How can I solve this issue?

MRE
Let's start with a Minimal Reproducible Example so that we know we are on the same page:
import asyncio
import signal
import sys
loop = asyncio.new_event_loop()
async def main():
while True:
print('Hey')
await asyncio.sleep(0.5)
async def _signal_handler():
try:
loop.stop()
except RuntimeError as err:
print('SIGINT or SIGTSTP raised')
print("cleaning and exiting")
sys.exit(1)
def signal_handler(*args):
loop.create_task(_signal_handler())
signal.signal(signal.SIGINT, signal_handler)
loop.run_until_complete(main())
This will print the following when SIGINT is received:
Hey
Hey
Hey
^CTraceback (most recent call last):
File "73094030.py", line 26, in <module>
loop.run_until_complete(main())
File "/usr/lib/python3.8/asyncio/base_events.py", line 614, in run_until_complete
raise RuntimeError('Event loop stopped before Future completed.')
RuntimeError: Event loop stopped before Future completed.
Problem
The error will be raised in main(). It happens because the loop is forced to exit before asyncio.sleep() task is finished.
Solution
To solve this, we should cancel the task before exiting. Let's replace
loop.stop()
with
tasks = asyncio.all_tasks(loop)
for task in tasks:
task.cancel()
This still raises an exception:
Hey
Hey
Hey
^CTraceback (most recent call last):
File "73094030.py", line 28, in <module>
loop.run_until_complete(main())
File "/usr/lib/python3.8/asyncio/base_events.py", line 616, in run_until_complete
return future.result()
asyncio.exceptions.CancelledError
But we have proceeded to change RuntimeError to CancelledError which is more descriptive. It also allows the running functions to run their finally blocks, freeing resources. The event loop will stop automatically after all the tasks finish.
Of course we now except to get a CancelledException. So let's add a try/except block to main():
async def main():
try:
while True:
print('Hey')
await asyncio.sleep(0.5)
except asyncio.CancelledError:
print('\nFunction stopped manually.')
Now we get no clutter:
Hey
Hey
Hey
^C
Function stopped manually.
The final code looks like this:
import asyncio
import signal
import sys
loop = asyncio.new_event_loop()
async def main():
try:
while True:
print('Hey')
await asyncio.sleep(0.5)
except asyncio.CancelledError:
print('\nFunction stopped manually.')
async def _signal_handler():
try:
tasks = asyncio.all_tasks(loop)
for task in tasks:
task.cancel()
except RuntimeError as err:
print('SIGINT or SIGTSTP raised')
print("cleaning and exiting")
sys.exit(1)
def signal_handler(*args):
loop.create_task(_signal_handler())
signal.signal(signal.SIGINT, signal_handler)
loop.run_until_complete(main())

Related

Create an async generator "from scratch"

Most of the time I am using asyncio API. But how would I create an async generator "from scratch"?
Say I have a classic generator, and I want to make it async. How can I do that?
I naively thought that I could do something like below, but it does not run asynchronously (the 3 "for loops" are running one after the other, instead of concurrently). My hope was to make some_loop() asynchronous by calling it from some_async_loop() and releasing the event loop after each iteration with asyncio.sleep(0):
#!/usr/bin/env python3
import asyncio
async def run():
task = asyncio.ensure_future(run_async_loop())
asyncio.ensure_future(run_async_loop())
asyncio.ensure_future(run_async_loop())
await task
async def run_async_loop():
async for i in some_async_loop():
print(i)
async def some_async_loop():
for i in some_loop():
yield i
asyncio.sleep(0)
def some_loop():
for i in range(10):
yield i
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(run())
I'm glad the fixing the call to asyncio.sleep() got things running.
I'm concerned partly about your run() function, because you're only await-ing on the first task, which means your code could exit before the other tasks are complete. I would suggest this:
async def run():
tasks = [run_async_loop(), run_async_loop(), run_async_loop()]
await asyncio.gather(*tasks)
I think you can also simplify your __main__ block:
if __name__ == '__main__':
asyncio.run(run())

ValueError when running simple asyncio script

I'm learning about asycio and trying to run this script I get this error:
ValueError: a coroutine was expected, got <async_generator object
mygen at 0x7fa2af959a60>
What am I missing here?
import asyncio
async def mygen(u=10):
"""Yield powers of 2."""
i = 0
while i < int(u):
yield 2 ** i
i += 1
await asyncio.sleep(0.1)
asyncio.run(mygen(5))
The asyncio.run function expects a coroutine but mygen is an asynchronous generator.
You can try something like this:
test.py:
import asyncio
async def mygen(u=10):
"""Yield powers of 2."""
i = 0
while i < int(u):
yield 2**i
i += 1
await asyncio.sleep(0.1)
async def main():
async for i in mygen(5):
print(i)
if __name__ == "__main__":
asyncio.run(main())
Test:
$ python test.py
1
2
4
8
16
References:
What does the "yield" keyword do?
PEP 525 -- Asynchronous Generators

Problems with the economy system discord.py

I'm trying to make sure that when you create an account you create 2 variables that is wallet and bank only that it gives me this specific error:
Ignoring exception in command create:
Traceback (most recent call last):
File "C:\Users\PC GIUSEPPE\PycharmProjects\LMIIBot\venv\lib\site-packages\discord\ext\commands\core.py", line 83, in wrapped
ret = await coro(*args, **kwargs)
File "C:/Users/PC GIUSEPPE/PycharmProjects/LMIIBot/LMII-Bot.py", line 7042, in create
economy_system[id_author]["wallet"] = 100
KeyError: '488826524791734275'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "C:\Users\PC GIUSEPPE\PycharmProjects\LMIIBot\venv\lib\site-packages\discord\ext\commands\bot.py", line 892, in invoke
await ctx.command.invoke(ctx)
File "C:\Users\PC GIUSEPPE\PycharmProjects\LMIIBot\venv\lib\site-packages\discord\ext\commands\core.py", line 797, in invoke
await injected(*ctx.args, **ctx.kwargs)
File "C:\Users\PC GIUSEPPE\PycharmProjects\LMIIBot\venv\lib\site-packages\discord\ext\commands\core.py", line 92, in wrapped
raise CommandInvokeError(exc) from exc
discord.ext.commands.errors.CommandInvokeError: Command raised an exception: KeyError: '488826524791734275'
After many attempts I was unable to solve in any way, how can I solve?
Code:
#client.event
async def on_ready():
global economy_system
try:
with open('economy.json') as f:
economy_system = json.load(f)
except FileNotFoundError:
print("Impossibile caricare amounts.json")
economy_system = {}
#client.command(pass_context=True, aliases=["Create", "CREATE", "register", "Register", "REGISTER", "crea", "Crea", "CREA"])
async def create(ctx):
id_author = str(ctx.author.id)
embed = discord.Embed(
color=0x003399
)
if id_author not in economy_system:
economy_system[id_author]["wallet"] = 100
economy_system[id_author]["bank"] = 0
embed.set_author(
name="Hai creato il tuo account!"
)
await ctx.send(embed=embed, delete_after=10)
_save()
else:
embed.set_author(
name="Hai giĆ  registrato un account!"
)
await ctx.send(embed=embed, delete_after=10)
def _save():
with open('economy.json', 'w+') as f:
json.dump(economy_system, f)
economy_system[id_author]["wallet"] = 100
tries to access a dict that does not exist yet.
If you put economy_system[id_author] = {} one line above, the problem should be solved.
"KeyError: '488826524791734275'" means that the author can't be found in economy_system

Python Multiprocessing not working (Discord.py + Windows 10)

The following code does not initiate, the pool or process method. Please help.
if __name__ == '__main__':
#client.event
async def on_message(message):
if message.guild is not None:
if (message.content.startswith('%season15')):
input = str(message.content[len('%season15'):].strip())
df = get_data(input) ##this line of code is processed
p = Pool(processes=3)
result = p.starmap(get_season_data, [(input,df,5),(input,df, 10),(input,df,15)])
processes = [Process(target=get_season_data, args=(symbol, df, i)) for i in [5,10,15]]
# Run processes
for p in processes:
p.start()
# Exit the completed processes
for p in processes:
p.join()
client.run('token#')
The code does initiate multiple python processes in task manager but does not process any of the code in get_season_data and does not throw any errors.

infinite loop cannot be connected websocket server

A client connect websocket and calls tail_log method, and new client can't connect
How to solve this problem
def on_message(self, message):
def tail_log(user,ip,port,cmd,log_path,url):
cmd = "/usr/bin/ssh -p {port} {user}#{ipaddr} {command} {logpath}" \
.format(user=user, ipaddr=ip, port=port, command=cmd, logpath=log_path)
f = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
while True:
line = f.stdout.readline().strip()
if line == '':
self.write_message('failed')
break
self.write_message(line)
tail_log(user=SSH_USER,ip=IP_ADDR,cmd=CMD,port=SSH_PORT,log_path=LOG_PATH,url=SOCKET_URL)
Your infinite loop must yield control back to Tornado's event loop, either by executing a yield, await, or by returning from the tail_log function. Since your infinite loop does not yield control to the event loop, the event loop can never process any more events, including new websocket connections.
Try using Tornado's own process module to read from your subprocess's stdout asynchronously. Something like this:
import tornado.ioloop
import tornado.process
import tornado.web
import tornado.websocket
class TailHandler(tornado.websocket.WebSocketHandler):
def open(self):
self.write_message(u"Tailing....")
self.p = tornado.process.Subprocess(
"tail -f log.log",
stdout=tornado.process.Subprocess.STREAM,
stderr=tornado.process.Subprocess.STREAM,
shell=True)
tornado.ioloop.IOLoop.current().add_callback(
lambda: self.tail(self.p.stdout))
tornado.ioloop.IOLoop.current().add_callback(
lambda: self.tail(self.p.stderr))
self.p.set_exit_callback(self.close)
async def tail(self, stream):
try:
while True:
line = await stream.read_until(b'\n')
if line:
self.write_message(line.decode('utf-8'))
else:
# "tail" exited.
return
except tornado.iostream.StreamClosedError:
# Subprocess killed.
pass
finally:
self.close()
def on_close(self):
# Client disconnected, kill the subprocess.
self.p.proc.kill()
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("""<html><head><script>
var ws = new WebSocket("ws://localhost:8888/tail");
ws.onmessage = function (evt) {
document.write('<p>' + evt.data + '</p>');
};</script></head></html>""")
def make_app():
return tornado.web.Application([
(r"/", MainHandler),
(r"/tail", TailHandler),
])
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
If you're not on Python 3.5 yet, substitute #gen.coroutine for "async def", substitute "yield" for "await", and substitute "break" for "return".

Resources