-
-
Notifications
You must be signed in to change notification settings - Fork 133
feat: Remove hidden relays automatically #8402
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
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 |
|---|---|---|
|
|
@@ -47,6 +47,9 @@ mod pool; | |
|
|
||
| use pool::{Pool, WalCheckpointStats}; | ||
|
|
||
| /// How long a hidden and unused transport should be kept in the database before being deleted. | ||
| pub const HIDDEN_TRANSPORT_CLEANUP_TIME: i64 = 90 * 24 * 60 * 60; | ||
|
|
||
| /// A wrapper around the underlying Sqlite3 object. | ||
| #[derive(Debug)] | ||
| pub struct Sql { | ||
|
|
@@ -908,10 +911,54 @@ pub async fn housekeeping(context: &Context) -> Result<()> { | |
| .log_err(context) | ||
| .ok(); | ||
|
|
||
| remove_unused_hidden_transports(context) | ||
| .await | ||
| .context("failed to remove unused hidden transports") | ||
| .log_err(context) | ||
| .ok(); | ||
|
|
||
| info!(context, "Housekeeping done."); | ||
| Ok(()) | ||
| } | ||
|
|
||
| /// Removes transports that are hidden (`is_published=0`), | ||
| /// and haven't been used to receive new messages for [`HIDDEN_TRANSPORT_CLEANUP_TIME`] seconds. | ||
| pub async fn remove_unused_hidden_transports(context: &Context) -> Result<usize> { | ||
| let now = time(); | ||
| let cutoff = now.saturating_sub(HIDDEN_TRANSPORT_CLEANUP_TIME); | ||
| context | ||
| .sql | ||
| .execute( | ||
| "DELETE FROM transports | ||
| WHERE is_published=0 | ||
| AND last_rcvd_timestamp<?1 | ||
| AND add_timestamp<?1", | ||
| (cutoff,), | ||
| ) | ||
| .await | ||
| } | ||
|
|
||
| /// Updates the corresponding transport's `last_rcvd_timestamp` | ||
| /// with value from message's `timestamp_rcvd`, | ||
| /// if it is larger than the current value. | ||
| pub async fn update_transport_last_rcvd_timestamp( | ||
| context: &Context, | ||
| rfc724_mid: &str, | ||
| ) -> Result<usize> { | ||
| context | ||
| .sql | ||
| .execute( | ||
| "UPDATE transports | ||
| SET last_rcvd_timestamp=MAX(msgs.timestamp_rcvd, transports.last_rcvd_timestamp) | ||
| FROM transports t1 | ||
| INNER JOIN imap ON t1.id=imap.transport_id | ||
| INNER JOIN msgs ON msgs.rfc724_mid=imap.rfc724_mid | ||
| WHERE msgs.rfc724_mid=?", | ||
|
Comment on lines
+951
to
+956
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 I'm not mistaken, this function is called right after receiving the message, which means that
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. that is true, I mainly did that for consistency, so we won't have some weird shift-by-few-seconds in the db, I can change it to just
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 think that with just
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. Yes, sounds sensible to just set last_rcvd_timestamp to the current time, including reducing it if it was bigger before. In general, we usually prefer whichever solution is least complex, because core is already quite complex and it's hard to keep everything in mind when implementing new features, so that we want to optimize for quick readability and easy understandability. |
||
| (rfc724_mid,), | ||
| ) | ||
| .await | ||
| } | ||
|
|
||
| /// Get the value of a column `idx` of the `row` as `Vec<u8>`. | ||
| pub fn row_get_vec(row: &Row, idx: usize) -> rusqlite::Result<Vec<u8>> { | ||
| row.get(idx).or_else(|err| match row.get_ref(idx)? { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| use super::*; | ||
| use crate::message::Message; | ||
| use crate::{EventType, test_utils::TestContext}; | ||
| use deltachat_time::SystemTimeTools; | ||
|
|
||
| #[test] | ||
| fn test_maybe_add_file() { | ||
|
|
@@ -367,3 +368,138 @@ async fn test_incremental_vacuum() -> Result<()> { | |
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
| #[tokio::test(flavor = "multi_thread", worker_threads = 2)] | ||
| async fn test_remove_unused_hidden_transports() -> Result<()> { | ||
|
Hocuri marked this conversation as resolved.
|
||
| let t = TestContext::new().await; | ||
| let now = time(); | ||
|
|
||
| let transport_id = t | ||
| .sql | ||
| .transaction(|t| { | ||
| // published transport | ||
| t.execute( | ||
| "INSERT INTO transports | ||
| (addr, | ||
| entered_param, | ||
| configured_param, | ||
| add_timestamp) | ||
| VALUES (?, ?, ?, ?)", | ||
| ("1", "", "", now), | ||
| )?; | ||
|
|
||
| // not published transport | ||
| let transport_id = t.query_row::<u64, _, _>( | ||
| "INSERT INTO transports | ||
| (addr, | ||
| entered_param, | ||
| configured_param, | ||
| is_published, | ||
| add_timestamp) | ||
| VALUES (?, ?, ?, ?, ?) | ||
| RETURNING id", | ||
| ("2", "", "", 0, now), | ||
| |row| row.get(0), | ||
| )?; | ||
|
|
||
| // this transport has add_timestamp in the future, | ||
| // and should not be removed at all, since we only shift time | ||
| // by 90 days and one second. | ||
| t.execute( | ||
| "INSERT INTO transports | ||
| (addr, | ||
| entered_param, | ||
| configured_param, | ||
| is_published, | ||
| add_timestamp) | ||
| VALUES (?, ?, ?, ?, ?)", | ||
| ("3", "", "", 0, now + 60), | ||
| )?; | ||
|
|
||
| // msgs | ||
| t.execute( | ||
| "INSERT INTO msgs | ||
| (rfc724_mid, | ||
| timestamp_rcvd, | ||
| mime_headers, | ||
| mime_in_reply_to, | ||
| mime_references, | ||
| txt_normalized) | ||
| VALUES (?, ?, ?, ?, ?, ?)", | ||
| ("smth", now, "", "", "", ""), | ||
| )?; | ||
|
|
||
| // imap | ||
| t.execute( | ||
| "INSERT INTO imap | ||
| (transport_id, | ||
| rfc724_mid, | ||
| folder, | ||
| target, | ||
| uid, | ||
| uidvalidity) | ||
| VALUES (?, ?, ?, ?, ?, ?)", | ||
| (transport_id, "smth", "", "", 0, 0), | ||
| )?; | ||
|
|
||
| Ok(transport_id) | ||
| }) | ||
| .await?; | ||
|
|
||
| macro_rules! assert_transport_removed { | ||
|
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. Can this just be a regular function rather than a macro? Seems easier to read, and only one additional parameter (
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. Two additional parameters, as this also needs to take
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. Generally, in chatmail/core, for readability reasons and for avoiding surprising effects (like that $expr is evaluated in all the places it's copy-pasted to), we only use macros when really needed. I made a PR to explicitly state this in STYLE.md: #8410
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. ok
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. As a general rule #8410 makes sense, but overall i think it's not that important, especially in the test. |
||
| ($removed:expr) => { | ||
| let transports_count: u32 = t | ||
| .sql | ||
| .query_get_value("SELECT count(*) FROM transports", ()) | ||
| .await? | ||
| .unwrap(); | ||
| assert_eq!(transports_count, if $removed { 2 } else { 3 }); | ||
| let hidden_transport_present: bool = t | ||
| .sql | ||
| .query_get_value( | ||
| "SELECT count(*) FROM transports WHERE id=?", | ||
| (transport_id,), | ||
| ) | ||
| .await? | ||
| .unwrap(); | ||
| assert_eq!(hidden_transport_present, !$removed); | ||
| }; | ||
| } | ||
|
|
||
| assert_eq!( | ||
| t.sql | ||
| .query_get_value::<i64>( | ||
| "SELECT last_rcvd_timestamp FROM transports WHERE id=?", | ||
| (transport_id,) | ||
| ) | ||
| .await? | ||
| .unwrap(), | ||
| 0 | ||
| ); | ||
| update_transport_last_rcvd_timestamp(&t, "smth").await?; | ||
| // last_rcvd_timestamp should update | ||
| assert_eq!( | ||
| t.sql | ||
| .query_get_value::<i64>( | ||
| "SELECT last_rcvd_timestamp FROM transports WHERE id=?", | ||
| (transport_id,) | ||
| ) | ||
| .await? | ||
| .unwrap(), | ||
| now | ||
| ); | ||
|
|
||
| assert_transport_removed!(false); | ||
| remove_unused_hidden_transports(&t).await?; | ||
| // it was recently used, so nothing is deleted | ||
| assert_transport_removed!(false); | ||
|
|
||
| SystemTimeTools::shift(Duration::from_secs( | ||
| (HIDDEN_TRANSPORT_CLEANUP_TIME + 1).try_into()?, | ||
| )); | ||
|
|
||
| remove_unused_hidden_transports(&t).await?; | ||
| assert_transport_removed!(true); | ||
|
|
||
| Ok(()) | ||
| } | ||
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.
receive_imfis only called after downloading a message from the relay where it arrived first. When the message arrives again on another relay, it's not called again. (although we had multiple bugs in this area, so that it's worth testing again whether this actually works when basing more logic on it).This means that with the PR as-is,
transport_last_rcvd_timestampwill only be updated for the fastest transport. This is not exactly what was specified in #8384, but actually it is fine: If an unpublished relay is slower than the others AND all the messages are also received on the other, faster relays, then it's safe to delete the unpublished relay.It does mean that we can't use the same timestamp to automatically unpublish a relay that seems not to be working. Then again, it's not entirely clear how we'll implement this logic, anyways.
So, the two options now are as follows:
last_rcvd_timestampis only updated on the relay via which the message was received firstfetch_new_msg_batch()Either of them seems equally fine. @link2xt do you have an opinion?
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.
I'd leave this logic in
receive_imfand also check if the contact is blocked or a contact request. I don't think it's a good idea to allow everyone knowing our public key to control which transports we can remove. We consider the public key as confidential data, but it's still not secret, so we can't absolutely protect from spammers getting know it, particularly if the user is a big channel operator.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.
No need to check if the contact is blocked or a contact request, nothing bad happens if you keep a transport for longer
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.
"For longer" sounds like "maybe never". Actually bad things may happen even if we check that, because we have MAX_TRANSPORT_RELAYS = 5. This means that the user can't add a new transport more often than once in 90/5, i.e. 18, days on average, if we remove the capability of immediately deleting a transport from the UIs. Some users often add new transports for experiments. Then hidden transports shouldn't be limited by MAX_TRANSPORT_RELAYS.