Opening messages is still very slow, even with all messages downloaded #148

Closed
opened 2026-07-02 19:59:02 +00:00 by JMR-dev · 0 comments
JMR-dev commented 2026-07-02 19:59:02 +00:00 (Migrated from github.com)

Context

ReaderViewModel.init (ui/reader/ReaderViewModel.kt, ~lines 67-88) awaits
repository.openMessage(messageId) before flipping loading to false, so nothing renders
until it returns.

MailRepositoryImpl.openMessage() (data/repository/MailRepositoryImpl.kt, ~lines 106-122): when
the body is already cached (entity.bodyFetched == true) but the message is unread, it still
runs imapClient.setFlag(params, entity.folder, uidOf(id), Flags.Flag.SEEN, true) — a live IMAP
round trip (connection setup + STORE command) — before returning, purely to mark the
message read on the server:

} else if (!entity.isRead) {
    runCatching { imapClient.setFlag(params, entity.folder, uidOf(id), Flags.Flag.SEEN, true) }
    messageDao.setRead(id, true)
}

That network call sits directly on the reader's critical path even though everything needed to
render the screen (body + attachments) is already fully local. This is very likely the dominant
cost behind "opening messages is still slow, even with all messages downloaded" — the body isn't
the bottleneck, the flag round trip is. It's related to, but distinct from, #125's investigation
of IMAP folder-open latency: this is specifically the message-open path's own network call.

Scope

  • Make server-side read-flag propagation asynchronous/best-effort: update
    messageDao.setRead(id, true) immediately (optimistic, local-only) and return from
    openMessage without waiting on imapClient.setFlag; push the SEEN flag to the server on a
    background coroutine (fire-and-forget), not on the path the reader screen awaits.
  • Make sure a failed or slow background SEEN-flag push doesn't leave local and server state
    permanently diverged — confirm whether the existing folder-sync flag-reconciliation logic
    already self-heals this (the row is already marked read locally; the next syncFolder/
    refresh should reconcile), or add an explicit retry path if it doesn't.
  • Re-measure reader-open latency before/after for an already-cached, unread message to confirm
    the fix addresses the reported slowness (vs. some other cost also being present).

Acceptance criteria

  • Opening an already-downloaded, unread message renders immediately, bounded only by local
    DB/disk reads — no network round trip on the critical path.
  • The SEEN flag still reaches the server on a best-effort basis.
  • A GreenMail-backed test asserts openMessage doesn't block its result on the setFlag call
    (e.g. by asserting timing, or by asserting the flag call is dispatched independently).

Relevant files

  • data/repository/MailRepositoryImpl.kt (~lines 106-122), ui/reader/ReaderViewModel.kt
    (~lines 60-96).

Dependencies

Related to #125 (IMAP folder-open latency investigation) — that ticket covers SELECT/EXAMINE
and header-fetch latency when opening a folder; this one is about the message-open path's own
network call. Worth fixing independently of whatever #125 concludes.

## Context `ReaderViewModel.init` (`ui/reader/ReaderViewModel.kt`, ~lines 67-88) awaits `repository.openMessage(messageId)` before flipping `loading` to `false`, so nothing renders until it returns. `MailRepositoryImpl.openMessage()` (`data/repository/MailRepositoryImpl.kt`, ~lines 106-122): when the body is already cached (`entity.bodyFetched == true`) but the message is unread, it still runs `imapClient.setFlag(params, entity.folder, uidOf(id), Flags.Flag.SEEN, true)` — a live IMAP round trip (connection setup + `STORE` command) — **before returning**, purely to mark the message read on the server: ```kotlin } else if (!entity.isRead) { runCatching { imapClient.setFlag(params, entity.folder, uidOf(id), Flags.Flag.SEEN, true) } messageDao.setRead(id, true) } ``` That network call sits directly on the reader's critical path even though everything needed to render the screen (body + attachments) is already fully local. This is very likely the dominant cost behind "opening messages is still slow, even with all messages downloaded" — the body isn't the bottleneck, the flag round trip is. It's related to, but distinct from, #125's investigation of IMAP *folder-open* latency: this is specifically the *message-open* path's own network call. ## Scope - [ ] Make server-side read-flag propagation asynchronous/best-effort: update `messageDao.setRead(id, true)` immediately (optimistic, local-only) and return from `openMessage` without waiting on `imapClient.setFlag`; push the SEEN flag to the server on a background coroutine (fire-and-forget), not on the path the reader screen awaits. - [ ] Make sure a failed or slow background SEEN-flag push doesn't leave local and server state permanently diverged — confirm whether the existing folder-sync flag-reconciliation logic already self-heals this (the row is already marked read locally; the next `syncFolder`/ `refresh` should reconcile), or add an explicit retry path if it doesn't. - [ ] Re-measure reader-open latency before/after for an already-cached, unread message to confirm the fix addresses the reported slowness (vs. some other cost also being present). ## Acceptance criteria - Opening an already-downloaded, unread message renders immediately, bounded only by local DB/disk reads — no network round trip on the critical path. - The SEEN flag still reaches the server on a best-effort basis. - A GreenMail-backed test asserts `openMessage` doesn't block its result on the `setFlag` call (e.g. by asserting timing, or by asserting the flag call is dispatched independently). ## Relevant files - `data/repository/MailRepositoryImpl.kt` (~lines 106-122), `ui/reader/ReaderViewModel.kt` (~lines 60-96). ## Dependencies Related to #125 (IMAP folder-open latency investigation) — that ticket covers `SELECT`/`EXAMINE` and header-fetch latency when opening a *folder*; this one is about the *message*-open path's own network call. Worth fixing independently of whatever #125 concludes.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: JMR-dev/LibreMail#148