Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

idle_done() Hangs When Network Connection Severed #449

Open
TheConcolor opened this issue Oct 24, 2021 · 5 comments
Open

idle_done() Hangs When Network Connection Severed #449

TheConcolor opened this issue Oct 24, 2021 · 5 comments
Assignees
Labels
Milestone

Comments

@TheConcolor
Copy link

While trying to find a good way to detect when there is no network connection while in a idle_check() loop, I discovered that if the network connection to the IMAP server is severed after idle() has been called, a call to idle_close() just hangs unless the network connection is restored.

with IMAPClient(host) as server:
    server.login(address, password)
    
    select = server.select_folder("INBOX", readonly=True)
    
    while True:

        i = 0

        print("IDLE starting...")
        server.idle()
        print("IDLE started")

        # Reset IDLE state every 30 seconds to ensure IMAP server connection exists
        while i < 6:
            responses = server.idle_check(timeout=5)
            print("Server sent:", responses if responses else "nothing")
            i += 1
        
        print("Stopping IDLE...")
        server.idle_done()
        print("IDLE stopped")

The purpose of stopping the IDLE state and restarting it every 30 seconds is to ensure a connection to the IMAP server still exists since idle_check() does not detect a connection loss.

Steps to Reproduce

  1. Connect to IMAP server and call idle()
  2. Disable network connection on client (I did this by disabling my wired NIC in NetworkManager)
  3. Code hangs at server.idle_done() while no network connection

Code Output

IDLE starting...
IDLE started
Server sent: nothing
Server sent: nothing
Server sent: nothing      # NIC disbled at this point
Server sent: nothing
Server sent: nothing
Server sent: nothing
Stopping IDLE...          # Code hangs here unless network connection restored

My Setup

  • Linux Mint 20
  • Python v3.8.10
  • IMAPClient v2.2.0 (installed via pip)
@mjs
Copy link
Owner

mjs commented Oct 26, 2021

Thanks for the detailed report. I’ll take a look.

@mjs mjs added this to the 2.2.0 milestone Oct 26, 2021
@mjs mjs added the bug label Oct 26, 2021
@mjs mjs self-assigned this Oct 26, 2021
@TheConcolor
Copy link
Author

TheConcolor commented Oct 26, 2021

If you need any more information for testing, just let me know. Thanks for taking a look!

@BoniLindsley
Copy link
Contributor

I can reproduce this issue, and the traceback suggests that it was stuck waiting for a response in line 975 from idle_done:

logger.debug("< DONE")
self._imap.send(b"DONE\r\n")
return self._consume_until_tagged_response(self._idle_tag, "IDLE")

This means the DONE request was sent "successfully" and it then waited on a disconnected socket. This makes sense because, with so little data, sending only fills the network buffer, and receiving can block indefinitely by default.

I believe the receiving part is expected behaviour that can be configured with IMAPClient(HOST, timeout=TIMEOUT). This blocking behaviour can be reproduced with select_folder as well.

I am uncertain whether DONE succeeding should be considered expected though.

@mjs
Copy link
Owner

mjs commented Nov 17, 2021

Sorry for my slow responses. I don't have much spare time these days.

My guess would be that if the server kills the connection such that a TCP reset arrives at the client, the read should fail because the client OS will terminate the connection. If packets between the server and client are just dropped then I could see idle_done getting stuck (and other commands too). This is somewhat expected. When packets are dropped it is impossible to distinguish a slow connection from a broken one.

Using a socket timeout is probably the best approach to deal with this kind of thing.

Can you think of anything that IMAPClient should be doing differently? Maybe a generous timeout should be the default?

@BoniLindsley
Copy link
Contributor

Using a socket timeout is probably the best approach to deal with this kind of thing.

So, like a timeout parameter to override on idle_done? Or expose the _timeout attribute for future requests?

Can you think of anything that IMAPClient should be doing differently? Maybe a generous timeout should be the default?

Nothing really comes to mind in terms of changing behaviour. And I am not sure there is a sensible default, with varying use cases. I would suggest a "no defeault" instead, to force the user to acknowledge that they have picked something, but that breaks existing code.

As for what can be changed... I am not sure. The only thing that comes to mind is, may be remind the user of the timeout in the documentation of idle_done? Or may be in "advanced usage" next to the IDLE example? I can imagine the example being used a lot as template code.

@mjs mjs modified the milestones: 2.3.0, 3.0.0 Jul 9, 2022
@mjs mjs modified the milestones: 3.0.0, 3.1.0 Oct 27, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants