-
-
Notifications
You must be signed in to change notification settings - Fork 137
fix various transport multi-device de-synchronizinzation issues #8499
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
ca32d39
56f49d1
13c8d4e
a5f60bd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -1,5 +1,8 @@ | ||||||||
| use std::cmp; | ||||||||
| use std::future::Future; | ||||||||
| use std::num::NonZeroUsize; | ||||||||
| use std::pin::Pin; | ||||||||
| use std::sync::atomic::Ordering; | ||||||||
|
|
||||||||
| use anyhow::{Context as _, Error, Result, bail}; | ||||||||
| use async_channel::{self as channel, Receiver, Sender}; | ||||||||
|
|
@@ -407,6 +410,12 @@ async fn inbox_loop( | |||||||
| .await; | ||||||||
| } | ||||||||
|
|
||||||||
| /// Same as `context.restart_io_if_running()`, but `Box::pin`ed and with a `+ Send` bound | ||||||||
| /// to break the async type cycle with the IMAP loop it restarts. | ||||||||
| fn restart_io_if_running_boxed(context: Context) -> Pin<Box<dyn Future<Output = ()> + Send>> { | ||||||||
| Box::pin(async move { context.restart_io_if_running().await }) | ||||||||
| } | ||||||||
|
|
||||||||
| async fn inbox_fetch_idle(ctx: &Context, imap: &mut Imap, mut session: Session) -> Result<Session> { | ||||||||
| let transport_id = session.transport_id(); | ||||||||
|
|
||||||||
|
|
@@ -491,6 +500,11 @@ async fn fetch_idle(ctx: &Context, connection: &mut Imap, mut session: Session) | |||||||
| .await | ||||||||
| .context("download_msgs")?; | ||||||||
|
|
||||||||
| if ctx.restart_io_after_fetch.swap(false, Ordering::Relaxed) { | ||||||||
| // Stopping IO from within the inbox loop would cancel it. | ||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
| task::spawn(restart_io_if_running_boxed(ctx.clone())); | ||||||||
| } | ||||||||
|
Comment on lines
+503
to
+506
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TL;DR: This won't solve the flakiness completely, it can just make it smaller (even though I didn't understand yet why restarting IO causes this problem at all). I have ideas for a proper solution, but they are bigger changes. If the fix here noticeably improves test flakiness, then it might be fine for now as a partial solution.
I'm afraid that we still have this problem, it might just happen less frequently (I didn't test whether it does). If one transport sets I didn't really understand yet why restarting IO causes problems at all, because The complete solution to protect against stops or restarts at a bad timing would be to prevent cancellation during receive_imf(). There are two possibilities for this:
Then again, a partial solution is better than no solution, and if this change removes the apparent test flakiness, then it might still be an improvement, even though the test is probably just less flaky rather than being actually stable.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Even if we stop racing against cancelled(), scheduler may still cancel the inbox loop with a timeout if shutting down takes too long, and we want to eventually shutdown the inbox loop e.g. if the connection is very slow and the inbox loop is stuck downloading the message for hours. Ideally most async code should be cancellation-safe with rare exceptions marked as such. If cancelling IMAP loop at the wrong moment results in some message never being downloaded, it is a bug somewhere down the IMAP loop code, e.g. if we record somewhere that the message is downloaded before running receive_imf to completion.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I mean, we do: We call
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we decide to make
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
here is the problem with main as i see it:
This leads to the flakyness of one or two Regular messages also don't get re-processed after You are right that Transport A and B concurrently fetching might cancel each other's message receiving mid-flight, But in the current code path, the only place that triggers concurrent cancellation from within |
||||||||
|
|
||||||||
| connection.connectivity.set_idle(ctx); | ||||||||
|
|
||||||||
| ctx.emit_event(EventType::ImapInboxIdle); | ||||||||
|
|
||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -115,6 +115,19 @@ fn dummy_configured_login_param(addr: &str) -> ConfiguredLoginParam { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async fn add_dummy_transport(t: &TestContext, addr: &str) -> Result<()> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| dummy_configured_login_param(addr) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .save_to_transports_table( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| &EnteredLoginParam { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| addr: addr.to_string(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ..Default::default() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| time(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .await | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #[tokio::test(flavor = "multi_thread", worker_threads = 2)] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async fn test_is_published_flag() -> Result<()> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let mut tcm = TestContextManager::new(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -138,16 +151,7 @@ async fn test_is_published_flag() -> Result<()> { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .await; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| dummy_configured_login_param("alice@otherprovider.com") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .save_to_transports_table( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| alice, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| &EnteredLoginParam { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| addr: "alice@otherprovider.com".to_string(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ..Default::default() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| time(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .await?; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| add_dummy_transport(alice, "alice@otherprovider.com").await?; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| send_sync_transports(alice).await?; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sync_and_check_recipients(alice, alice2, "alice@otherprovider.com alice@example.org").await; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -195,10 +199,7 @@ async fn test_is_published_flag() -> Result<()> { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SystemTime::shift(Duration::from_secs(2)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| alice | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .set_config(Config::ConfiguredAddr, Some("alice@otherprovider.com")) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .await?; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sync_and_check_recipients(alice, alice2, "alice@example.org alice@otherprovider.com").await; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| promote_transport_and_check_success(alice, alice2, "alice@otherprovider.com").await?; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| check_addrs( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| alice, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -215,6 +216,87 @@ async fn test_is_published_flag() -> Result<()> { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Ok(()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Tests that changing the primary transport propagates to other devices | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// even if the promoted transport was added within the same second. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #[tokio::test(flavor = "multi_thread", worker_threads = 2)] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async fn test_promote_transport_same_second() -> Result<()> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let mut tcm = TestContextManager::new(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let alice = &tcm.alice().await; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let alice2 = &tcm.alice().await; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for a in [alice, alice2] { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| a.set_config_bool(Config::SyncMsgs, true).await?; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| a.set_config_bool(Config::BccSelf, true).await?; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| add_dummy_transport(alice, "alice@otherprovider.com").await?; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| send_sync_transports(alice).await?; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sync_and_check_recipients(alice, alice2, "alice@otherprovider.com alice@example.org").await; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| promote_transport_and_check_success(alice, alice2, "alice@otherprovider.com").await | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Tests that `sync_transports()` requests an IO restart | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// if and only if it modified anything. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #[tokio::test(flavor = "multi_thread", worker_threads = 2)] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async fn test_sync_transports_requests_io_restart() -> Result<()> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let alice = &TestContext::new_alice().await; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let data = TransportData { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| configured: dummy_configured_login_param("alice@otherprovider.com").into(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| entered: EnteredLoginParam { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| addr: "alice@otherprovider.com".to_string(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ..Default::default() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| timestamp: time(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| is_published: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let data = std::slice::from_ref(&data); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sync_transports(alice, data, &[]).await?; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert!(alice.restart_io_after_fetch.swap(false, Ordering::Relaxed)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Applying the same data again modifies nothing. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sync_transports(alice, data, &[]).await?; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert!(!alice.restart_io_after_fetch.load(Ordering::Relaxed)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Ok(()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Promotes `addr` to primary on `alice` and checks the change syncs to `alice2`. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async fn promote_transport_and_check_success( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| alice: &TestContext, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| alice2: &TestContext, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| addr: &str, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) -> Result<()> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let old_timestamp = add_timestamp(alice2, addr).await; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| alice.set_config(Config::ConfiguredAddr, Some(addr)).await?; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert!(add_timestamp(alice, addr).await > old_timestamp); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| alice.send_sync_msg().await?.unwrap(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let sync_msg = alice.pop_sent_msg().await; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert_eq!(sync_msg.recipients, format!("alice@example.org {addr}")); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Other devices switch their primary transport | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // based on the From address of the sync message. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert!(sync_msg.payload.contains(&format!("From: <{addr}>"))); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| alice2.recv_msg_trash(&sync_msg).await; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // add_timestamp must monotonically increase because | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // other devices ignore the change otherwise. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert!(add_timestamp(alice2, addr).await > old_timestamp); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SQL is now changed to increase add_timestamp at least by 1, and the test is checking that add_timestamp is increased.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have checked out the branch, split the first commit into the test changes and the fix, commented out two
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert_eq!( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| alice2.get_config(Config::ConfiguredAddr).await?.as_deref(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Was this already working without increasing Lines 802 to 830 in 020e477
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Some(addr) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Ok(()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async fn add_timestamp(t: &TestContext, addr: &str) -> i64 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t.sql | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .query_get_value("SELECT add_timestamp FROM transports WHERE addr=?", (addr,)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .await | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .unwrap() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .unwrap() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| struct Addresses { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| primary: &'static str, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| secondary_published: &'static [&'static str], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So my suggestion is to remove this block (with large comment about
get_all_self_addrs) completely, and only update theconfigured_addrsand still send the sync message, but with unchanged addresses. And then expect only a single event on the other side. This does not depend on #8501 and we are anyway going to only add up to 3 relays with automatic relay management.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is_published=1for promoting an unpublished transporttest_is_published_flagfails in the finalcheck_addrswithout increasing timestamp. As far as i seemerge_openpgp_certificatesdepends on timestamps increasing, or it will keep the stored cert with signature timestamp being the max of transport/removed_transports timestamps.sidenote: i think all transport manipulation should be done in transport.rs and tested there, and other sites only use it. This would make it easier i think to get a complete picture and certainty that it handles all cases. But that's clearly outside this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fwiw, i also pushed a revised and shortened comment block.