Repository navigation
Conversation
On first load a folder fetched its ~100 newest UIDs and then all older
headers in a single background FETCH. If that one FETCH failed, the folder
stayed incomplete for good: the failure was only logged ("will retry on
next request"), but later syncs saw a non-empty cache and only fetched
mail newer than the cached max UID.
In practice a single message is enough to break it: an attachment
filename sent as a literal containing a raw 8-bit byte (0xAE) makes the
IMAP parser reject the BODYSTRUCTURE and drop the connection, aborting the
whole FETCH. On a 29k-message INBOX only 76 messages ever appeared.
- Sync worker: when a previously loaded folder has fewer cached messages
than the server's EXISTS, fetch the missing UIDs newest first in batches
of 500 (max 5000 per folder per cycle), storing each batch as it
arrives.
- A failed batch is bisected; a single UID that still fails is fetched
without BODYSTRUCTURE (new ImapClient::fetch_headers_basic), or skipped
with a warning. Backfill stops for the cycle after 20 consecutive
failures (server likely unreachable).
- Cold start now hands older headers to this backfill instead of its own
one-shot task, and falls back to fetch_headers_basic if the recent-
headers fetch fails, so the folder still opens.
- Shared upsert/index helpers in sync.rs replace duplicated code.
Never-opened folders (nothing cached) are not backfilled.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
On a mailbox with ~29,000 messages in INBOX, Rav only ever showed 76 of them.
Other large folders (Sent, 23,650) loaded fine.
On first load,
list_messagesfetches the ~100 newest UIDs, then spawns onebackground
UID FETCH 1:<n>for all older headers. If that single fetchfails, the folder stays incomplete for good:
Background sync: failed (will retry on next request)),but the retry never happens: the cache is no longer empty, so later
requests take the "stale" path;
highest cached UID, so older gaps are never filled.
The trigger here was one message whose BODYSTRUCTURE has an attachment
filename sent as a literal with a raw 8-bit byte (Latin-1
®,0xAE):The IMAP parser rejects it (
TakeWhile1), the connection drops, and thewhole 120k-UID fetch is lost. A second folder (Junk) failed the same one-shot
fetch on
database is locked, and was never retried either.Change
Gap-filling backfill in the sync worker (
realtime/sync.rs)(cached > 0) has fewer cached messages than the server's
EXISTS, theworker fetches the UID list, computes the missing UIDs, and fetches their
headers newest first in batches of 500, storing each batch as it
arrives.
delay new mail in other folders; the remainder follows on later cycles.
Resilient to unparseable messages
the new
ImapClient::fetch_headers_basic: the same items minusBODYSTRUCTURE, so the message is listed and opens normally, just withoutthe attachment flag. If that fails too, the UID is skipped with a warning.
server is then likely unreachable rather than one message being bad.
Cold start (
routes/messages/mod.rs)separate one-shot task (
sync_remaining_headersremoved).fetch_headers_basic, so a bad message among the newest 100 no longermakes the folder impossible to open.
Refactoring
RealImapClient::fetch_headers_with_itemsholds the shared fetch/parsecode for
fetch_headersandfetch_headers_basic.upsert_headers/index_headershelpers insync.rsreplace the inlinecopy in
fetch_and_store_new.Tests
sync_backfills_older_messages_missing_from_cache: a folder with only itsnewest message cached and unchanged MODSEQ gets all 1,200 older messages
over three batches, including one whose full header fetch always fails,
which is stored without the attachment flag. Verified to fail with the gap
fill disabled.
sync_does_not_backfill_never_loaded_foldersto_uid_set_collapses_consecutive_runsMockImapClientnow honours the requested UID set, and can simulateunparseable UIDs (
with_unparseable_uids).cargo test: all pass.Verified against the affected 29k-message mailbox (Dovecot 2.3): the INBOX
filled in completely within a few minutes, and the problem message is listed.
Known limitation
A message that can't be fetched even without BODYSTRUCTURE stays missing,
and is retried (with a warning) on each sync cycle.
Note: CI clippy currently fails on
mainwith Rust 1.99 for unrelatedreasons; that's fixed separately in fix/clippy-rust-1.99.
🤖 Generated with Claude Code