Python: Aioimaplib catch exceptions - python

im trying to check multiple imap login informations asynchronously with aioimaplib.
This code works as long as the imap servers are reachable and / or the clients don't time out.
What is the correct way to catch the exceptions?
Example exception:
ERROR:asyncio:Task exception was never retrieved future: <Task finished coro=<BaseEventLoop.create_connection() done, defined at G:\WinPython-3.5.4\python-3.5.4.amd64\lib\asyncio\base_events.py:679> exception=TimeoutError(10060, "Connect call failed ('74.117.114.100', 993)")>
Code:
account_infos = [
# User Password Server
('user1#web.com', 'password1', 'imap.google.com'),
('user2#web.com', 'password2', 'imap.yandex.com'),
('user3#web.com', 'password3', 'imap.server3.com'),
]
class MailLogin:
def __init__(self):
self.loop = asyncio.get_event_loop()
self.queue = asyncio.Queue(loop=self.loop)
self.max_workers = 2
async def produce_work(self):
for i in account_infos:
await self.queue.put(i)
for _ in range(max_workers):
await self.queue.put((None, None, None))
async def worker(self):
while True:
(username, password, server) = await self.queue.get()
if username is None:
break
while True:
try:
s = IMAP4_SSL(server)
await s.wait_hello_from_server()
r = await s.login(username, password)
await s.logout()
if r.result != 'NO':
print('Information works')
except Exception as e:
# DOES NOT CATCH
print(str(e))
else:
break
def start(self):
try:
self.loop.run_until_complete(
asyncio.gather(self.produce_work(), *[self.worker() for _ in range(self.max_workers)],
loop=self.loop, return_exceptions=True)
)
finally:
print('Done')
if __name__ == '__main__':
MailLogin().start()

There are several ways to do this but the TimeoutError is probably caught in your except. You don't see it because str(e) is an empty string.
You can see the stacks enabling debug mode of asyncio.
First, you can catch the exception as you did:
async def fail_fun():
try:
imap_client = aioimaplib.IMAP4_SSL(host='foo', timeout=1)
await imap_client.wait_hello_from_server()
except Exception as e:
print('Exception : ' + str(e))
if __name__ == '__main__':
get_event_loop().run_until_complete(fail_fun())
Second, you can catch the exception at run_until_complete
async def fail_fun():
imap_client = aioimaplib.IMAP4_SSL(host='foo', timeout=1)
await imap_client.wait_hello_from_server()
if __name__ == '__main__':
try:
get_event_loop().run_until_complete(fail_fun())
except Exception as e:
print('Exception : ' + str(e))
The connection is established wrapping the loop.create_connection coroutine with create_task : we wanted to establish the connection in the IMAP4 constructor and __init__ should return None.
So if your host has a wrong value, you could test it before, or wait for the timeout :
socket.gaierror: [Errno -5] No address associated with hostname
if a host is not responding before the timeout, you can raise the timeout. And if the connection is lost during the connection, you can add a connection lost callback in the IMAP4 constructor.

Related

FastAPI: Task was destroyed but it is pending

