Skip to content

Commit a7b69cd

Browse files
committed
fix: do not cancel the task returned from async_imap Handle.wait_with_timeout
This task is not guaranteed to be cancellation-safe and provides a stop token for safe cancellation instead. We should always cancel the task properly and not by racing against another future. Otherwise following call to Handle.done() may work on IMAP session that is in the middle of response, for example.
1 parent 0973a46 commit a7b69cd

File tree

1 file changed

+12
-16
lines changed

1 file changed

+12
-16
lines changed

src/imap/idle.rs

+12-16
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ use std::time::Duration;
33
use anyhow::{Context as _, Result};
44
use async_channel::Receiver;
55
use async_imap::extensions::idle::IdleResponse;
6-
use futures_lite::FutureExt;
76
use tokio::time::timeout;
87

98
use super::session::Session;
@@ -63,42 +62,39 @@ impl Session {
6362
handle.as_mut().set_read_timeout(None);
6463
let (idle_wait, interrupt) = handle.wait_with_timeout(IDLE_TIMEOUT);
6564

66-
enum Event {
67-
IdleResponse(IdleResponse),
68-
Interrupt,
69-
}
70-
7165
info!(
7266
context,
7367
"IDLE entering wait-on-remote state in folder {folder:?}."
7468
);
75-
let fut = idle_wait.map(|ev| ev.map(Event::IdleResponse)).race(async {
69+
70+
let interrupt_relay = tokio::spawn(async move {
7671
idle_interrupt_receiver.recv().await.ok();
7772

73+
info!(context, "{folder:?}: Received interrupt, stopping IDLE.");
74+
7875
// cancel imap idle connection properly
7976
drop(interrupt);
80-
81-
Ok(Event::Interrupt)
8277
});
8378

84-
match fut.await {
85-
Ok(Event::IdleResponse(IdleResponse::NewData(x))) => {
79+
match idle_wait.await {
80+
Ok(IdleResponse::NewData(x)) => {
8681
info!(context, "{folder:?}: Idle has NewData {x:?}");
8782
}
88-
Ok(Event::IdleResponse(IdleResponse::Timeout)) => {
83+
Ok(IdleResponse::Timeout) => {
8984
info!(context, "{folder:?}: Idle-wait timeout or interruption.");
9085
}
91-
Ok(Event::IdleResponse(IdleResponse::ManualInterrupt)) => {
86+
Ok(IdleResponse::ManualInterrupt) => {
9287
info!(context, "{folder:?}: Idle wait was interrupted manually.");
9388
}
94-
Ok(Event::Interrupt) => {
95-
info!(context, "{folder:?}: Idle wait was interrupted.");
96-
}
9789
Err(err) => {
9890
warn!(context, "{folder:?}: Idle wait errored: {err:?}.");
9991
}
10092
}
10193

94+
// Abort the task, then await to encure the future is dropped.
95+
interrupt_relay.abort();
96+
interrupt_relay.await.ok();
97+
10298
let mut session = tokio::time::timeout(Duration::from_secs(15), handle.done())
10399
.await
104100
.with_context(|| format!("{folder}: IMAP IDLE protocol timed out"))?

0 commit comments

Comments
 (0)