How to gracefully timeout with asyncio - python-3.x

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()

Related

asyncio.wait_for didn't cancel the task

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()

How to send multiple messages in parallel and consume them at one shot using RabbitMQ?

New to RabbitMQ. After browsing through multiple sites I could construct the following program to send multiple messages in parallel.
sender.py
import pika
from threading import Thread
from queue import Queue
import multiprocessing
class MetaClass(type):
_instance = {}
def __call__(cls, *args, **kwargs):
"""
Singleton Design pattern
if the instance already exist don't create one!
"""
if cls not in cls._instance:
cls._instance[cls] = super(MetaClass, cls).__call__(*args, **kwargs)
return cls._instance[cls]
class RabbitMQConfigure(metaclass=MetaClass):
def __init__(self, queue='durable_task_queue', host="localhost", routing_key="durable_task_queue", exchange=""):
"""
Configure RabbitMQ server
"""
self.queue = queue
self.host = host
self.routing_key = routing_key
self.exchange = exchange
class RabbitMQ(Thread):
def __init__(self, rabbit_mq_server, queue1):
Thread.__init__(self)
self.rabbit_mq_server = rabbit_mq_server
self.queue1 = queue1
self._connection = pika.BlockingConnection(
pika.ConnectionParameters(self.rabbit_mq_server.host))
self._channel = self._connection.channel()
self._channel.queue_declare(queue=self.rabbit_mq_server.queue, durable=True)
def __enter__(self):
print("__enter__")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("__exit__")
self._connection.close()
def publish(self, message=""):
print("Inside publish method...")
self._channel.basic_publish(exchange=self.rabbit_mq_server.exchange,
routing_key=self.rabbit_mq_server.routing_key, body=message)
print(" [x] Sent %r" % message)
def run(self):
i = 0
while i <= 5:
# Get the work from the queue and expand the tuple
message = self.queue1.get()
print("Inside run method...")
print("Going to call publish method...")
print("Message value:" + message)
self.publish(message=message)
i += 1
if __name__ == "__main__":
rabbit_mq_server = RabbitMQConfigure(queue='durable_task_queue', host="localhost", routing_key='durable_task_queue',
exchange="")
# with RabbitMQ(rabbit_mq_server, message="Hello World!") as rabbitmq:
# rabbitmq.publish()
queue1 = Queue()
no_of_CPUs = multiprocessing.cpu_count()
messages = []
for i in range(5):
messages.append("Hello world1" + str(i))
for x in range(2):
with RabbitMQ(rabbit_mq_server, queue1) as rabbitmq:
# rabbitmq.daemon = True
rabbitmq.start()
# Put the tasks into the queue as a tuple
for message in messages:
queue1.put(message)
# Causes the main thread to wait for the queue to finish processing all the tasks
queue1.join()
But this program always produce the following output without being sending any messages:
E:\rabbitmq\venv\Scripts\python.exe E:/rabbitmq/work_queues/new_task.py
__enter__
__exit__
__enter__
__exit__ Inside run method...Inside run method... Going to call publish method... Going to call publish method...
Message value:Hello world11Message value:Hello world10 Inside publish method...
Inside publish method... Exception in thread Exception in thread Thread-3: Traceback (most recent call last): File "C:\Python38\lib\threading.py", line 932, in _bootstrap_inner Thread-1: Traceback (most recent call last): File "C:\Python38\lib\threading.py", line 932, in _bootstrap_inner
self.run() File "E:/rabbitmq/work_queues/new_task.py", line 79, in run self.run() File "E:/rabbitmq/work_queues/new_task.py", line 79, in run
self.publish(message=message) File "E:/rabbitmq/work_queues/new_task.py", line 67, in publish
self.publish(message=message) File "E:/rabbitmq/work_queues/new_task.py", line 67, in publish
self._channel.basic_publish(exchange=self.rabbit_mq_server.exchange, File "E:\rabbitmq\venv\lib\site-packages\pika\adapters\blocking_connection.py", line 2242, in basic_publish
self._channel.basic_publish(exchange=self.rabbit_mq_server.exchange, File "E:\rabbitmq\venv\lib\site-packages\pika\adapters\blocking_connection.py", line 2242, in basic_publish
self._impl.basic_publish( self._impl.basic_publish( File "E:\rabbitmq\venv\lib\site-packages\pika\channel.py", line 421, in basic_publish
File "E:\rabbitmq\venv\lib\site-packages\pika\channel.py", line 421, in basic_publish
self._raise_if_not_open() File "E:\rabbitmq\venv\lib\site-packages\pika\channel.py", line 1389, in
_raise_if_not_open
self._raise_if_not_open() File "E:\rabbitmq\venv\lib\site-packages\pika\channel.py", line 1389, in
_raise_if_not_open
raise exceptions.ChannelWrongStateError('Channel is closed.') pika.exceptions. raise exceptions.ChannelWrongStateError('Channel is closed.') ChannelWrongStateError: Channel is closed.pika.exceptions.ChannelWrongStateError: Channel is closed.
Is it possible to send multiple message in parallel?
Is it possible to consume all those messages at one shot?
Queues are independent of each other, the same for individual messages inside each queue. You can control which consumer is subscribed to which queue, though, but that's it.
If the messages are sent and consumed in parallel, why not create a big message with all the payloads?

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 close python process on program end

I fail to close a serial connection that runs in a process properly at the end of the program. (on windows/VSCode and Ctrl-C)
I get an error message and most of the time the port is already opened at the next start of the program.
Do I have to finish the run process first?
class serialOne(Process):
def __init__(self, serial_port, debug, baudrate=57600, timeout=1):
...
def terminate(self):
print("close ports")
self.active = False
self.ser.close()
def run(self):
while self.active:
self.initCom()
self.readCom()
time.sleep(0.005)
def main():
global processList
global debug
while True:
if debug == True:
print("main")
time.sleep(1.0)
for process in processList:
process.terminate()
and my main:
def main():
global processList
global debug
while True:
if debug == True:
print("main") # actually doing nothing
time.sleep(1.0)
for process in processList:
process.terminate()
that's the error message:
Process serialOne-1:
Traceback (most recent call last):
File "C:\Users\dgapp\AppData\Local\Programs\Python\Python37-32\lib\multiprocessing\process.py", line 297, in _bootstrap
self.run()
Traceback (most recent call last):
File "e:\_python\rfid_jacky\simple_multiprocess_rfid_02.py", line 129, in run
time.sleep(0.005)
File "c:\Users\dgapp\.vscode\extensions\ms-python.python-2018.12.1\pythonFiles\ptvsd_launcher.py", line 45, in <module>
KeyboardInterrupt
main(ptvsdArgs)
File "c:\Users\dgapp\.vscode\extensions\ms-python.python-2018.12.1\pythonFiles\lib\python\ptvsd\__main__.py", line 265, in main
wait=args.wait)
File "c:\Users\dgapp\.vscode\extensions\ms-python.python-2018.12.1\pythonFiles\lib\python\ptvsd\__main__.py", line 258, in handle_args
debug_main(addr, name, kind, *extra, **kwargs)
File "c:\Users\dgapp\.vscode\extensions\ms-python.python-2018.12.1\pythonFiles\lib\python\ptvsd\_local.py", line 45, in debug_main
run_file(address, name, *extra, **kwargs)
File "c:\Users\dgapp\.vscode\extensions\ms-python.python-2018.12.1\pythonFiles\lib\python\ptvsd\_local.py", line 79, in run_file
run(argv, addr, **kwargs)
File "c:\Users\dgapp\.vscode\extensions\ms-python.python-2018.12.1\pythonFiles\lib\python\ptvsd\_local.py", line 140, in _run
_pydevd.main()
File "c:\Users\dgapp\.vscode\extensions\ms-python.python-2018.12.1\pythonFiles\lib\python\ptvsd\_vendored\pydevd\pydevd.py", line 1925, in main
debugger.connect(host, port)
File "c:\Users\dgapp\.vscode\extensions\ms-python.python-2018.12.1\pythonFiles\lib\python\ptvsd\_vendored\pydevd\pydevd.py", line 1283, in run
return self._exec(is_module, entry_point_fn, module_name, file, globals, locals)
File "c:\Users\dgapp\.vscode\extensions\ms-python.python-2018.12.1\pythonFiles\lib\python\ptvsd\_vendored\pydevd\pydevd.py", line 1290, in _exec
pydev_imports.execfile(file, globals, locals) # execute the script
File "c:\Users\dgapp\.vscode\extensions\ms-python.python-2018.12.1\pythonFiles\lib\python\ptvsd\_vendored\pydevd\_pydev_imps\_pydev_execfile.py", line 25, in execfile
exec(compile(contents+"\n", file, 'exec'), glob, loc)
File "e:\_python\rfid_jacky\simple_multiprocess_rfid_02.py", line 161, in <module>
main()
File "e:\_python\rfid_jacky\simple_multiprocess_rfid_02.py", line 140, in main
time.sleep(1.0)
KeyboardInterrupt
When you press Ctrl+C, a KeyboardInterrupt exception is thrown, and interrupts your infinite sleep loop. But since you don't catch this exception, the code after this loop (with process.terminate()) is never called, which probably causes your issue.
So you have several options:
catch KeyboardInterrupt and use that to exit the inifite loop:
def main():
global processList
global debug
try:
while True:
if debug == True:
print("main") # actually doing nothing
time.sleep(1.0)
except KeyboardInterrupt:
pass
for process in processList:
process.terminate()
Which is simple and very readable.
register an exit handler that will be run when your program exits:
import atexit
#atexit.register
def shutdown():
global processList
for process in processList:
process.terminate()
def main():
global debug
while True:
if debug == True:
print("main") # actually doing nothing
time.sleep(1.0)
Which is more reliable since it will work even if your process is terminated by another signal.
The KeyboardInterrupt happens when the user hits the interrupt key.
A simple solution would be to catch the exception:
while True:
if debug == True:
print("main") # actually doing nothing
try:
# do your things
except KeyboardInterrupt:
print("program was interrupted by user")
break
You could also use the finally keyword to properly end your program:
try:
while True:
# do your things
except KeyboardInterrupt:
print("program was interrupted by user")
break
finally:
close() # this will always happen, even if an exception was raised

Keyboard Interrupting an asyncio.run() raises CancelledError and keeps running the code

I have inspected this SO question on how to gracefully close out the asyncio process. Although, when I perform it on my code:
async def ob_main(product_id: str, freq: int) -> None:
assert freq >= 1, f'The minimum frequency is 1s. Adjust your value: {freq}.'
save_loc = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data', 'ob', product_id)
while True:
close = False
try:
full_save_path = create_save_location(save_loc)
file = open(full_save_path, 'a', encoding='utf-8')
await ob_collector(product_id, file)
await asyncio.sleep(freq)
except KeyboardInterrupt:
close = True
task.cancel()
loop.run_forever()
task.exception()
except:
exc_type, exc_value, exc_traceback = sys.exc_info()
error_msg = repr(traceback.format_exception(exc_type, exc_value, exc_traceback))
print(error_msg)
logger.warning(f'[1]-Error encountered collecting ob data: {error_msg}')
finally:
if close:
loop.close()
cwow()
exit(0)
I get the following traceback printed in terminal:
^C['Traceback (most recent call last):\n', ' File "/anaconda3/lib/python3.7/asyncio/runners.py", line 43, in run\n return loop.run_until_complete(main)\n', ' File "/anaconda3/lib/python3.7/asyncio/base_events.py", line 555, in run_until_complete\n self.run_forever()\n', ' File "/anaconda3/lib/python3.7/asyncio/base_events.py", line 523, in run_forever\n self._run_once()\n', ' File "/anaconda3/lib/python3.7/asyncio/base_events.py", line 1722, in _run_once\n event_list = self._selector.select(timeout)\n', ' File "/anaconda3/lib/python3.7/selectors.py", line 558, in select\n kev_list = self._selector.control(None, max_ev, timeout)\n', 'KeyboardInterrupt\n', '\nDuring handling of the above exception, another exception occurred:\n\n', 'Traceback (most recent call last):\n', ' File "coinbase-collector.py", line 98, in ob_main\n await asyncio.sleep(freq)\n', ' File "/anaconda3/lib/python3.7/asyncio/tasks.py", line 564, in sleep\n return await future\n', 'concurrent.futures._base.CancelledError\n']
and the code keeps running.
task and loop are the variables from the global scope, defined in the __main__:
loop = asyncio.get_event_loop()
task = asyncio.run(ob_main(args.p, 10))
Applying this question's method solves the issue. So in the above case:
try:
loop.run_until_complete(ob_main(args.p, 10))
except KeyboardInterrupt:
cwow()
exit(0)
However, I do not uderstand why that works.

Resources