Closing a client - python

There is the following code:
import asyncio
import aiohttp
aut_token = ("token")
tasks = []
iter_flag = False
class WAPI:
async def receiver(WAPI_S):
async for msg in WAPI_S:
data = msg.json()
raise aiohttp.ClientError #test
async def heartbeating(WAPI_S):
while iter_flag:
await WAPI_S.send_json({
"op": 1,
"d": None
})
await asyncio.sleep(42.5)
async def event_manager():
loop = asyncio.get_running_loop()
try:
async with aiohttp.ClientSession().ws_connect("url") as WAPI_S:
task_receive = loop.create_task(WAPI.receiver(WAPI_S)); task_heartbeating = loop.create_task(WAPI.heartbeating(WAPI_S))
tasks.append(task_receive); tasks.append(task_heartbeating)
await asyncio.gather(*tasks)
except aiohttp.ClientError:
global iter_flag
iter_flag = False
await asyncio.sleep(44)
[task.cancel() for task in tasks]
try:
loop.close()
except:
loop.stop()
asyncio.run(WAPI.event_manager())
I want to correctly shutdown the client when the exception is raised. My implementation throws "RuntimeError: Event loop stopped before Future completed" exception while executing. How to do it right?

In method event_manager, the statement:
async with aiohttp.ClientSession().ws_connect("url") as WAPI_S:
needs to be replaced with:
async with aiohttp.ClientSession() as session:
async with session.ws_connect("url") as WAPI_S:
Also, it is considered anti-Pythonic to use a list comprehension for its side effects. See Is it Pythonic to use list comprehensions for just side effects? So you really should replace:
[task.cancel() for task in tasks]
with:
for task in tasks:
task.cancel()
Putting this all together:
async def event_manager():
loop = asyncio.get_running_loop()
try:
async with aiohttp.ClientSession() as session:
async with session.ws_connect("url") as WAPI_S:
task_receive = loop.create_task(WAPI.receiver(WAPI_S)); task_heartbeating = loop.create_task(WAPI.heartbeating(WAPI_S))
tasks.append(task_receive); tasks.append(task_heartbeating)
await asyncio.gather(*tasks)
except aiohttp.ClientError:
global iter_flag
iter_flag = False
await asyncio.sleep(44)
for task in tasks:
task.cancel()
try:
loop.close()
except:
loop.stop()

Related

Asyncio PyTest RuntimeError: no running event loop

