-
-
Notifications
You must be signed in to change notification settings - Fork 884
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
Check for dead federated instances (fixes #2221) #3427
Changes from all commits
ccc86f7
d2a1dc0
69812dd
7425f2a
d021ddb
dae74f6
8156416
0ce2ff7
da11ec0
93b6410
a898aca
733bdd2
6c5c6e1
93e98b6
382ad74
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
dessalines marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,10 +18,13 @@ use lemmy_db_schema::{ | |
utils::{naive_now, DELETED_REPLACEMENT_TEXT}, | ||
}; | ||
use lemmy_routes::nodeinfo::NodeInfo; | ||
use lemmy_utils::{error::LemmyError, REQWEST_TIMEOUT}; | ||
use lemmy_utils::{ | ||
error::{LemmyError, LemmyResult}, | ||
REQWEST_TIMEOUT, | ||
}; | ||
use reqwest::blocking::Client; | ||
use std::{thread, time::Duration}; | ||
use tracing::{error, info}; | ||
use tracing::{error, info, warn}; | ||
|
||
/// Schedules various cleanup tasks for lemmy in a background thread | ||
pub fn setup( | ||
|
@@ -79,7 +82,9 @@ pub fn setup( | |
// Update the Instance Software | ||
scheduler.every(CTimeUnits::days(1)).run(move || { | ||
let mut conn = PgConnection::establish(&db_url).expect("could not establish connection"); | ||
update_instance_software(&mut conn, &user_agent); | ||
update_instance_software(&mut conn, &user_agent) | ||
.map_err(|e| warn!("Failed to update instance software: {e}")) | ||
.ok(); | ||
}); | ||
|
||
// Manually run the scheduler in an event loop | ||
|
@@ -323,62 +328,65 @@ fn update_banned_when_expired(conn: &mut PgConnection) { | |
} | ||
|
||
/// Updates the instance software and version | ||
fn update_instance_software(conn: &mut PgConnection, user_agent: &str) { | ||
/// | ||
/// TODO: this should be async | ||
/// TODO: if instance has been dead for a long time, it should be checked less frequently | ||
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. For this one, in the select below, you could do Even better, would be to add this as Another possibility, would be to recheck the alive_instances every day, but only re-check all of them (even previously dead ones) every month. Up to you. 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. Or check old instances using random probability, eg in 1% of all checks. 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. Anyway this can be improved later, no need to include it in this PR. |
||
fn update_instance_software(conn: &mut PgConnection, user_agent: &str) -> LemmyResult<()> { | ||
info!("Updating instances software and versions..."); | ||
|
||
let client = match Client::builder() | ||
let client = Client::builder() | ||
.user_agent(user_agent) | ||
.timeout(REQWEST_TIMEOUT) | ||
.build() | ||
{ | ||
Ok(client) => client, | ||
Err(e) => { | ||
error!("Failed to build reqwest client: {}", e); | ||
return; | ||
} | ||
}; | ||
.build()?; | ||
|
||
let instances = match instance::table.get_results::<Instance>(conn) { | ||
Ok(instances) => instances, | ||
Err(e) => { | ||
error!("Failed to get instances: {}", e); | ||
return; | ||
} | ||
}; | ||
let instances = instance::table.get_results::<Instance>(conn)?; | ||
|
||
for instance in instances { | ||
let node_info_url = format!("https://{}/nodeinfo/2.0.json", instance.domain); | ||
|
||
// Skip it if it can't connect | ||
let res = client | ||
.get(&node_info_url) | ||
.send() | ||
.ok() | ||
.and_then(|t| t.json::<NodeInfo>().ok()); | ||
|
||
if let Some(node_info) = res { | ||
let software = node_info.software.as_ref(); | ||
let form = InstanceForm::builder() | ||
.domain(instance.domain) | ||
.software(software.and_then(|s| s.name.clone())) | ||
.version(software.and_then(|s| s.version.clone())) | ||
.updated(Some(naive_now())) | ||
.build(); | ||
|
||
match diesel::update(instance::table.find(instance.id)) | ||
.set(form) | ||
.execute(conn) | ||
{ | ||
Ok(_) => { | ||
info!("Done."); | ||
// The `updated` column is used to check if instances are alive. If it is more than three days | ||
// in the past, no outgoing activities will be sent to that instance. However not every | ||
// Fediverse instance has a valid Nodeinfo endpoint (its not required for Activitypub). That's | ||
// why we always need to mark instances as updated if they are alive. | ||
let default_form = InstanceForm::builder() | ||
.domain(instance.domain.clone()) | ||
.updated(Some(naive_now())) | ||
.build(); | ||
let form = match client.get(&node_info_url).send() { | ||
Ok(res) if res.status().is_client_error() => { | ||
// Instance doesnt have nodeinfo but sent a response, consider it alive | ||
Some(default_form) | ||
} | ||
Ok(res) => match res.json::<NodeInfo>() { | ||
Ok(node_info) => { | ||
// Instance sent valid nodeinfo, write it to db | ||
Some( | ||
InstanceForm::builder() | ||
.domain(instance.domain) | ||
.updated(Some(naive_now())) | ||
.software(node_info.software.and_then(|s| s.name)) | ||
.version(node_info.version.clone()) | ||
.build(), | ||
) | ||
} | ||
Err(e) => { | ||
error!("Failed to update site instance software: {}", e); | ||
return; | ||
Err(_) => { | ||
// No valid nodeinfo but valid HTTP response, consider instance alive | ||
Some(default_form) | ||
} | ||
}, | ||
Err(_) => { | ||
// dead instance, do nothing | ||
None | ||
} | ||
}; | ||
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. This code is quite confusing, open for suggestions how to simplify it. 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. Maybe something like this? I'm new to lemmy and rust so apologies if it is not appropriate for me to post this here. It explicitly sets
Edit: Or perhaps even this.
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 gave this a try but feel like its getting even more confusing. So I will leave it as is. |
||
if let Some(form) = form { | ||
diesel::update(instance::table.find(instance.id)) | ||
.set(form) | ||
.execute(conn)?; | ||
} | ||
} | ||
info!("Finished updating instances software and versions..."); | ||
Ok(()) | ||
} | ||
|
||
#[cfg(test)] | ||
|
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.
Bit weird to use caches with capacity one and no key, but seems like the easiest way to implement this.
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 wanted to mention that when writing my other PR, I accidentally made it construct a new whole moka cache for every single incoming event (so 1000s per second) and insert a single value, and it didn't negatively affect performance at all. Just as a reference that constructing tiny moka caches is probably fine regarding performance (if maybe not code beauty).