Skip to content

Commit 828cc1f

Browse files
committed
fix(connectivity): return false from all_work_done() immediately after connecting
We do not want all_work_done() to return true immediately after calling start_io(), but only when connection goes idle. "Connected" state is set immediately after connecting to the server, but it does not mean there is nothing to do. This change make all_work_done() return false from the Connected state and introduces a new Idle connectivity state that is only set before connection actually goes idle. For idle state all_work_done() returns true. From the user point of view both old Connected state and new Idle state look the same.
1 parent 57f4958 commit 828cc1f

File tree

3 files changed

+57
-12
lines changed

3 files changed

+57
-12
lines changed

python/tests/test_1_online.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1979,6 +1979,32 @@ def test_connectivity(acfactory, lp):
19791979
ac1._evtracker.wait_for_connectivity(dc.const.DC_CONNECTIVITY_NOT_CONNECTED)
19801980

19811981

1982+
def test_all_work_done(acfactory, lp):
1983+
"""
1984+
Tests that calling start_io() immediately followed by maybe_network()
1985+
and then waiting for all_work_done() reliably fetches the messages
1986+
delivered while account was offline.
1987+
In other words, connectivity should not change to a state
1988+
where all_work_done() returns true until IMAP connection goes idle.
1989+
"""
1990+
ac1, ac2 = acfactory.get_online_accounts(2)
1991+
1992+
ac1.stop_io()
1993+
ac1._evtracker.wait_for_connectivity(dc.const.DC_CONNECTIVITY_NOT_CONNECTED)
1994+
1995+
ac1.direct_imap.select_config_folder("inbox")
1996+
with ac1.direct_imap.idle() as idle1:
1997+
ac2.create_chat(ac1).send_text("Hi")
1998+
idle1.wait_for_new_message()
1999+
2000+
ac1.start_io()
2001+
ac1.maybe_network()
2002+
ac1._evtracker.wait_for_all_work_done()
2003+
msgs = ac1.create_chat(ac2).get_messages()
2004+
assert len(msgs) == 1
2005+
assert msgs[0].text == "Hi"
2006+
2007+
19822008
def test_fetch_deleted_msg(acfactory, lp):
19832009
"""This is a regression test: Messages with \\Deleted flag were downloaded again and again,
19842010
hundreds of times, because uid_next was not updated.

src/scheduler.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -590,7 +590,7 @@ async fn fetch_idle(ctx: &Context, connection: &mut Imap, folder_meaning: Folder
590590
.log_err(ctx)
591591
.ok();
592592

593-
connection.connectivity.set_connected(ctx).await;
593+
connection.connectivity.set_idle(ctx).await;
594594

595595
ctx.emit_event(EventType::ImapInboxIdle);
596596
let Some(session) = connection.session.take() else {
@@ -727,7 +727,7 @@ async fn smtp_loop(
727727
// Fake Idle
728728
info!(ctx, "smtp fake idle - started");
729729
match &connection.last_send_error {
730-
None => connection.connectivity.set_connected(&ctx).await,
730+
None => connection.connectivity.set_idle(&ctx).await,
731731
Some(err) => connection.connectivity.set_err(&ctx, err).await,
732732
}
733733

src/scheduler/connectivity.rs

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,18 @@ enum DetailedConnectivity {
3333
#[default]
3434
Uninitialized,
3535
Connecting,
36+
37+
/// Connection is just established, but there may be work to do.
38+
Connected,
39+
40+
/// There is actual work to do, e.g. there are messages in SMTP queue
41+
/// or we detected a message that should be downloaded.
3642
Working,
43+
3744
InterruptingIdle,
38-
Connected,
45+
46+
/// Connection is established and is idle.
47+
Idle,
3948

4049
/// The folder was configured not to be watched or configured_*_folder is not set
4150
NotConfigured,
@@ -54,6 +63,8 @@ impl DetailedConnectivity {
5463
// Just don't return a connectivity, probably the folder is configured not to be
5564
// watched or there is e.g. no "Sent" folder, so we are not interested in it
5665
DetailedConnectivity::NotConfigured => None,
66+
67+
DetailedConnectivity::Idle => Some(Connectivity::Connected),
5768
}
5869
}
5970

@@ -65,7 +76,8 @@ impl DetailedConnectivity {
6576
DetailedConnectivity::Connecting => "<span class=\"yellow dot\"></span>".to_string(),
6677
DetailedConnectivity::Working
6778
| DetailedConnectivity::InterruptingIdle
68-
| DetailedConnectivity::Connected => "<span class=\"green dot\"></span>".to_string(),
79+
| DetailedConnectivity::Connected
80+
| DetailedConnectivity::Idle => "<span class=\"green dot\"></span>".to_string(),
6981
}
7082
}
7183

@@ -75,9 +87,9 @@ impl DetailedConnectivity {
7587
DetailedConnectivity::Uninitialized => "Not started".to_string(),
7688
DetailedConnectivity::Connecting => stock_str::connecting(context).await,
7789
DetailedConnectivity::Working => stock_str::updating(context).await,
78-
DetailedConnectivity::InterruptingIdle | DetailedConnectivity::Connected => {
79-
stock_str::connected(context).await
80-
}
90+
DetailedConnectivity::InterruptingIdle
91+
| DetailedConnectivity::Connected
92+
| DetailedConnectivity::Idle => stock_str::connected(context).await,
8193
DetailedConnectivity::NotConfigured => "Not configured".to_string(),
8294
}
8395
}
@@ -94,9 +106,9 @@ impl DetailedConnectivity {
94106
// We don't know any more than that the last message was sent successfully;
95107
// since sending the last message, connectivity could have changed, which we don't notice
96108
// until another message is sent
97-
DetailedConnectivity::InterruptingIdle | DetailedConnectivity::Connected => {
98-
stock_str::last_msg_sent_successfully(context).await
99-
}
109+
DetailedConnectivity::InterruptingIdle
110+
| DetailedConnectivity::Connected
111+
| DetailedConnectivity::Idle => stock_str::last_msg_sent_successfully(context).await,
100112
DetailedConnectivity::NotConfigured => "Not configured".to_string(),
101113
}
102114
}
@@ -108,8 +120,9 @@ impl DetailedConnectivity {
108120
DetailedConnectivity::Connecting => false,
109121
DetailedConnectivity::Working => false,
110122
DetailedConnectivity::InterruptingIdle => false,
111-
DetailedConnectivity::Connected => true,
123+
DetailedConnectivity::Connected => false, // Just connected, there may still be work to do.
112124
DetailedConnectivity::NotConfigured => true,
125+
DetailedConnectivity::Idle => true,
113126
}
114127
}
115128
}
@@ -141,6 +154,9 @@ impl ConnectivityStore {
141154
pub(crate) async fn set_not_configured(&self, context: &Context) {
142155
self.set(context, DetailedConnectivity::NotConfigured).await;
143156
}
157+
pub(crate) async fn set_idle(&self, context: &Context) {
158+
self.set(context, DetailedConnectivity::Idle).await;
159+
}
144160

145161
async fn get_detailed(&self) -> DetailedConnectivity {
146162
self.0.lock().await.deref().clone()
@@ -164,6 +180,7 @@ pub(crate) async fn idle_interrupted(inbox: ConnectivityStore, oboxes: Vec<Conne
164180
// return Connected until DC is completely done with fetching folders; this also
165181
// includes scan_folders() which happens on the inbox thread.
166182
if *connectivity_lock == DetailedConnectivity::Connected
183+
|| *connectivity_lock == DetailedConnectivity::Idle
167184
|| *connectivity_lock == DetailedConnectivity::NotConfigured
168185
{
169186
*connectivity_lock = DetailedConnectivity::InterruptingIdle;
@@ -172,7 +189,9 @@ pub(crate) async fn idle_interrupted(inbox: ConnectivityStore, oboxes: Vec<Conne
172189

173190
for state in oboxes {
174191
let mut connectivity_lock = state.0.lock().await;
175-
if *connectivity_lock == DetailedConnectivity::Connected {
192+
if *connectivity_lock == DetailedConnectivity::Connected
193+
|| *connectivity_lock == DetailedConnectivity::Idle
194+
{
176195
*connectivity_lock = DetailedConnectivity::InterruptingIdle;
177196
}
178197
}

0 commit comments

Comments
 (0)