I've been fighting asyncio + FastAPI for the last 48 hours. Any help would be appreciated. I'm registering an on_gift handler that parses gifts I care about and appends them to gift_queue. I'm then pulling them out in a while loop later in the code to send to the websocket.
The goal is to block the main thread until the client disconnects. Then we can do cleanup. It appears that the cleanup is being ran but I'm seeing this error after the first request:
Task was destroyed but it is pending!
task: <Task pending name='Task-12' coro=<WebSocketProtocol13._receive_frame_loop() running at /Users/zane/miniconda3/lib/python3.9/site-packages/tornado/websocket.py:1106> wait_for=<Future pending cb=[IOLoop.add_future.<locals>.<lambda>() at /Users/zane/miniconda3/lib/python3.9/site-packages/tornado/ioloop.py:687, <TaskWakeupMethWrapper object at 0x7f9558a53af0>()]> cb=[IOLoop.add_future.<locals>.<lambda>() at /Users/zane/miniconda3/lib/python3.9/site-packages/tornado/ioloop.py:687]>
#router.websocket('/donations')
async def scan_donations(websocket: WebSocket, username):
await websocket.accept()
client = None
gift_queue = []
try:
client = TikTokLiveClient(unique_id=username,
sign_api_key=api_key)
def on_gift(event: GiftEvent):
gift_cost = 1
print('[DEBUG] Could not find gift cost for {}. Using 1.'.format(
event.gift.extended_gift.name))
try:
if event.gift.streakable:
if not event.gift.streaking:
if gift_cost is not None:
gift_total_cost = gift_cost * event.gift.repeat_count
# if it cost less than 99 coins, skip it
if gift_total_cost < 99:
return
gift_queue.append(json.dumps({
"type": 'tiktok_gift',
"data": dict(name=event.user.nickname)
}))
else:
if gift_cost < 99:
return
gift_queue.append(json.dumps({
'type': 'tiktok_gift',
'data': dict(name=event.user.nickname)
}))
except:
print('[DEBUG] Could not parse gift event: {}'.format(event))
client.add_listener('gift', on_gift)
await client.start()
while True:
if gift_queue:
gift = gift_queue.pop(0)
await websocket.send_text(gift)
del gift
else:
try:
await asyncio.wait_for(websocket.receive_text(), timeout=1)
except asyncio.TimeoutError:
continue
except ConnectionClosedOK:
pass
except ConnectionClosedError as e:
print("[ERROR] TikTok: ConnectionClosedError {}".format(e))
pass
except FailedConnection as e:
print("[ERROR] TikTok: FailedConnection {}".format(e))
pass
except Exception as e:
print("[ERROR] TikTok: {}".format(e))
pass
finally:
print("[DEBUG] TikTok: Stopping listener...")
if client is not None:
print('Stopping TTL')
client.stop()
await websocket.close()
To add:
I can see
[ERROR] TikTok: 1000
[DEBUG] TikTok: Stopping listener...
Stopping TTL
Once I disconnect in Postman. It seems that something is not exiting cleanly. I have a scan_chat function which is nearly identical and I don't get these errors.

Force the end of coroutine asyncio

I'm trying to understand coroutines in python, but I have some troubles grasping how I could end one.
I try to understand the following code :
async def send_recieve():
async with websockets.connect(*parameters*) as _ws:
async def send():
while True:
#function send...
async def recieve():
while True:
#function recieve...
if #condition met:
break
send_result, receive_result = await asyncio.gather(send(), receive())
asyncio.run(send_receive())
When the condition is met, the recieve function is ended, but the send function keep working and I can't end the whole send_recieve async.
I tried to sum up the code to be more clear, I can share the whole version if it's easier to understand.
I get that I miss a condition in the send function fulfilled when the recieve function is ended but I can't understand how I can write it.
If I try to add loop.stop() if the condition is met, it raises the error "RuntimeError: Event loop stopped before Future completed."
The whole code is the following :
async def send_receive():
print(f'Connecting websocket to url ${URL}')
async with websockets.connect(
URL,
extra_headers=(("Authorization", auth_key),),
ping_interval=5,
ping_timeout=20
) as _ws:
await asyncio.sleep(0.3)
print("Receiving SessionBegins ...")
session_begins = await _ws.recv()
print(session_begins)
print("Sending messages ...")
async def send():
while True:
try:
data = stream.read(FRAMES_PER_BUFFER)
data = base64.b64encode(data).decode("utf-8")
json_data = json.dumps({"audio_data":str(data)})
await _ws.send(json_data)
except websockets.exceptions.ConnectionClosedError as e:
print(e)
assert e.code == 4008
break
except Exception as e:
assert False, "Not a websocket 4008 error"
await asyncio.sleep(0.01)
return True
async def receive():
while True:
try:
result_str = await _ws.recv()
majtext = json.loads(result_str)['text']
print(majtext)
except websockets.exceptions.ConnectionClosedError as e:
print(e)
assert e.code == 4008
return
except Exception as e:
assert False, "Not a websocket 4008 error"
if json.loads(result_str)['message_type'] == 'FinalTranscript':
break
send_result, receive_result = await asyncio.gather(send(), receive())
loop = asyncio.get_event_loop()
loop.run_until_complete(send_receive())
how could I tell the send function to end when the receive function is ended
asyncio.gather() waits for both functions to finish. You can instead wait for either function to finish by replacing:
send_result, receive_result = await asyncio.gather(send(), receive())
with:
await asyncio.wait(
[asyncio.create_task(send()), asyncio.create_task(receive())],
return_when=asyncio.FIRST_COMPLETED
)
(Note that "results" of send and receive you retrieved from gather() don't make sense since neither function returns a useful value.)

Exception in thread StompReceiverThread-1

I'm having trouble with this error:
Exception in thread StompReceiverThread-1 (most likely raised during
interpreter shutdown):
That is no traceback at all.. just that.
Usualy everything works fine but rarely it happens and then the action does not conclude.
Any tips?
My code:
class Listener(stomp.ConnectionListener):
def __init__(self, conn, request):
self.conn = conn
self.request = request
def on_error(self, headers, message):
global WAITING_RESPONSE
print('received an error: ' + message)
WAITING_RESPONSE = False
def on_message(self, headers, message):
global WAITING_RESPONSE
try:
msg = json.loads(message)
if str(msg.get('transaction_id','')) == str(CURRENT_ID):
printDebugLine('Queue response:'+str(message))
manageQueueResponse(message,self.request)
WAITING_RESPONSE = False
self.conn.ack(headers['message-id'], '11')
except stomp.exception.ConnectFailedException:
print('Stomp error on message')
sys.exit(3)
except Exception as e:
print('ERROR: %s' % str(e))
sys.exit(3)
class Queue(object):
def __init__(self):
self.host = xx
self.port = xx
self.login = xx
self.passwd = xx
self.request = {}
self.start()
def start(self):
try:
self.conn = stomp.Connection(host_and_ports=[(self.host, self.port)])
self.conn.start()
self.conn.connect(self.login, self.passwd, wait=True)
self.conn.set_listener('xx', Listener(self.conn, self.request))
self.conn.subscribe(destination='xx', id='xx', ack='xx')
except stomp.exception.ConnectFailedException:
print('ERROR: unable to connect')
sys.exit(3)
except Exception as e:
print('ERROR: %s' % str(e))
sys.exit(3)
def send(self, data):
global CURRENT_ID
while WAITING_RESPONSE:
time.time(0.1)
try:
CURRENT_ID = str(uuid.uuid4())
data.update({'transaction_id': CURRENT_ID})
b = json.dumps(data)
self.request.update(data)
printDebugLine('Queue request:'+str(data))
self.conn.send(body=b, destination='xx')
timeout(data,self.request,29)
except stomp.exception.ConnectFailedException:
print('ERROR: unable to connect')
except Exception as e:
print('ERROR: %s' % str(e))
It looks like your main program is exiting, the interpreter is cleaning up things, but the stomp receiver thread was not shutdown first. The receiver thread goes to do something but basic modules are no longer available, so it gives an exception message, but cannot print a Traceback because that fuctionality is no longer available due to the program exiting.
Look at why the main program would be exiting.

Cancel asynchronous iterator by timeout

I have a process running with asyncio which should run forever.
I can interact with that process with a ProcessIterator, which can (left out here) send data to stdin and fetch from stdout.
I can access the data with async for fd, data in ProcessIterator(...):.
The problem is now that the execution of this async iterator must be timelimited. If the time runs out, the timeout() function is called,
but the exception does not originate out of the __anext__ function to notify of the timeout.
How can I raise this exception in the async iterator?
I found no way of calling awaitable.throw(something) or similar for it.
class ProcessIterator:
def __init__(self, process, loop, run_timeout):
self.process = process
self.loop = loop
self.run_timeout = run_timeout
# set the global timer
self.overall_timer = self.loop.call_later(
self.run_timeout, self.timeout)
def timeout(self):
# XXX: how do i pass this exception into the iterator?
raise ProcTimeoutError(
self.process.args,
self.run_timeout,
was_global,
)
async def __aiter__(self):
return self
async def __anext__(self):
if self.process.exited:
raise StopAsyncIteration()
else:
# fetch output from the process asyncio.Queue()
entry = await self.process.output_queue.get()
if entry == StopIteration:
raise StopAsyncIteration()
return entry
The usage of the async iterator is now roughly:
async def test_coro(loop):
code = 'print("rofl"); time.sleep(5); print("lol")'
proc = Process([sys.executable, '-u', '-c', code])
await proc.create()
try:
async for fd, line in ProcessIterator(proc, loop, run_timeout=1):
print("%d: %s" % (fd, line))
except ProcessTimeoutError as exc:
# XXX This is the exception I'd like to get here! How can i throw it?
print("timeout: %s" % exc)
await proc.wait()
tl;dr: How can I throw a timed exception so it originates from a async iterator?
EDIT: Added solution 2
Solution 1:
Can the timeout() callback store the ProcTimeoutError exception in an instance variable? Then __anext__() can check the instance variable and raise the exception if it is set.
class ProcessIterator:
def __init__(self, process, loop, run_timeout):
self.process = process
self.loop = loop
self.error = None
self.run_timeout = run_timeout
# set the global timer
self.overall_timer = self.loop.call_later(
self.run_timeout, self.timeout)
def timeout(self):
# XXX: set instance variable
self.error = ProcTimeoutError(
self.process.args,
self.run_timeout,
was_global
)
async def __aiter__(self):
return self
async def __anext__(self):
# XXX: if error is set, then raise the exception
if self.error:
raise self.error
elif self.process.exited:
raise StopAsyncIteration()
else:
# fetch output from the process asyncio.Queue()
entry = await self.process.output_queue.get()
if entry == StopIteration:
raise StopAsyncIteration()
return entry
Solution 2:
Put the exception on the process.output_queue.
....
def timeout(self):
# XXX: set instance variable
self.process.ouput_queue.put(ProcTimeoutError(
self.process.args,
self.run_timeout,
was_global
))
....
# fetch output from the process asyncio.Queue()
entry = await self.process.output_queue.get()
if entry == StopIteration:
raise StopAsyncIteration()
elif entry = ProcTimeoutError:
raise entry
....
If there may be entries on the queue, use a priority queue. Assign ProcTimeoutError a higher priority than the other entries, e.g., (0, ProcTimeoutError) vs (1, other_entry).
Please check out timeout context manager from asyncio:
with asyncio.timeout(10):
async for i in get_iter():
process(i)
It is not released yet but you can copy-paste the implementation from asyncio master branch
You could use get_nowait, which will return entry or throw QueueEmpty immediately. Wrapping it in while loop on self.error with some async sleep should do the trick. Something like:
async def __anext__(self):
if self.process.exited:
raise StopAsyncIteration()
else:
while self.error is None:
try:
entry = self.process.output_queue.get_nowait()
if entry == StopIteration:
raise StopAsyncIteration()
return entry
except asyncio.QueueEmpty:
# some sleep to give back control to ioloop
# since we using nowait
await asyncio.sleep(0.1)
else:
raise self.error
And as a hint approach that is used in Tornado's Queue.get implementation with timeout:
def get(self, timeout=None):
"""Remove and return an item from the queue.
Returns a Future which resolves once an item is available, or raises
`tornado.gen.TimeoutError` after a timeout.
"""
future = Future()
try:
future.set_result(self.get_nowait())
except QueueEmpty:
self._getters.append(future)
_set_timeout(future, timeout)
return future
This is the solution I came up with by now.
See https://github.com/SFTtech/kevin kevin/process.py for the upstream version.
It also features line counting and output timeouts, which I stripped from this example.
class Process:
def __init__(self, command, loop=None):
self.loop = loop or asyncio.get_event_loop()
self.created = False
self.killed = asyncio.Future()
self.proc = self.loop.subprocess_exec(
lambda: WorkerInteraction(self), # see upstream repo
*command)
self.transport = None
self.protocol = None
async def create(self):
self.transport, self.protocol = await self.proc
def communicate(self, timeout):
if self.killed.done():
raise Exception("process was already killed "
"and no output is waiting")
return ProcessIterator(self, self.loop, timeout)
class ProcessIterator:
"""
Asynchronous iterator for the process output.
Use like `async for (fd, data) in ProcessIterator(...):`
"""
def __init__(self, process, loop, run_timeout):
self.process = process
self.loop = loop
self.run_timeout = run_timeout
self.overall_timer = None
if self.run_timeout < INF:
# set the global timer
self.overall_timer = self.loop.call_later(
self.run_timeout,
functools.partial(self.timeout, was_global=True))
def timeout(self):
if not self.process.killed.done():
self.process.killed.set_exception(ProcTimeoutError(
self.process.args,
self.run_timeout,
))
async def __aiter__(self):
return self
async def __anext__(self):
# either the process exits,
# there's an exception (process killed, timeout, ...)
# or the queue gives us the next data item.
# wait for the first of those events.
done, pending = await asyncio.wait(
[self.process.protocol.queue.get(), self.process.killed],
return_when=asyncio.FIRST_COMPLETED)
# at least one of them is done now:
for future in done:
# if something failed, cancel the pending futures
# and raise the exception
# this happens e.g. for a timeout.
if future.exception():
for future_pending in pending:
future_pending.cancel()
# kill the process before throwing the error!
await self.process.pwn()
raise future.exception()
# fetch output from the process
entry = future.result()
# it can be stopiteration to indicate the last data chunk
# as the process exited on its own.
if entry == StopIteration:
if not self.process.killed.done():
self.process.killed.set_result(entry)
# raise the stop iteration
await self.stop_iter(enough=False)
return entry
raise Exception("internal fail: no future was done!")
async def stop_iter(self):
# stop the timer
if self.overall_timer:
self.overall_timer.cancel()
retcode = self.process.returncode()
raise StopAsyncIteration()
The magic function is this:
done, pending = await asyncio.wait(
[self.process.protocol.queue.get(), self.process.killed],
return_when=asyncio.FIRST_COMPLETED)
When the timeout occurs, the queue fetching is aborted reliably.

Python asyncio - heartbeat() method not writing to stream

I trying to create a proof of concept with Python 3 asyncio, implementing a client that sends heartbeats periodically to a server in order to keep the connection alive.
Note that the server is simply an echo server and doesn't close the connection. But it is important that the client is able to send a heartbeat periodically.
Here is the current implementation:
stream_client.py
import asyncio
class StreamClient:
def __init__(self, heartbeat_int, loop=None):
self.heartbeat_int = heartbeat_int
if loop is not None:
self.loop = loop
else:
self.loop = asyncio.get_event_loop()
def start(self):
""" start manages the event loop, but it is not a coroutine """
self.loop.run_until_complete(self.get_client())
self.loop.create_task(self.start_timed_session())
msg = self.loop.run_until_complete(self.logon('hello'))
if msg == 'hello':
try:
self.loop.run_forever()
except KeyboardInterrupt:
print('Closing connection with server...')
self.writer.close()
self.loop.close()
else:
print('Logon unsuccessful, closing connection with server...')
self.writer.close()
self.loop.close()
#asyncio.coroutine
def get_client(self):
self.reader, self.writer = yield from asyncio.open_connection(
'127.0.0.1',
9871,
loop=self.loop
)
print('Connection established at "localhost:9871"')
#asyncio.coroutine
def timed_session(self):
yield from asyncio.sleep(self.heartbeat_int)
self.loop.create_task(self.start_timed_session())
#asyncio.coroutine
def start_timed_session(self):
heartbeat_task = self.loop.create_task(self.timed_session())
heartbeat_task.add_done_callback(self.heartbeat)
#asyncio.coroutine
def logon(self, msg):
print('Sending message:', msg)
self.writer.write(msg.encode())
data = yield from self.reader.read(15)
resp = data.decode()
print('Data received:', resp)
return resp
def heartbeat(self, fut):
"""
This is future's callback:
1) Can't be a coroutine
2) Takes a future as an argument
"""
print('> Sending heartbeat...')
self.writer.write('heartbeat'.encode())
# Start the client
client = StreamClient(5)
client.start()
stream_server.py
import asyncio
class StreamServer:
def __init__(self, loop=None):
if loop is not None:
self.loop = loop
else:
self.loop = asyncio.get_event_loop()
#asyncio.coroutine
def server_handler(self, reader, writer):
data = yield from reader.read(15)
msg = data.decode()
if data is not 'bye':
print('Received data: "{}" from {}'.format(msg, writer.get_extra_info('peername')))
print('Echoing the message...')
writer.write(data)
yield from writer.drain()
else:
print('Received data: "{}" from {}'.format(
data,
writer.get_extra_info('peername')
)
)
print('Closing the connection...')
writer.close()
loop = asyncio.get_event_loop()
stream_server = StreamServer(loop)
coro_server = asyncio.start_server(
stream_server.server_handler,
'127.0.0.1',
9871,
loop=stream_server.loop
)
server = loop.run_until_complete(coro_server)
print('Listening on:', server.sockets[0].getsockname())
try:
loop.run_forever()
except KeyboardInterrupt:
pass
print('Closing server')
server.close()
loop.run_until_complete(server.wait_closed())
loop.close()
Question:
On the heartbeat() method the line self.writer.write('heartbeat'.encode()) seems to never be executed.
How can I get it to work?
The code is being executed on the client side, you just don't have any code on the server-side to receive the message. server_handler is only called once per connection, not once per message. So, if you want it to be able to receive an infinite number of heartbeats from a given client, you need to set up a loop inside of server_handler to receive them:
#asyncio.coroutine
def server_handler(self, reader, writer):
while True: # Keep receiving data from client.
data = yield from reader.read(15)
msg = data.decode()
if data is not 'bye':
print('Received data: "{}" from {}'.format(msg, writer.get_extra_info('peername')))
print('Echoing the message...')
writer.write(data)
yield from writer.drain()
else:
print('Received data: "{}" from {}'.format(
data,
writer.get_extra_info('peername')
)
)
print('Closing the connection...')
writer.close()

Categories