IMAP

The IMAP protocol implementation is the most complicated in the mailnews code, though IMAP is by far the most complicated protocol as well.

The IMAP implementation is the only multi-threaded protocol in mailnews. The other protocols use necko to do the actual socket i/o on a separate thread, but everything else, including the parsing of the server responses, happens on the main UI thread. In IMAP, we spin up a new thread for every connection to the server, and read from the input nsPipe and parse the responses on that thread. This allows us to write code as if the sending of commands and the retrieving of responses was synchronous, e.g., the code at nsImapProtocol::SelectMailbox where we send the select mailbox command, and parse the server response in the next line of code. In contrast, POP3/NNTP/SMTP all send commands and receive responses using a state machine.

At the time the IMAP code was originally written, it was felt that the state machine approach would just be too complicated, and make the code hard to maintain. The trade-off is that we have to deal with thread safety issues, deadlock, etc. The biggest problem we have right now is that there are times when we try to shutdown connections from the UI thread, and we end up calling methods on the UI thread that should only be called from the IMAP thread.

Generally, almost all the code in nsImapProtocol, and all the parsing code (nsImapServerResponseParser, nsImapGenericParser, and nsImapBodyShell) is run on the imap connection thread. When an imap thread needs to communicate with the UI thread, the nsImapProtocol object uses one of three xpcom proxy sink objects - nsIImapMailFolderSink, nsIImapServerSink, and nsIImapMessageSink. These sinks allow the nsImapProtocol object to make synchronous method calls to the UI thread. nsImapIncomingServer implements the nsIImapServerSink, and nsImapMailFolder implements the nsIImapMailFolderSink and the nsIImapMessageSink.

Running an IMAP url is somewhat complicated for a few reasons. By default, we will create and cache up to five connections per server, but no more (this limit is user-configurable). We want to make sure that we don't ever get two connections selecting the same imap folder, and we want to keep a cached connection to the INBOX if at all possible. The nsImapIncomingServer object is generally responsible for handling these things. When the nsImapService wants to run a url, it calls nsImapIncomingServer::GetImapConnectionAndLoadUrl(). If the url needs to select an imap folder, this code checks if any existing connection already has that folder selected, or is running a url that will select that folder. If an existing connection has the folder selected, but isn't running a url, then we use the existing connection to run the new url. If no existing connection has or will select the folder, we either create a new connection to select the folder, if we're under the limit, or we use an existing connection. If all connections are busy, or a connection is busy with a folder, we queue the new url, and run it when a connection becomes available, and no connections are busy with the folder.

To actually run a url with an nsImapProtocol object, the ui thread calls nsImapProtocol::LoadImapUrl. This sets m_runningUrl to the new url and m_nextUrlReadyToRun to TRUE, and notifies the m_urlReadyToRunMonitor monitor, which the free imap thread is spinning waiting for. The imap thread then takes that url and processes it.

When a url finishes, we attempt to run any queued urls.

We cache connections because it can be quite expensive for both the client and the server to build up a connected state, especially when a folder is selected, and most operations require a folder to be selected. But this can cause problems when either the network or the server drops a cached connection, or the connection times out. We try to detect if a connection is really alive before we use it, but this check isn't always reliable. Often the only way we find out a connection is bad is that we try to write data to it, and it fails. So we have a mechanism for retrying urls when we get an error doing i/o. We re-use the same nsImapProtocol object, but make it reconnect to the server, and then re-run the url.

One other connection issue - Courier servers, by default, only allow 4 connections from the same IP address. Since by default, we cache 5 connections, the Courier server will kick off the 5th connection abruptly. We try to tell the user how to change the setting when this happens, but it's always an issue. We don't currently do sniffing of server greetings, but we might be able to handle this more gracefully/automatically by automatically adjusting the connection limit, especially when we know it's a Courier server.

Document Tags and Contributors

Tags:
Contributors to this page: wsmwk, Kohei, DavidBienvenu
Last updated by: wsmwk,