Hello everyone, I'm writing tests for a game that is written in the python websockets library. When running the test test_game_started an error appears that asyncio loop is not running, but the test passes.
I provide the entire test code and the error below:
Code of HelpTest class:
class HelpTest:
def __init__(self, loop):
self.loop = loop
self.ws1 = None
self.ws2 = None
self.msg1 = None
self.msg2 = None
self.tasks = []
async def listen1(self):
async with websockets.connect(f"ws://localhost:27000") as websocket:
self.ws1 = websocket
while True:
try:
self.msg1 = await websocket.recv()
await asyncio.sleep(0.1)
except RuntimeError:
break
async def listen2(self):
async with websockets.connect(f"ws://localhost:27000") as websocket:
self.ws2 = websocket
while True:
try:
self.msg2 = await websocket.recv()
await asyncio.sleep(0.1)
except RuntimeError:
break
async def in_game_helper(self, mapData1, mapData2):
self.tasks.extend([self.loop.create_task(self.listen1()), self.loop.create_task(self.listen2())])
await asyncio.wait(self.tasks)
await asyncio.sleep(1)
await self.ws1.send(generate_payload("knock-knock", {"nick": 'a'}))
await asyncio.sleep(0.4)
await self.ws2.send(generate_payload("knock-knock", {"nick": 'b'}))
await asyncio.sleep(0.4)
await self.ws1.send(json.dumps({"header": "send_map", 'data': mapData1}))
await asyncio.sleep(0.4)
await self.ws1.send(json.dumps({"header": "send_map", 'data': mapData2}))
await asyncio.sleep(3)
#for task in self.tasks:
# print(task)
# task.cancel()
#self.loop.stop()
return json.loads(self.msg1)
async def kill_helper(self, coords):
pass
async def miss_helper(self, coords):
pass
Code of TestServer class with tests:
class TestServer:
# main()
def setup_class(self):
self.loop = asyncio.new_event_loop()
self.ws1 = None
self.ws2 = None
self.msg1 = None
self.msg2 = None
self.tasks = []
#pytest.fixture
def mapData1(self):
f = open("map1.json", 'r').read()
data = json.loads(f)
return data
#pytest.fixture
def mapData2(self):
f = open("map2.json", 'r').read()
data = json.loads(f)
return data
#pytest.fixture
def event_loop(self):
loop = asyncio.get_event_loop()
yield loop
loop.close()
#pytest.mark.asyncio
async def test_knock(self):
async with websockets.connect(f"ws://localhost:27000") as websocket:
await websocket.send(generate_payload("knock-knock", {"nick": 'a'}))
for i in range(3):
msg = json.loads(await websocket.recv())
await asyncio.sleep(0.5)
assert msg['header'] == 'registered'
#pytest.mark.asyncio
async def test_send1(self, mapData1):
async with websockets.connect(f"ws://localhost:27000") as websocket:
await websocket.send(generate_payload("knock-knock", {"nick": 'a'}))
for i in range(3):
await websocket.recv()
await asyncio.sleep(0.5)
await websocket.send(json.dumps({"header": "send_map", 'data': mapData1}))
msg = json.loads(await websocket.recv())
assert msg['header'] == 'ready'
await websocket.close()
#pytest.mark.asyncio
async def test_send2(self, mapData2):
async with websockets.connect(f"ws://localhost:27000") as websocket:
await websocket.send(generate_payload("knock-knock", {"nick": 'b'}))
for i in range(4):
await websocket.recv()
await asyncio.sleep(0.5)
await websocket.send(json.dumps({"header": "send_map", 'data': mapData2}))
msg = json.loads(await websocket.recv())
assert msg['header'] == 'ready'
await websocket.close()
#pytest.mark.asyncio
async def test_game_started(self, mapData1, mapData2, event_loop):
helper = HelpTest(event_loop)
answer = await helper.in_game_helper(mapData1, mapData2)
print(answer)
assert answer['header'] == "in_game!!!"
Traceback of runned test:
Traceback (most recent call last):
File "tests.py", line 40, in listen2
break
File "SeaBattle\venv\lib\site-packages\websockets\legacy\client.py", line 650, in __aexit__
await self.protocol.close()
File "SeaBattle\venv\lib\site-packages\websockets\legacy\protocol.py", line 768, in close
await asyncio.wait_for(
File "C:\Users\zayyc\AppData\Local\Programs\Python\Python39\lib\asyncio\tasks.py", line 435, in wait_for
loop = events.get_running_loop()
RuntimeError: no running event loop
I've tried to use pytest,mark.asyncio loop, but it did not help. I think the problem is that the tasks listen1() and listen2() keep running after the loop is closed, I tried to stop them, but the error still persists. I will be very happy with your decisions.

Close asyncio loop

import asyncio
import aiohttp
aut_token = ("token")
tasks = []
iter_flag = True
interval = 0
seq = 0
class WAPI:
async def receiver(WAPI_S):
async for msg in WAPI_S:
global interval
global seq
data = msg.json()
seq = data.get("s")
if data.get("op") == 10:
interval = data.get("d").get("heartbeat_interval") / 1000
if data.get("op") == 11:
pass
raise aiohttp.ClientError
async def heartbeating(WAPI_S):
while iter_flag:
await WAPI_S.send_json({
"op": 1,
"d": seq
})
await asyncio.sleep(interval)
async def event_manager():
loop = asyncio.get_running_loop()
try:
async with aiohttp.ClientSession() as session:
async with session.ws_connect("url") as WAPI_S:
task_receive = loop.create_task(WAPI.receiver(WAPI_S)); task_heartbeating = loop.create_task(WAPI.heartbeating(WAPI_S))
tasks.append(task_receive); tasks.append(task_heartbeating)
await asyncio.gather(*tasks)
except aiohttp.ClientError:
global iter_flag
iter_flag = False
await asyncio.sleep(interval)
for task in tasks:
task.cancel()
try:
loop.close()
except:
loop.stop()
asyncio.run(WAPI.event_manager())
I am trying to implement catching ClientError exception with loop closing, however, loop.close throws "RuntimeError: Event loop stopped before Future completed."
How to implement interception correctly?
You do not need to keep track of your tasks manually, you can simply use
asyncio.all_tasks():
Return a set of not yet finished Task objects run by the loop.
And then a:
pending = asyncio.all_tasks()
c in pending:
wait_for(c, timeout=5)
Also, you are trying to stop the loop while you are in it.
This is the pattern I use most of the times:
async def main():
<do some stuff>
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(main())
except ExceptionYouWantToHandle:
<cleaning up>
finally:
loop.stop()
And in the event_manager you simply return once you get an execption or you pass the exception

With PyTest, run_until_complete does continue without finishing

Basically, what it does, is to do 20 requests async to google.
If I launch it without using PyTest, just a snip of code, like this, it works:
import asyncio
import aiohttp
async def get(
session: aiohttp.ClientSession,
) -> dict:
url = f"https://www.google.com/"
resp = await session.request('GET', url=url)
data = await resp.json()
return data
async def sessions():
async with aiohttp.ClientSession() as session:
tasks = []
for i in range(20):
tasks.append(get(session=session))
return await asyncio.gather(*tasks, return_exceptions=True)
def main():
loop = asyncio.new_event_loop()
try:
asyncio.set_event_loop(loop)
htmls = loop.run_until_complete(sessions())
finally:
loop.close()
print(htmls)
But when I use PyTest, in spite of being the same code (almost), the "htmls" variable at the end is not assignated any value
import aiohttp
import asyncio
async def get(
session: aiohttp.ClientSession,
) -> dict:
url = f"https://www.google.com/"
resp = await session.request('GET', url=url)
data = await resp.json()
return data
async def sessions(self):
async with aiohttp.ClientSession() as session:
tasks = []
for i in range(20):
tasks.append(self.get(session=session))
return await asyncio.gather(*tasks, return_exceptions=True)
def test_example(self):
loop = asyncio.new_event_loop()
try:
asyncio.set_event_loop(loop)
htmls = loop.run_until_complete(self.sessions())
finally:
loop.close()
print(htmls)
Why is this? It is like loop.run_until_complete(self.sessions()) is not waiting for it to finish.
It is resolved. It needed a self as first parameter for the get() method :S

Any chance to shield create_task'ed task?

I need to shield a task been fired with create_task within an aiohttp handler process like
async def handler(request):
asyncio.create_task(long_process())
return {}, 200
So the code is
import asyncio
async def shielded(coro):
try:
await asyncio.shield(coro)
except asyncio.CancelledError:
await coro
raise
def create_task(coro) -> asyncio.Task:
task = asyncio.create_task(shielded(coro))
return task
But this test
async def test_create_task_cancel():
async def coro():
await asyncio.sleep(1)
return None
task = create_task(coro())
await asyncio.sleep(0.1)
task.cancel()
await asyncio.sleep(1)
assert task.done() and task.result() is None
with
RuntimeError: coroutine is being awaited already
aiohttp manual suggests to use aiojobs.Scheduler, but it doesn't work as expected
https://github.com/aio-libs/aiojobs/issues/148
https://github.com/aio-libs/aiojobs/issues/72
Working shield function
async def shielded(coro):
ft = asyncio.ensure_future(coro)
shielded_ft = asyncio.shield(ft)
try:
await shielded_ft
except asyncio.CancelledError:
await ft
raise

Manage asyncio coroutines in dict

I want to manage some coroutines in a dict, while running a main coroutine.
Specifically I want to start endless coroutines, put the handler of them in a dict and cancel them via a dict call again. In my example I want to start 4 Coroutines and they shall cancel one by one again with the coroutine doomsday. I'm using Python 3.6.
import asyncio
import traceback
async def add_to_handler(node, func):
func_handler[node] = asyncio.ensure_future(func, loop=loop)
return
async def test_func1():
while True:
print("1 - HeNlO")
await asyncio.sleep(1)
async def test_func2():
while True:
print("2 - TestFunc2")
await asyncio.sleep(2)
async def test_func3():
while True:
print("3 - Tukan")
await asyncio.sleep(3)
async def test_func4():
while True:
print("4 - Do Coro!")
await asyncio.sleep(4)
async def doomsday():
# Cancel coroutine every 10 seconds
print("launch doomsday")
for i in range(len(func_handler)):
await asyncio.sleep(10)
print("start cancelling with {}".format(i))
func_handler[str(i + 1)].cancel()
return
async def main():
await add_to_handler("1", test_func1)
await add_to_handler("2", test_func2)
await add_to_handler("3", test_func3)
await add_to_handler("4", test_func4)
await doomsday()
while True:
print("z..z..Z..Z...Z")
print(func_handler)
await asyncio.sleep(5)
func_handler = {}
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(main())
except KeyboardInterrupt:
print("stop loop")
loop.close()
I tried it with the .call_latermethod of AbstractEventLoop instead of an endless while loop, but it still doesn't want to work and it seems, that my coroutines are seen as function, but I don't know why. Where is my fault?
try to change this function:
async def add_to_handler(node, func):
func_handler[node] = asyncio.ensure_future(func(), loop=loop)
return None
pay attention on asyncio.ensure_future(func(),loop=loop)

Categories