IdleManager.watch() returns before IDLE state is established
Current Behavior
The IdleManager.watch(folder) method returns immediately after queuing the folder for IDLE monitoring, before the IDLE state is actually established. This makes it impossible to know when the IMAP IDLE connection is ready to receive messages.
Debug logs show this sequence:
DEBUG IMAP: IdleManager.watch startIdle succeeded for imaps://%[email protected]/INBOX // watch() returns here DEBUG IMAP: IdleManager selected 0 channels DEBUG IMAP: IdleManager adding imaps://%[email protected]/INBOX to selector DEBUG IMAP: IdleManager waiting... // IDLE is actually ready here.
It is easy to make the mistake of thinking IDLE is ready when it isn't as watch() succeeds prematurely. We understand the reasoning on why watch() exits prematurely - it is due to the executor running in parallel however we must be able to wait for IDLE to succeed before continuing.
Expected Behavior
One of these options:
watch()should wait until IDLE is fully established- Add a new method like
watchAndWait()that blocks until IDLE is ready - Expose a way to query/listen for the IDLE ready state
Use Case
When setting up IMAP IDLE monitoring, applications need to know when the connection is actually ready to receive messages. Currently there's no clean way to detect this without resorting to implementation details or log parsing.
Mail server:
It is an issue with the angus-mail itself and not the mail server.
- Protocol being used: imap
- Mail service URL: gmail.com
Additional context Currently, it appears the only way to wait for an IDLE state to be ready is via "hacks".
@jmehrens what do you think?
@brr53 IMAPFolder::idle is documented with:
The mail.imap.minidletime property enforces a minimum delay before returning from this method, to ensure that other threads have a chance to issue commands before the caller invokes this method again. The default delay is 10 milliseconds.
Makes me wonder if bug here is that IdleManager::watch should also respect mail.imap.minidletime property. That would at least allow you to feed a backoff property via the Session given to the IdleManager. Would such a backoff property be sufficient for what you are doing or are you requiring a proper join?
If not then I would think that we could go with adding something like public boolean watch(Folder, long delay) throws MessagingException method that returns if the delay was exceeded. So I'm clear, the signal that you want to wait for is that SocketChannel::register completed?
@jmehrens I'm not sure I understand the purpose of a backoff property. Are you suggesting we poll if IDLE is ready or not? Typically polling is not a good solution. A proper system where we block off or have a Future for readiness would be great. I think when we call watch() it should not return immediately. Instead, the watch method should return if the watch fails or succeeds.
public boolean watch(Folder, long delay) throws MessagingException
I think this would be fine.
the signal that you want to wait for is that SocketChannel::register completed
I haven't looked too deeply at the underlying code. However upon a debug analysis, the IDLE watch is actually ready at this stage: "DEBUG IMAP: IdleManager waiting...". I think the watch() function should succeed at the stage where the IDLE is actually ready for action. Currently, if we receive an email within a few seconds of calling watch(), the mail client will not receive it. Because it will arrive before IDLEing.