asyncio.wait_for didn't cancel the task - python-3.x

After the asyncio.wait_for timeout, the task was not cancelled
The script below is the minimized script to reproduce it. The tcp server just sent two chars after 100 seconds later after client connected
import sys
import asyncio
import socket
async def test_single_call():
reader, writer = await asyncio.open_connection(host='127.0.0.1', port=8888)
try:
msg = await asyncio.wait_for(reader.read(1), timeout=3)
print("Unexcepted message received:" , msg, file=sys.stderr)
assert False
except asyncio.TimeoutError:
pass
msg = await reader.read(1)
loop = asyncio.get_event_loop()
loop.run_until_complete(test_single_call())
loop.close()
The tcpclient(code above) is expected to timeout 3 seconds later, and read again after that; but it seems the task was not cancelled after it was timeout. My python version is 3.6.9
Traceback (most recent call last):
File "tcpclient.py", line 17, in <module>
loop.run_until_complete(test_single_call())
File "/usr/lib/python3.6/asyncio/base_events.py", line 484, in run_until_complete
return future.result()
File "tcpclient.py", line 14, in test_single_call
msg = await reader.read(1)
File "/usr/lib/python3.6/asyncio/streams.py", line 634, in read
yield from self._wait_for_data('read')
File "/usr/lib/python3.6/asyncio/streams.py", line 452, in _wait_for_data
'already waiting for incoming data' % func_name)
RuntimeError: read() called while another coroutine is already waiting for incoming data
I also uploaded the tcp server here

For Linux Python 3.6, it had this issue. two options:
Upgrade to Python 3.8 or 3.9
Replace the open_connection and StreamReader with loop.sock_connet, loop.sock_recv, loop.sock_sendall
eg:
import sys
import asyncio
import socket
async def test_single_call(loop):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock = await loop.sock_connect(sock, server_address)
try:
msg = await asyncio.wait_for(loop.sock_recv(sock, 1), timeout=3)
print("Unexcepted message received:" , msg, file=sys.stderr)
assert False
except asyncio.TimeoutError:
pass
msg = await loop.sock_recv(sock, 1)
loop = asyncio.get_event_loop()
loop.run_until_complete(test_single_call(loop))
loop.close()

Related

How to use timeout to stop blocking function subscribe.simple

I want to use timeout to stop the blocking function of mqtt, I use a the timeout_decorator module, it can stop command function but cannot stop blocking function, subscribe.simple.
The following code runs successfully
import time
import timeout_decorator
#timeout_decorator.timeout(5, timeout_exception=StopIteration)
def mytest():
print("Start")
for i in range(1,10):
time.sleep(1)
print("{} seconds have passed".format(i))
if __name__ == '__main__':
mytest()
the result as follow:
Start
1 seconds have passed
2 seconds have passed
3 seconds have passed
4 seconds have passed
Traceback (most recent call last):
File "timeutTest.py", line 12, in <module>
mytest()
File "/home/gyf/.local/lib/python3.5/site-packages/timeout_decorator/timeout_decorator.py", line 81, in new_function
return function(*args, **kwargs)
File "timeutTest.py", line 8, in mytest
time.sleep(1)
File "/home/gyf/.local/lib/python3.5/site-packages/timeout_decorator/timeout_decorator.py", line 72, in handler
_raise_exception(timeout_exception, exception_message)
File "/home/gyf/.local/lib/python3.5/site-packages/timeout_decorator/timeout_decorator.py", line 45, in _raise_exception
raise exception()
timeout_decorator.timeout_decorator.TimeoutError: 'Timed Out'
but I failed with the subscribe.simple API
import timeout_decorator
#timeout_decorator.timeout(5)
def sub():
# print(type(msg))
print("----before simple")
# threading.Timer(5,operateFail,args=)
msg = subscribe.simple("paho/test/simple", hostname=MQTT_IP,port=MQTT_PORT,)
print("----after simple")
return msg
publish.single("paho/test/single", "cloud to device", qos=2, hostname=MQTT_IP,port=MQTT_PORT)
try:
print("pub")
msg = sub()
print(msg)
except StopIteration as identifier:
print("error")
The result infinitely wait
pub
----before simple
I want the function which include subscribe.simple API can stop after 5 seconds.
Asyncio won't be able to handle blocking function in the same thread. therefore using asyncio.wait_for failed. However, inspired by this blog post I used loop.run_in_executor to keep control on the blocking thread.
from paho.mqtt import subscribe
import asyncio
MQTT_IP = "localhost"
MQTT_PORT = 1883
msg = None
def possibly_blocking_function():
global msg
print("listenning for message")
msg = subscribe.simple(
"paho/test/simple",
hostname=MQTT_IP,
port=MQTT_PORT,
)
print("message received!")
async def main():
print("----before simple")
try:
await asyncio.wait_for(
loop.run_in_executor(None, possibly_blocking_function), timeout=5
)
except asyncio.TimeoutError:
pass
print("----after simple")
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
Output :
----before simple
listenning for message
----after simple
Please note this is not perfect, the program won't end since there are running tasks. You can exit it using various solution but this is out of scope since I am still looking for a clean way to close that stuck thread.

How to gracefully timeout with asyncio

So before adding try/catch block my event loop closed gracefully when process ran for less than 5 minutes, but after adding try/catch block I started getting this error when the process exceeded 5 minutes
async def run_check(shell_command):
p = await asyncio.create_subprocess_shell(shell_command,
stdin=PIPE, stdout=PIPE, stderr=STDOUT)
fut = p.communicate()
try:
pcap_run = await asyncio.wait_for(fut, timeout=5)
except asyncio.TimeoutError:
pass
def get_coros():
for pcap_loc in print_dir_cointent():
for pcap_check in get_pcap_executables():
tmp_coro = (run_check('{args}'
.format(e=sys.executable, args=args)))
if tmp_coro != False:
coros.append(tmp_coro)
return coros
async def main(self):
p_coros = get_coros()
for f in asyncio.as_completed(p_coros):
res = await f
loop = asyncio.get_event_loop()
loop.run_until_complete(get_coros())
loop.close()
Traceback:
Exception ignored in: <bound method BaseSubprocessTransport.__del__ of
<_UnixSubprocessTransport closed pid=171106 running stdin=
<_UnixWritePipeTransport closing fd=8 open> stdout=<_UnixReadPipeTransport fd=9 open>>>
Traceback (most recent call last):
File "/usr/lib/python3.5/asyncio/base_subprocess.py", line 126, in __del__
File "/usr/lib/python3.5/asyncio/base_subprocess.py", line 101, in close
File "/usr/lib/python3.5/asyncio/unix_events.py", line 568, in close
File "/usr/lib/python3.5/asyncio/unix_events.py", line 560, in write_eof
File "/usr/lib/python3.5/asyncio/base_events.py", line 497, in call_soon
File "/usr/lib/python3.5/asyncio/base_events.py", line 506, in _call_soon
File "/usr/lib/python3.5/asyncio/base_events.py", line 334, in _check_closed
RuntimeError: Event loop is closed
The traceback occurs after the last line in my code is executed.
Debug logs:
DEBUG:asyncio:Using selector: EpollSelector
DEBUG:asyncio:run shell command '/local/p_check w_1.pcap --json' stdin=<pipe> stdout=stderr=<pipe>
DEBUG:asyncio:process '/local/p_check w_1.pcap --json' created: pid 171289DEBUG:asyncio:Write pipe 8 connected: (<_UnixWritePipeTransport fd=8 idle bufsize=0>, <WriteSubprocessPipeProto fd=0 pipe=<_UnixWritePipeTransport fd=8 idle bufsize=0>>)
DEBUG:asyncio:Read pipe 9 connected: (<_UnixReadPipeTransport fd=9 polling>, <ReadSubprocessPipeProto fd=1 pipe=<_UnixReadPipeTransport fd=9 polling>>) INFO:asyncio:run shell command '/local/p_check w_1.pcap --json': <_UnixSubprocessTransport pid=171289 running stdin=<_UnixWritePipeTransport fd=8 idle bufsize=0> stdout=<_UnixReadPipeTransport fd=9 polling>>
DEBUG:asyncio:<Process 171289> communicate: read stdout
INFO:asyncio:poll 4997.268 ms took 5003.093 ms: timeout
DEBUG:asyncio:Close <_UnixSelectorEventLoop running=False closed=False debug=True>
loop.run_until_complete accepts something awaitable: coroutine or future. You pass result of function that returns nothing.
You should change get_coros() to actually return list of coros:
def get_coros():
...
return coros
And cast that list to awaitable that executes jobs one-by-one (or parallely if you want). For example:
async def main():
for coro in get_coros():
await coro
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()
Upd:
I can't test my guess right now, but here it is: while asyncio.wait_for(fut, timeout=5) cancels task after 5 seconds, it doesn't terminate the process. You could do that manually:
try:
await asyncio.wait_for(fut, timeout=5)
except asyncio.TimeoutError:
p.kill()
await p.communicate()

python program not exiting on exception using aiozmq

I am using aiozmq for a simple RPC program.
I have created a client and server.
When the server is running, the client runs just fine.
I have a timeout set in the client to raise an exception in the event of no server being reachable.
The client code is below. When I run it without the server running, I get an expected exception but the script doesn't actually return to the terminal. It still seems to be executing.
Could someone firstly explain how this is happening and secondly how to fix it?
import asyncio
from asyncio import TimeoutError
from aiozmq import rpc
import sys
import os
import signal
import threading
import sys
import traceback
#signal.signal(signal.SIGINT, signal.SIG_DFL)
async def client():
print("waiting for connection..")
client = await rpc.connect_rpc(
connect='tcp://127.0.0.1:5555',
timeout=1
)
print("got client")
for i in range(100):
print("{}: calling simple_add".format(i))
ret = await client.call.simple_add(1, 2)
assert 3 == ret
print("calling slow_add")
ret = await client.call.slow_add(3, 5)
assert 8 == ret
client.close()
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.set_debug(True)
future = asyncio.ensure_future(client())
try:
loop.run_until_complete(future)
except TimeoutError:
print("Timeout occurred...")
future.cancel()
loop.stop()
#loop.run_forever()
main_thread = threading.currentThread()
for t in threading.enumerate():
if t is main_thread:
print("skipping main_thread...")
continue
print("Thread is alive? {}".format({True:'yes',
False:'no'}[t.is_alive()]))
print("Waiting for thread...{}".format(t.getName()))
t.join()
print(sys._current_frames())
traceback.print_stack()
for thread_id, frame in sys._current_frames().items():
name = thread_id
for thread in threading.enumerate():
if thread.ident == thread_id:
name = thread.name
traceback.print_stack(frame)
print("exiting..")
sys.exit(1)
#os._exit(1)
print("eh?")
The result of running the above is below. Note again that the program was still running, I had to to exit.
> python client.py
waiting for connection..
got client
0: calling simple_add
Timeout occurred...
skipping main_thread...
{24804: <frame object at 0x00000000027C3848>}
File "client.py", line 54, in <module>
traceback.print_stack()
File "client.py", line 60, in <module>
traceback.print_stack(frame)
exiting..
^C
I also tried sys.exit() which also didn't work:
try:
loop.run_until_complete(future)
except:
print("exiting..")
sys.exit(1)
I can get the program to die, but only if I use os._exit(1). sys.exit() doesn't seem to cut it. I doesn't appear that there are any other threads preventing the interpreter from dying. (Unless I'm mistaken?) What else could be stopping the program from exiting?

AttributeError: 'module' object has no attribute 'ensure_future'

Hi i am writing a n/w bound server application using python asyncio which can accept a post request.
In post request i am accepting a symbol parameter
please tell me the best way to deal with n/w bound application.where i am collecting the data from another web api's by sending the post request to them.
Following is the code :
import asyncio
import aiohttp
import json
import logging
# async def fetch_content(url, symbols):
# yield from aiohttp.post(url, symbols=symbols)
#asyncio.coroutine
def fetch_page(writer, url, data):
response = yield from aiohttp.post(url, data=data)
resp = yield from response.read_and_close()
print(resp)
writer.write(resp)
return
#asyncio.coroutine
def process_payload(writer, data, scale):
tasks = []
data = data.split('\r\n\r\n')[1]
data = data.split('\n')
data = [x.split(':') for x in data]
print(data)
data = {x[0]: x[1] for x in data}
print(data)
# data = data[0].split(':')[1]
data = data['symbols']
print(data)
data = data.split(',')
data_len = len(data)
data_first = 0
data_last = scale
url = 'http://xxxxxx.xxxxxx.xxx/xxxx/xxxx'
while data_last < data_len:
tasks.append(asyncio.ensure_future(fetch_page(writer, url,{'symbols': ",".join(data[data_first:data_last])})))
data_first += scale
data_last += scale
tasks.append(asyncio.ensure_future(fetch_page(writer, url,{'symbols': ",".join(data[data_first:data_last])})))
loop.run_until_complete(tasks)
return
#asyncio.coroutine
def process_url(url):
pass
#asyncio.coroutine
def echo_server():
yield from asyncio.start_server(handle_connection, 'xxxxxx.xxxx.xxx', 3000)
#asyncio.coroutine
def handle_connection(reader, writer):
data = yield from reader.read(8192)
if data:
message = data.decode('utf-8')
print(message)
yield from process_payload(writer, message, 400)
writer.write_eof()
writer.close()
#url = 'http://XXXXXXX.xxxxx.xxx/xxxx/xxxxxx/xxx'
data = {'symbols': 'GD-US,14174T10,04523Y10,88739910,03209R10,46071F10,77543110,92847N10'}
loop = asyncio.get_event_loop()
loop.run_until_complete(echo_server())
try:
loop.run_forever()
finally:
loop.close()
But i am receiving the following error:
future: <Task finished coro=<handle_connection() done, defined at fql_server_async_v2.py:53> exception=AttributeError("'module' object has no attribute 'ensure_future'",)>
Traceback (most recent call last):
File "/home/user/anugupta/lib/python3.4/asyncio/tasks.py", line 234, in _step
result = coro.send(value)
File "fql_server_async_v2.py", line 60, in handle_connection
yield from process_payload(writer, message, 400)
File "/home/user/anugupta/lib/python3.4/asyncio/coroutines.py", line 141, in coro
res = func(*args, **kw)
File "fql_server_async_v2.py", line 41, in process_payload
tasks.append(asyncio.ensure_future(fetch_page(writer, url, {'symbols':",".join(data[data_first:data_last])})))
AttributeError: 'module' object has no attribute 'ensure_future'
^CTraceback (most recent call last):
File "fql_server_async_v2.py", line 72, in <module>
loop.run_forever()
File "/home/user/anugupta/lib/python3.4/asyncio/base_events.py", line 236, in run_forever
self._run_once()
File "/home/user/anugupta/lib/python3.4/asyncio/base_events.py", line 1017, in _run_once
event_list = self._selector.select(timeout)
File "/home/user/anugupta/lib/python3.4/selectors.py", line 424, in select
fd_event_list = self._epoll.poll(timeout, max_ev)
ensure_future was added in asyncio 3.4.4, use async for earlier versions.
While async is deprecated now it will be supported in oversable future.

Connections aren't closing with Python3 asyncio concurrent HTTP get requests

I've just started using the asyncio libs from Python3.4 and wrote a small program which attempts to concurrently fetch 50 webpages at a time. The program blows up after a few hundred requests with a 'Too many open files' exception.
I thought that my fetch method closes the connections with the 'response.read_and_close()' method call.
Any ideas what's going on here? Am I going about this problem the right way?
import asyncio
import aiohttp
#asyncio.coroutine
def fetch(url):
response = yield from aiohttp.request('GET', url)
response = yield from response.read_and_close()
return response.decode('utf-8')
#asyncio.coroutine
def print_page(url):
page = yield from fetch(url)
# print(page)
#asyncio.coroutine
def process_batch_of_urls(round, urls):
print("Round starting: %d" % round)
coros = []
for url in urls:
coros.append(asyncio.Task(print_page(url)))
yield from asyncio.gather(*coros)
print("Round finished: %d" % round)
#asyncio.coroutine
def process_all():
api_url = 'https://google.com'
for i in range(10):
urls = []
for url in range(50):
urls.append(api_url)
yield from process_batch_of_urls(i, urls)
loop = asyncio.get_event_loop()
loop.run_until_complete(process_all())
The error I'm getting is:
Traceback (most recent call last):
File "/usr/local/lib/python3.4/site-packages/aiohttp/client.py", line 106, in request
File "/usr/local/lib/python3.4/site-packages/aiohttp/connector.py", line 135, in connect
File "/usr/local/lib/python3.4/site-packages/aiohttp/connector.py", line 242, in _create_connection
File "/usr/local/Cellar/python3/3.4.1/Frameworks/Python.framework/Versions/3.4/lib/python3.4/asyncio/base_events.py", line 424, in create_connection
File "/usr/local/Cellar/python3/3.4.1/Frameworks/Python.framework/Versions/3.4/lib/python3.4/asyncio/base_events.py", line 392, in create_connection
File "/usr/local/Cellar/python3/3.4.1/Frameworks/Python.framework/Versions/3.4/lib/python3.4/socket.py", line 123, in __init__
OSError: [Errno 24] Too many open files
During handling of the above exception, another exception occurred:
Aha, I grok you problem.
Explicit connector definitely can solve the issue.
https://github.com/KeepSafe/aiohttp/pull/79 should fix it for implicit connectors too.
Thank you very much for finding resource leak in aiohttp
UPD.
aiohttp 0.8.2 has no the problem.
Ok I finally got it to work.
Turns out I had to use a TCPConnector which pools connections.
So I made this variable:
connector = aiohttp.TCPConnector(share_cookies=True, loop=loop)
and pass it through to each get request. My new fetch routine looks like this:
#asyncio.coroutine
def fetch(url):
data = ""
try:
yield from asyncio.sleep(1)
response = yield from aiohttp.request('GET', url, connector=connector)
except Exception as exc:
print('...', url, 'has error', repr(str(exc)))
else:
data = (yield from response.read()).decode('utf-8', 'replace')
response.close()
return data

Resources