feat: improve IMAP loop logs

Only inbox loop is changed because non-inbox loop is going to be removed
together with `mvbox_move`.

Added transport IDs to the log and logging around quota updates.
Removed some logs that add noise,
like logging that IDLE is supported each time right before using it.
This commit is contained in:
link2xt
2026-03-24 05:43:20 +01:00
committed by l
parent 76e2c36d85
commit c99b8a4482
6 changed files with 90 additions and 30 deletions

View File

@@ -296,6 +296,11 @@ impl Imap {
Ok(imap) Ok(imap)
} }
/// Returns transport ID of the IMAP client.
pub fn transport_id(&self) -> u32 {
self.transport_id
}
/// Connects to IMAP server and returns a new IMAP session. /// Connects to IMAP server and returns a new IMAP session.
/// ///
/// Calling this function is not enough to perform IMAP operations. Use [`Imap::prepare`] /// Calling this function is not enough to perform IMAP operations. Use [`Imap::prepare`]
@@ -551,8 +556,13 @@ impl Imap {
folder: &str, folder: &str,
folder_meaning: FolderMeaning, folder_meaning: FolderMeaning,
) -> Result<bool> { ) -> Result<bool> {
let transport_id = session.transport_id();
if should_ignore_folder(context, folder, folder_meaning).await? { if should_ignore_folder(context, folder, folder_meaning).await? {
info!(context, "Not fetching from {folder:?}."); info!(
context,
"Transport {transport_id}: Not fetching from {folder:?}."
);
session.new_mail = false; session.new_mail = false;
return Ok(false); return Ok(false);
} }
@@ -563,7 +573,10 @@ impl Imap {
.with_context(|| format!("Failed to select folder {folder:?}"))?; .with_context(|| format!("Failed to select folder {folder:?}"))?;
if !session.new_mail { if !session.new_mail {
info!(context, "No new emails in folder {folder:?}."); info!(
context,
"Transport {transport_id}: No new emails in folder {folder:?}."
);
return Ok(false); return Ok(false);
} }
// Make sure not to return before setting new_mail to false // Make sure not to return before setting new_mail to false
@@ -1566,11 +1579,18 @@ impl Session {
return Ok(()); return Ok(());
} }
let transport_id = self.transport_id();
let Some(device_token) = context.push_subscriber.device_token().await else { let Some(device_token) = context.push_subscriber.device_token().await else {
return Ok(()); return Ok(());
}; };
if self.can_metadata() && self.can_push() { if self.can_metadata() && self.can_push() {
info!(
context,
"Transport {transport_id}: Subscribing for push notifications."
);
let old_encrypted_device_token = let old_encrypted_device_token =
context.get_config(Config::EncryptedDeviceToken).await?; context.get_config(Config::EncryptedDeviceToken).await?;

View File

@@ -27,6 +27,8 @@ impl Session {
idle_interrupt_receiver: Receiver<()>, idle_interrupt_receiver: Receiver<()>,
folder: &str, folder: &str,
) -> Result<Self> { ) -> Result<Self> {
let transport_id = self.transport_id();
self.select_with_uidvalidity(context, folder).await?; self.select_with_uidvalidity(context, folder).await?;
if self.drain_unsolicited_responses(context)? { if self.drain_unsolicited_responses(context)? {
@@ -36,13 +38,16 @@ impl Session {
if self.new_mail { if self.new_mail {
info!( info!(
context, context,
"Skipping IDLE in {folder:?} because there may be new mail." "Transport {transport_id}: Skipping IDLE in {folder:?} because there may be new mail."
); );
return Ok(self); return Ok(self);
} }
if let Ok(()) = idle_interrupt_receiver.try_recv() { if let Ok(()) = idle_interrupt_receiver.try_recv() {
info!(context, "Skip IDLE in {folder:?} because we got interrupt."); info!(
context,
"Transport {transport_id}: Skip IDLE in {folder:?} because we got interrupt."
);
return Ok(self); return Ok(self);
} }
@@ -61,7 +66,7 @@ impl Session {
info!( info!(
context, context,
"IDLE entering wait-on-remote state in folder {folder:?}." "Transport {transport_id}: IDLE entering wait-on-remote state in folder {folder:?}."
); );
// Spawn a task to relay interrupts from `idle_interrupt_receiver` // Spawn a task to relay interrupts from `idle_interrupt_receiver`

View File

@@ -71,13 +71,18 @@ impl ImapSession {
self.select(folder).await self.select(folder).await
}; };
let transport_id = self.transport_id();
// <https://tools.ietf.org/html/rfc3501#section-6.3.1> // <https://tools.ietf.org/html/rfc3501#section-6.3.1>
// says that if the server reports select failure we are in // says that if the server reports select failure we are in
// authenticated (not-select) state. // authenticated (not-select) state.
match res { match res {
Ok(mailbox) => { Ok(mailbox) => {
info!(context, "Selected folder {folder:?}."); info!(
context,
"Transport {transport_id}: Selected folder {folder:?}."
);
self.selected_folder = Some(folder.to_string()); self.selected_folder = Some(folder.to_string());
self.selected_mailbox = Some(mailbox); self.selected_mailbox = Some(mailbox);
Ok(NewlySelected::Yes) Ok(NewlySelected::Yes)

View File

@@ -136,6 +136,7 @@ impl PushSubscriber {
return Ok(()); return Ok(());
}; };
info!(context, "Subscribing for heartbeat notifications.");
if http::post_string( if http::post_string(
context, context,
"https://notifications.delta.chat/register", "https://notifications.delta.chat/register",
@@ -143,6 +144,7 @@ impl PushSubscriber {
) )
.await? .await?
{ {
info!(context, "Subscribed for heartbeat notifications.");
state.heartbeat_subscribed = true; state.heartbeat_subscribed = true;
} }
Ok(()) Ok(())

View File

@@ -123,6 +123,10 @@ impl Context {
/// in case for some providers the quota is always at ~100% /// in case for some providers the quota is always at ~100%
/// and new space is allocated as needed. /// and new space is allocated as needed.
pub(crate) async fn update_recent_quota(&self, session: &mut ImapSession) -> Result<()> { pub(crate) async fn update_recent_quota(&self, session: &mut ImapSession) -> Result<()> {
let transport_id = session.transport_id();
info!(self, "Transport {transport_id}: Updating quota.");
let quota = if session.can_check_quota() { let quota = if session.can_check_quota() {
let folders = get_watched_folders(self).await?; let folders = get_watched_folders(self).await?;
get_unique_quota_roots_and_usage(session, folders).await get_unique_quota_roots_and_usage(session, folders).await
@@ -149,18 +153,22 @@ impl Context {
.await?; .await?;
} }
} }
Err(err) => warn!(self, "cannot get highest quota usage: {:#}", err), Err(err) => warn!(
self,
"Transport {transport_id}: Cannot get highest quota usage: {err:#}"
),
} }
} }
self.quota.write().await.insert( self.quota.write().await.insert(
session.transport_id(), transport_id,
QuotaInfo { QuotaInfo {
recent: quota, recent: quota,
modified: tools::Time::now(), modified: tools::Time::now(),
}, },
); );
info!(self, "Transport {transport_id}: Updated quota.");
self.emit_event(EventType::ConnectivityChanged); self.emit_event(EventType::ConnectivityChanged);
Ok(()) Ok(())
} }

View File

@@ -355,6 +355,7 @@ async fn inbox_loop(
stop_token, stop_token,
} = inbox_handlers; } = inbox_handlers;
let transport_id = connection.transport_id();
let ctx1 = ctx.clone(); let ctx1 = ctx.clone();
let fut = async move { let fut = async move {
let ctx = ctx1; let ctx = ctx1;
@@ -368,23 +369,34 @@ async fn inbox_loop(
let session = if let Some(session) = old_session.take() { let session = if let Some(session) = old_session.take() {
session session
} else { } else {
info!(ctx, "Preparing new IMAP session for inbox."); info!(
ctx,
"Transport {transport_id}: Preparing new IMAP session for inbox."
);
match connection.prepare(&ctx).await { match connection.prepare(&ctx).await {
Err(err) => { Err(err) => {
warn!(ctx, "Failed to prepare inbox connection: {err:#}."); warn!(
ctx,
"Transport {transport_id}: Failed to prepare inbox connection: {err:#}."
);
continue; continue;
} }
Ok(session) => session, Ok(session) => {
info!(
ctx,
"Transport {transport_id}: Prepared new IMAP session for inbox."
);
session
}
} }
}; };
match inbox_fetch_idle(&ctx, &mut connection, session).await { match inbox_fetch_idle(&ctx, &mut connection, session).await {
Err(err) => warn!(ctx, "Failed inbox fetch_idle: {err:#}."), Err(err) => warn!(
ctx,
"Transport {transport_id}: Failed inbox fetch_idle: {err:#}."
),
Ok(session) => { Ok(session) => {
info!(
ctx,
"IMAP loop iteration for inbox finished, keeping the session."
);
old_session = Some(session); old_session = Some(session);
} }
} }
@@ -394,7 +406,7 @@ async fn inbox_loop(
stop_token stop_token
.cancelled() .cancelled()
.map(|_| { .map(|_| {
info!(ctx, "Shutting down inbox loop."); info!(ctx, "Transport {transport_id}: Shutting down inbox loop.");
}) })
.race(fut) .race(fut)
.await; .await;
@@ -431,17 +443,25 @@ pub async fn convert_folder_meaning(
} }
async fn inbox_fetch_idle(ctx: &Context, imap: &mut Imap, mut session: Session) -> Result<Session> { async fn inbox_fetch_idle(ctx: &Context, imap: &mut Imap, mut session: Session) -> Result<Session> {
let transport_id = session.transport_id();
// Update quota no more than once a minute. // Update quota no more than once a minute.
if ctx.quota_needs_update(session.transport_id(), 60).await if ctx.quota_needs_update(session.transport_id(), 60).await
&& let Err(err) = ctx.update_recent_quota(&mut session).await && let Err(err) = ctx.update_recent_quota(&mut session).await
{ {
warn!(ctx, "Failed to update quota: {:#}.", err); warn!(
ctx,
"Transport {transport_id}: Failed to update quota: {err:#}."
);
} }
if let Ok(()) = imap.resync_request_receiver.try_recv() if let Ok(()) = imap.resync_request_receiver.try_recv()
&& let Err(err) = session.resync_folders(ctx).await && let Err(err) = session.resync_folders(ctx).await
{ {
warn!(ctx, "Failed to resync folders: {:#}.", err); warn!(
ctx,
"Transport {transport_id}: Failed to resync folders: {err:#}."
);
imap.resync_request_sender.try_send(()).ok(); imap.resync_request_sender.try_send(()).ok();
} }
@@ -456,7 +476,10 @@ async fn inbox_fetch_idle(ctx: &Context, imap: &mut Imap, mut session: Session)
} }
} }
Err(err) => { Err(err) => {
warn!(ctx, "Failed to get last housekeeping time: {}", err); warn!(
ctx,
"Transport {transport_id}: Failed to get last housekeeping time: {err:#}"
);
} }
}; };
@@ -486,6 +509,8 @@ async fn fetch_idle(
mut session: Session, mut session: Session,
folder_meaning: FolderMeaning, folder_meaning: FolderMeaning,
) -> Result<Session> { ) -> Result<Session> {
let transport_id = session.transport_id();
let Some((folder_config, watch_folder)) = convert_folder_meaning(ctx, folder_meaning).await? let Some((folder_config, watch_folder)) = convert_folder_meaning(ctx, folder_meaning).await?
else { else {
// The folder is not configured. // The folder is not configured.
@@ -537,7 +562,7 @@ async fn fetch_idle(
if !session.can_idle() { if !session.can_idle() {
info!( info!(
ctx, ctx,
"IMAP session does not support IDLE, going to fake idle." "Transport {transport_id}: IMAP session does not support IDLE, going to fake idle."
); );
connection.fake_idle(ctx, watch_folder).await?; connection.fake_idle(ctx, watch_folder).await?;
return Ok(session); return Ok(session);
@@ -550,15 +575,14 @@ async fn fetch_idle(
.log_err(ctx) .log_err(ctx)
.unwrap_or_default() .unwrap_or_default()
{ {
info!(ctx, "IMAP IDLE is disabled, going to fake idle."); info!(
ctx,
"Transport {transport_id}: IMAP IDLE is disabled, going to fake idle."
);
connection.fake_idle(ctx, watch_folder).await?; connection.fake_idle(ctx, watch_folder).await?;
return Ok(session); return Ok(session);
} }
info!(
ctx,
"IMAP session in folder {watch_folder:?} supports IDLE, using it."
);
let session = session let session = session
.idle( .idle(
ctx, ctx,
@@ -619,10 +643,6 @@ async fn simple_imap_loop(
match fetch_idle(&ctx, &mut connection, session, folder_meaning).await { match fetch_idle(&ctx, &mut connection, session, folder_meaning).await {
Err(err) => warn!(ctx, "Failed fetch_idle: {err:#}"), Err(err) => warn!(ctx, "Failed fetch_idle: {err:#}"),
Ok(session) => { Ok(session) => {
info!(
ctx,
"IMAP loop iteration for {folder_meaning} finished, keeping the session"
);
old_session = Some(session); old_session = Some(session);
} }
} }