Change actor status to "unreachable" if delivery to inbox fails
This commit is contained in:
parent
bd158b0a1f
commit
498be66d8b
8 changed files with 101 additions and 7 deletions
|
@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
- Added `/api/v1/accounts/lookup` Mastodon API endpoint.
|
- Added `/api/v1/accounts/lookup` Mastodon API endpoint.
|
||||||
- Implemented activity delivery queue.
|
- Implemented activity delivery queue.
|
||||||
|
- Started to keep track of unreachable actors.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
ALTER TABLE actor_profile ADD COLUMN unreachable_since TIMESTAMP WITH TIME ZONE;
|
|
@ -32,6 +32,7 @@ CREATE TABLE actor_profile (
|
||||||
actor_id VARCHAR(200) UNIQUE GENERATED ALWAYS AS (actor_json ->> 'id') STORED,
|
actor_id VARCHAR(200) UNIQUE GENERATED ALWAYS AS (actor_json ->> 'id') STORED,
|
||||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
unreachable_since TIMESTAMP WITH TIME ZONE,
|
||||||
CHECK ((hostname IS NULL) = (actor_json IS NULL))
|
CHECK ((hostname IS NULL) = (actor_json IS NULL))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ use tokio::time::sleep;
|
||||||
use tokio_postgres::GenericClient;
|
use tokio_postgres::GenericClient;
|
||||||
|
|
||||||
use crate::config::Instance;
|
use crate::config::Instance;
|
||||||
use crate::database::DatabaseError;
|
use crate::database::{get_database_client, DatabaseError, DbPool};
|
||||||
use crate::http_signatures::create::{
|
use crate::http_signatures::create::{
|
||||||
create_http_signature,
|
create_http_signature,
|
||||||
HttpSignatureError,
|
HttpSignatureError,
|
||||||
|
@ -20,7 +20,10 @@ use crate::json_signatures::create::{
|
||||||
sign_object,
|
sign_object,
|
||||||
JsonSignatureError,
|
JsonSignatureError,
|
||||||
};
|
};
|
||||||
use crate::models::users::types::User;
|
use crate::models::{
|
||||||
|
profiles::queries::set_reachability_status,
|
||||||
|
users::types::User,
|
||||||
|
};
|
||||||
use crate::utils::crypto_rsa::deserialize_private_key;
|
use crate::utils::crypto_rsa::deserialize_private_key;
|
||||||
use super::actors::types::Actor;
|
use super::actors::types::Actor;
|
||||||
use super::constants::{AP_MEDIA_TYPE, ACTOR_KEY_SUFFIX};
|
use super::constants::{AP_MEDIA_TYPE, ACTOR_KEY_SUFFIX};
|
||||||
|
@ -46,6 +49,9 @@ pub enum DelivererError {
|
||||||
|
|
||||||
#[error("http error {0:?}")]
|
#[error("http error {0:?}")]
|
||||||
HttpError(reqwest::StatusCode),
|
HttpError(reqwest::StatusCode),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
DatabaseError(#[from] DatabaseError),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_client(instance: &Instance) -> reqwest::Result<Client> {
|
fn build_client(instance: &Instance) -> reqwest::Result<Client> {
|
||||||
|
@ -117,6 +123,7 @@ pub struct Recipient {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn deliver_activity_worker(
|
async fn deliver_activity_worker(
|
||||||
|
maybe_db_pool: Option<DbPool>,
|
||||||
instance: Instance,
|
instance: Instance,
|
||||||
sender: User,
|
sender: User,
|
||||||
activity: Value,
|
activity: Value,
|
||||||
|
@ -185,6 +192,18 @@ async fn deliver_activity_worker(
|
||||||
};
|
};
|
||||||
retry_count += 1;
|
retry_count += 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if let Some(ref db_pool) = maybe_db_pool {
|
||||||
|
// Get connection from pool only after finishing delivery
|
||||||
|
let db_client = &**get_database_client(db_pool).await?;
|
||||||
|
for (recipient, is_delivered) in queue {
|
||||||
|
set_reachability_status(
|
||||||
|
db_client,
|
||||||
|
&recipient.id,
|
||||||
|
is_delivered,
|
||||||
|
).await?;
|
||||||
|
};
|
||||||
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,6 +243,7 @@ impl OutgoingActivity {
|
||||||
|
|
||||||
pub async fn deliver(self) -> Result<(), DelivererError> {
|
pub async fn deliver(self) -> Result<(), DelivererError> {
|
||||||
deliver_activity_worker(
|
deliver_activity_worker(
|
||||||
|
None,
|
||||||
self.instance,
|
self.instance,
|
||||||
self.sender,
|
self.sender,
|
||||||
self.activity,
|
self.activity,
|
||||||
|
@ -243,6 +263,23 @@ impl OutgoingActivity {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn spawn_deliver_with_tracking(
|
||||||
|
self,
|
||||||
|
db_pool: DbPool,
|
||||||
|
) -> () {
|
||||||
|
tokio::spawn(async move {
|
||||||
|
deliver_activity_worker(
|
||||||
|
Some(db_pool),
|
||||||
|
self.instance,
|
||||||
|
self.sender,
|
||||||
|
self.activity,
|
||||||
|
self.recipients,
|
||||||
|
).await.unwrap_or_else(|err| {
|
||||||
|
log::error!("{}", err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn enqueue(
|
pub async fn enqueue(
|
||||||
self,
|
self,
|
||||||
db_client: &impl GenericClient,
|
db_client: &impl GenericClient,
|
||||||
|
|
|
@ -5,7 +5,12 @@ use tokio_postgres::GenericClient;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::database::{DatabaseError, DatabaseTypeError};
|
use crate::database::{
|
||||||
|
get_database_client,
|
||||||
|
DatabaseError,
|
||||||
|
DatabaseTypeError,
|
||||||
|
DbPool,
|
||||||
|
};
|
||||||
use crate::models::{
|
use crate::models::{
|
||||||
background_jobs::queries::{
|
background_jobs::queries::{
|
||||||
enqueue_job,
|
enqueue_job,
|
||||||
|
@ -122,8 +127,9 @@ impl OutgoingActivityJobData {
|
||||||
|
|
||||||
pub async fn process_queued_outgoing_activities(
|
pub async fn process_queued_outgoing_activities(
|
||||||
config: &Config,
|
config: &Config,
|
||||||
db_client: &impl GenericClient,
|
db_pool: &DbPool,
|
||||||
) -> Result<(), DatabaseError> {
|
) -> Result<(), DatabaseError> {
|
||||||
|
let db_client = &**get_database_client(db_pool).await?;
|
||||||
let batch_size = 1;
|
let batch_size = 1;
|
||||||
let batch = get_job_batch(
|
let batch = get_job_batch(
|
||||||
db_client,
|
db_client,
|
||||||
|
@ -141,7 +147,7 @@ pub async fn process_queued_outgoing_activities(
|
||||||
activity: job_data.activity,
|
activity: job_data.activity,
|
||||||
recipients: job_data.recipients,
|
recipients: job_data.recipients,
|
||||||
};
|
};
|
||||||
outgoing_activity.spawn_deliver();
|
outgoing_activity.spawn_deliver_with_tracking(db_pool.clone());
|
||||||
delete_job_from_queue(db_client, &job.id).await?;
|
delete_job_from_queue(db_client, &job.id).await?;
|
||||||
};
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -130,8 +130,7 @@ async fn outgoing_activity_queue_task(
|
||||||
config: &Config,
|
config: &Config,
|
||||||
db_pool: &DbPool,
|
db_pool: &DbPool,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let db_client = &**get_database_client(db_pool).await?;
|
process_queued_outgoing_activities(config, db_pool).await?;
|
||||||
process_queued_outgoing_activities(config, db_client).await?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -576,6 +576,36 @@ pub async fn update_post_count(
|
||||||
Ok(profile)
|
Ok(profile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Doesn't return error if profile doesn't exist
|
||||||
|
pub async fn set_reachability_status(
|
||||||
|
db_client: &impl GenericClient,
|
||||||
|
actor_id: &str,
|
||||||
|
is_reachable: bool,
|
||||||
|
) -> Result<(), DatabaseError> {
|
||||||
|
if !is_reachable {
|
||||||
|
// Don't update profile if unreachable_since is already set
|
||||||
|
db_client.execute(
|
||||||
|
"
|
||||||
|
UPDATE actor_profile
|
||||||
|
SET unreachable_since = CURRENT_TIMESTAMP
|
||||||
|
WHERE actor_id = $1 AND unreachable_since IS NULL
|
||||||
|
",
|
||||||
|
&[&actor_id],
|
||||||
|
).await?;
|
||||||
|
} else {
|
||||||
|
// Remove status (if set)
|
||||||
|
db_client.execute(
|
||||||
|
"
|
||||||
|
UPDATE actor_profile
|
||||||
|
SET unreachable_since = NULL
|
||||||
|
WHERE actor_id = $1
|
||||||
|
",
|
||||||
|
&[&actor_id],
|
||||||
|
).await?;
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Finds all empty remote profiles
|
/// Finds all empty remote profiles
|
||||||
/// (without any posts, reactions, relationships)
|
/// (without any posts, reactions, relationships)
|
||||||
/// updated before the specified date
|
/// updated before the specified date
|
||||||
|
@ -812,6 +842,23 @@ mod tests {
|
||||||
assert_eq!(profiles[0].id, profile.id);
|
assert_eq!(profiles[0].id, profile.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn test_set_reachability_status() {
|
||||||
|
let db_client = &create_test_database().await;
|
||||||
|
let actor_id = "https://example.com/users/test";
|
||||||
|
let profile_data = ProfileCreateData {
|
||||||
|
username: "test".to_string(),
|
||||||
|
hostname: Some("example.com".to_string()),
|
||||||
|
actor_json: Some(create_test_actor(actor_id)),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let profile = create_profile(db_client, profile_data).await.unwrap();
|
||||||
|
set_reachability_status(db_client, actor_id, false).await.unwrap();
|
||||||
|
let profile = get_profile_by_id(db_client, &profile.id).await.unwrap();
|
||||||
|
assert_eq!(profile.unreachable_since.is_some(), true);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial]
|
#[serial]
|
||||||
async fn test_find_empty_profiles() {
|
async fn test_find_empty_profiles() {
|
||||||
|
|
|
@ -302,6 +302,7 @@ pub struct DbActorProfile {
|
||||||
pub actor_json: Option<Actor>,
|
pub actor_json: Option<Actor>,
|
||||||
pub created_at: DateTime<Utc>,
|
pub created_at: DateTime<Utc>,
|
||||||
pub updated_at: DateTime<Utc>,
|
pub updated_at: DateTime<Utc>,
|
||||||
|
pub unreachable_since: Option<DateTime<Utc>>,
|
||||||
|
|
||||||
// auto-generated database fields
|
// auto-generated database fields
|
||||||
pub acct: String,
|
pub acct: String,
|
||||||
|
@ -381,6 +382,7 @@ impl Default for DbActorProfile {
|
||||||
actor_id: None,
|
actor_id: None,
|
||||||
created_at: now,
|
created_at: now,
|
||||||
updated_at: now,
|
updated_at: now,
|
||||||
|
unreachable_since: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue