Hi Jan,
On 8 Jun 2012, at 17:02, Jan Kundr?t <jkt@flaska.net> wrote:
> Hi,
> a big part of this mail is probably answered in RFC5162's errata, but
> I'd still like someone to explicitly confirm that how I understand this
> thing is correct.
>
> I'm afraid I have a possible race condition in my code which keeps
> mailbox up to date with server's responses. Right now, my code supports
> CONDSTORE but not QRESYNC. The following bits describe how it behaves
> after the mailbox has been "synced", ie. when my idea about UID -> seq
> mapping matches with the state on server.
>
> In this situation, whenever the program needs to perform something (like
> send the LIST command, issue a FETCH for some body part, store some
> flags through UID STORE etc), the command will be sent to the IMAP
> server almost immediately, and this can in turn trigger many interesting
> responses like EXISTS or EXPUNGE. That's why I'm ready to process them
> at any point, no matter if there's a command in progress or not. I
> believe that I'm handling them correctly now:
>
> - Whenever I receive EXISTS >= current number of messages, I simply add
> the correct number of "empty messages" with UID zero to the end of my
> UID -> seq mapping and to some internal data structure which is in turn
> shown to the user who will see a few "Loading..." placeholders. I also
> immediately queue a UID FETCH previous_uidnext:* FLAGS command. The goal
> is that the UID number will eventually arrive and when they arrive, I
> update my on-disk cache with the obtained data.
>
> - When EXPUNGE arrives, I check the number for correctness (1 <= number
> < mailbox_size) and remove the message which is at that particular
> index. If its UID was not zero, I also remove any data associated with
> that UID from my on-disk cache. If the number was zero, well, the idea
> is that this shouldn't matter at all as I couldn't possibly have any
> useful data for that message in my cache (message parts, envelopes and
> body structure are fetched only after the UID is known).
>
> I'm not sure how to deal with it if I enable QRESYNC, though. The spec
> says that I should be ready for both EXPUNGE and VANISHED. I'd like to
> leave my handling of EXPUNGE unchanged if possible, which means that I
> really have to create and insert the fake UID-0 messages immediately in
> response to increased EXISTS.
>
> The problem is what shall I do when I get VANISHED with a UID which is
> bigger than the highest UID I already know? (Like if a message arrived
> and before I had a chance to ask for its UID, it gets removed through
> VANISHED.) At this point it looks like I can just pick any of the still
> unknown messages in my in-memory state and delete one of them for each
> high UID received in the VANISHED response. I'm a bit nervous about that
> approach, though -- consider the following situation (which is partially
> answered by the QRESYNC errata):
>
> (Mailbox contains one message with UID 5; UIDNEXT is 11, everything is
> synced.)
>
> S: * 12 EXISTS
> C: x UID FETCH 11:* (FLAGS)
>
> (At this point, server sends unsolicited data like FLAGS or even
> BODYSTRUCTURE for the new arrivals. I believe that it's allowed to do so
> per RFC3501.)
>
> S: * 2 FETCH (FLAGS (a) BODYSTRUCTURE ...)
> S: * 3 FETCH (FLAGS (b) ENVELOPE ...)
>
> (I'm on a slow network, so even before my UID FETCH arrives to the
> server, one of these messages is gone and the server sends back VANISHED:)
>
> S: * VANISHED 11
>
> (The programmer has flipped a coin and hence the code always deletes the
> very last message with UID=0. That means that the message flagged "b" is
> gone and "a" remains.)
>
> S: * 2 FETCH (FLAGS (b) UID 12)
>
> (At this point, client finally gets the UID for the only remaining
> message with UID=0, along with flags. The bad thing is that user has
> already seen a stub message flagged as "a" while in fact there was just
> the "b" one.)
Right, guessing which of the yet unknown to your client messages got expunged doesn't look right.
>
> After reading the errata, I realize that servers are discouraged from
> not sending UIDs in FETCH responses when QRESYNC has been enabled. Would
> it be reasonable for my client to always ignore FETCH updates which do
> not contain UID when QRESYNC is in effect?
Yes.
> (I can easily change my code
> to never issue a FETCH, always UID FETCH with QRESYNC.)
Good idea.
>
> My biggest concern is that my code perform potentially intrusive actions
> (like allocating complex structures for BODYSTRUCTURE and actually
> showing them to the user) upon seeing the FETCH responses for a given
> message for the first time. When I receive interleaved VANISHED and
> FETCH-without-UID responses, the result could be confusing to the user
> (and given that I already have an application where the messages are
> pushed into an external DB as soon as they arrive, I can imagine a use
> case where people would want to store only messages with a particular
> flag -- again, the code would get confused in the situation I described
> above.)
It looks like your code should get smarter if you don't want to just ignore FETCH responses with no UIDs.
>
> It seems to me that if the QRESYNC extension introduced new * ARRIVED
> <uid-sequence-set> response to replace EXISTS in a similar way that
> VANISHED replaces EXPUNGE, the window where client knows about new
> arrivals but doesn't know about the UIDs would cease to exist. Is there
> a particular reason why QRESYNC is not modeled in that way?
The idea didn't' occur at the time of writing the QRESYNC spec :).
>
> Finally, my last question is why does QRESYNC require me to issue an
> ENABLE QRESYNC at all? I feel like the SELECT ... QRESYNC should be
> enough to tell the server that I'm really expecting the VANISHED
> responses. My biggest issue with ENABLE is that (at least according to
> the last errata by Alfred Hoenes from 2008-03-11 which is "held for
> document update") I'm supposed to wait for untagged ENABLED before
> issuing SELECT ... QRESYNC which introduces another roundtrip to the
> connection process. I'm ignoring this requirement for now, so I'll
> happily pipeline ENABLE QRESYNC with SELECT ... QRESYNC and hope that I
> won't get BAD in response from some pedantic server. I'd love to get
> some comments on this as well.
ENABLE QRESYNC failing should be a rare event, so doing the optimization you do seems to be Ok, as long as you can handle the BAD response.
Best Regards,
Alexey