aiosmtplib icon indicating copy to clipboard operation
aiosmtplib copied to clipboard

client.connect() call hangs after server timeout

Open notypecheck opened this issue 1 year ago • 8 comments

Describe the bug It seems like at some point my smtp client is losing connection client.is_connected is None, and it hangs indefinitely if I call client.connect(). That problem may be related to specific SMTP server I'm using, but I don't know 🤷.

My question is: I keep SMTP instance in memory indefinitely, is that okay, or should I create a new client each time I need to send a message?

notypecheck avatar Jan 19 '24 08:01 notypecheck

client.is_connected should always be a boolean, and client.connect() shouldn't hang. Any idea what it's hanging on?

You can avoid the whole problem by using the send coroutine and creating a new connection each time, which is probably better practice anyway. But if you do have any more information, please provide it 🙂

cole avatar Feb 14 '24 03:02 cole

I'm not sure why client was hanging, that may have something to do with the SMTP server we use, which I have no info about. For now I fixed it by creating a new client each time, which seems to work 🤔

notypecheck avatar Feb 14 '24 03:02 notypecheck

Could you explain why creating a new connection would be considered best practice by the way?

notypecheck avatar Feb 14 '24 03:02 notypecheck

It's just a simple way to avoid a lot of error states from server timeouts, etc.

I wonder if there is an internal lock in the library that's getting stuck, I'll see if I can add some test cases around that.

cole avatar Feb 14 '24 03:02 cole

I tried writing a context manager like this:

@contextlib.asynccontextmanager
async def reconnect_smtp_client(
    client: aiosmtplib.SMTP,
    timeout: float = 15,
) -> AsyncIterator[None]:
    try:
        if not client.is_connected:
            logging.warning("SMTP client is not connected, reconnecting.")
            async with asyncio.timeout(timeout):
                await client.connect()
        yield
    except SMTPServerDisconnected:
        logging.warning("Caught SMTPServerDisconnected exception, reconnecting.")
        async with asyncio.timeout(timeout):
            await client.connect()
        raise

But as I said it hanged on client.connect() indefinitely (initially asyncio.timeout wasn't there)

notypecheck avatar Feb 14 '24 03:02 notypecheck

im also facing this issue my idea is to keep SMTP client instance in memory because i dont want to spend time connecting & doing login to a server each time i want to send a message

my steps to reproduce are:

  1. create SMTP client instance
smtp_client = SMTP(...)
  1. connect to an SMTP server (i use mailslurper for development) and send a message
if not smtp_client.is_connected:
   await smtp_client.connect(timeout=10)

smtp_client.send_message(message)
  1. intentionally stop SMTP server
  2. try to repeat step 2. when calling await smtp_client.connect the program hangs infinitely

alex-pirogov avatar Apr 10 '24 21:04 alex-pirogov

I think that's the exact problem I had

notypecheck avatar Apr 10 '24 22:04 notypecheck

i did some digging and found out that this is the place where program hangs so we need to explicitly release _connect_lock in order to be able to reconnect again this code seems to work

if not smtp_client.is_connected:
    if smtp_client._connect_lock and smtp_client._connect_lock.locked():
        smtp_client.close()

    await smtp_client.connect()

alex-pirogov avatar Apr 11 '24 08:04 alex-pirogov