mirror of
https://github.com/chatmail/core.git
synced 2026-09-22 13:01:21 +03:00
fix: make background_fetch not wait on or trigger smtp connections
Also adds tests and docs to respective functions, clarifying background fetching behaviour and the `ACCOUNTS_BACKGROUND_FETCH_DONE` event, that came up in questions/discussions with UI devs lately.
This commit is contained in:
@@ -482,11 +482,15 @@ impl Accounts {
|
||||
/// return immediately even before the timeout expiration
|
||||
/// or finishing fetching.
|
||||
///
|
||||
/// Pending outgoing messages are not waited for and not triggered.
|
||||
///
|
||||
/// The `AccountsBackgroundFetchDone` event is emitted at the end,
|
||||
/// process all events until you get this one and you can safely return to the background
|
||||
/// without forgetting to create notifications caused by timing race conditions.
|
||||
/// If another background fetch is already running,
|
||||
/// nothing is fetched and the event is emitted immediately.
|
||||
/// The event carries no data identifying the call it belongs to,
|
||||
/// so it only safely refers to your call if no concurrent background fetch is happening.
|
||||
///
|
||||
/// Returns a future that resolves when background fetch is done,
|
||||
/// but does not capture `&self`.
|
||||
|
||||
@@ -601,9 +601,13 @@ impl Context {
|
||||
|
||||
/// Does a single round of fetching messages from all transports and returns.
|
||||
///
|
||||
/// Can be used even if I/O is currently stopped.
|
||||
/// If I/O is stopped, fetches over a dedicated connection per transport
|
||||
/// and returns as soon as one of them fetched messages.
|
||||
/// If IO is stopped, pauses the scheduler and fetches over a dedicated connection
|
||||
/// per transport, returning as soon as one of them fetched messages.
|
||||
/// If IO is running, interrupts IMAP IDLE on all transports
|
||||
/// and waits until they are done fetching.
|
||||
///
|
||||
/// Does not wait for outgoing messages to be sent out,
|
||||
/// use [`crate::accounts::Accounts::is_sending_finished`] for that.
|
||||
pub async fn background_fetch(&self) -> Result<()> {
|
||||
if !(self.is_configured().await?) {
|
||||
return Ok(());
|
||||
@@ -613,8 +617,9 @@ impl Context {
|
||||
info!(self, "background_fetch started.");
|
||||
|
||||
if self.scheduler.is_running().await {
|
||||
self.scheduler.maybe_network().await;
|
||||
self.wait_for_all_work_done().await;
|
||||
self.scheduler.interrupt_inbox_idle().await;
|
||||
let include_smtp = false;
|
||||
self.wait_for_work_done(include_smtp).await;
|
||||
} else {
|
||||
self.scheduler.background_fetch_any(self).await?;
|
||||
}
|
||||
|
||||
@@ -363,6 +363,9 @@ pub enum EventType {
|
||||
/// A call made while another background fetch is running gets the event immediately,
|
||||
/// and the running fetch keeps emitting events until its own marker.
|
||||
///
|
||||
/// The event carries no data identifying the call it belongs to,
|
||||
/// so it is unambiguous only if there are no concurrent background fetch calls.
|
||||
///
|
||||
/// This event is only emitted by the account manager.
|
||||
AccountsBackgroundFetchDone,
|
||||
/// Inform that set of chats or the order of the chats in the chatlist has changed.
|
||||
|
||||
@@ -215,10 +215,17 @@ impl SchedulerState {
|
||||
|
||||
/// Indicate that the network likely has come back.
|
||||
pub(crate) async fn maybe_network(&self) {
|
||||
self.interrupt_inbox_idle().await;
|
||||
self.interrupt_smtp().await;
|
||||
}
|
||||
|
||||
/// Interrupts IDLE on all transports so that they fetch,
|
||||
/// and marks them as having work to do.
|
||||
pub(crate) async fn interrupt_inbox_idle(&self) {
|
||||
let inner = self.inner.read().await;
|
||||
let inboxes = match *inner {
|
||||
InnerSchedulerState::Started(ref scheduler) => {
|
||||
scheduler.maybe_network();
|
||||
scheduler.interrupt_inbox();
|
||||
scheduler
|
||||
.inboxes
|
||||
.iter()
|
||||
@@ -799,13 +806,6 @@ impl Scheduler {
|
||||
self.inboxes.iter()
|
||||
}
|
||||
|
||||
fn maybe_network(&self) {
|
||||
for b in self.boxes() {
|
||||
b.conn_state.interrupt();
|
||||
}
|
||||
self.interrupt_smtp();
|
||||
}
|
||||
|
||||
fn maybe_network_lost(&self) {
|
||||
for b in self.boxes() {
|
||||
b.conn_state.interrupt();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use core::fmt;
|
||||
use std::cmp::min;
|
||||
use std::{iter::once, ops::Deref, sync::Arc};
|
||||
use std::{ops::Deref, sync::Arc};
|
||||
|
||||
use anyhow::Result;
|
||||
use humansize::{BINARY, format_size};
|
||||
@@ -531,14 +531,15 @@ impl Context {
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
/// Returns true if all background work is done.
|
||||
async fn all_work_done(&self) -> bool {
|
||||
/// Returns true if all background work is done,
|
||||
/// checking the outgoing message queue only if `include_smtp` is set.
|
||||
async fn work_done(&self, include_smtp: bool) -> bool {
|
||||
let lock = self.scheduler.inner.read().await;
|
||||
let stores: Vec<_> = match *lock {
|
||||
InnerSchedulerState::Started(ref sched) => sched
|
||||
.boxes()
|
||||
.map(|b| &b.conn_state.state)
|
||||
.chain(once(&sched.smtp.state))
|
||||
.chain(include_smtp.then_some(&sched.smtp.state))
|
||||
.map(|state| state.connectivity.clone())
|
||||
.collect(),
|
||||
_ => return false,
|
||||
@@ -555,19 +556,26 @@ impl Context {
|
||||
|
||||
/// Waits until background work is finished.
|
||||
pub async fn wait_for_all_work_done(&self) {
|
||||
let include_smtp = true;
|
||||
self.wait_for_work_done(include_smtp).await
|
||||
}
|
||||
|
||||
/// Waits until background work is finished,
|
||||
/// checking the outgoing message queue only if `include_smtp` is set.
|
||||
pub(crate) async fn wait_for_work_done(&self, include_smtp: bool) {
|
||||
// Ideally we could wait for connectivity change events,
|
||||
// but sleep loop is good enough.
|
||||
|
||||
// First 100 ms sleep in chunks of 10 ms.
|
||||
for _ in 0..10 {
|
||||
if self.all_work_done().await {
|
||||
if self.work_done(include_smtp).await {
|
||||
break;
|
||||
}
|
||||
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
||||
}
|
||||
|
||||
// If we are not finished in 100 ms, keep waking up every 100 ms.
|
||||
while !self.all_work_done().await {
|
||||
while !self.work_done(include_smtp).await {
|
||||
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
||||
}
|
||||
}
|
||||
@@ -576,6 +584,34 @@ impl Context {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::test_utils::TestContext;
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_background_fetch_leaves_smtp_alone() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
alice.start_io().await;
|
||||
alice.wait_for_all_work_done().await;
|
||||
|
||||
let smtp = match *alice.scheduler.inner.read().await {
|
||||
InnerSchedulerState::Started(ref scheduler) => {
|
||||
scheduler.smtp.state.connectivity.clone()
|
||||
}
|
||||
_ => panic!("scheduler is not running"),
|
||||
};
|
||||
smtp.set_working(&alice);
|
||||
|
||||
alice.background_fetch().await?;
|
||||
assert!(!smtp.get_all_work_done());
|
||||
assert!(alice.scheduler.is_running().await);
|
||||
|
||||
alice
|
||||
.assert_warns_or_errors(&[
|
||||
"No IMAP connection candidates provided",
|
||||
"IMAP got rate limited",
|
||||
])
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_combine_connectivities() {
|
||||
|
||||
Reference in New Issue
Block a user