2022-12-31 00:06:33 +00:00
|
|
|
use chrono::{Duration, Utc};
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use serde_json::Value;
|
2022-12-11 18:41:08 +00:00
|
|
|
use uuid::Uuid;
|
2022-12-31 00:06:33 +00:00
|
|
|
|
2023-02-18 23:52:48 +00:00
|
|
|
use mitra_config::Config;
|
|
|
|
|
2022-12-03 21:06:15 +00:00
|
|
|
use crate::database::{
|
|
|
|
get_database_client,
|
2023-01-17 23:14:18 +00:00
|
|
|
DatabaseClient,
|
2022-12-03 21:06:15 +00:00
|
|
|
DatabaseError,
|
|
|
|
DatabaseTypeError,
|
|
|
|
DbPool,
|
|
|
|
};
|
2022-12-31 00:06:33 +00:00
|
|
|
use crate::models::{
|
|
|
|
background_jobs::queries::{
|
|
|
|
enqueue_job,
|
|
|
|
get_job_batch,
|
|
|
|
delete_job_from_queue,
|
|
|
|
},
|
|
|
|
background_jobs::types::JobType,
|
2023-02-24 20:17:32 +00:00
|
|
|
profiles::queries::set_reachability_status,
|
2022-12-11 18:41:08 +00:00
|
|
|
users::queries::get_user_by_id,
|
2022-12-31 00:06:33 +00:00
|
|
|
};
|
2022-12-11 18:41:08 +00:00
|
|
|
use super::deliverer::{OutgoingActivity, Recipient};
|
2023-01-26 13:52:30 +00:00
|
|
|
use super::fetcher::fetchers::FetchError;
|
|
|
|
use super::receiver::{handle_activity, HandlerError};
|
2022-12-31 00:06:33 +00:00
|
|
|
|
|
|
|
#[derive(Deserialize, Serialize)]
|
2022-12-31 13:28:25 +00:00
|
|
|
pub struct IncomingActivityJobData {
|
2022-12-31 00:06:33 +00:00
|
|
|
activity: Value,
|
|
|
|
is_authenticated: bool,
|
2023-02-24 21:03:50 +00:00
|
|
|
failure_count: u32,
|
2022-12-31 00:06:33 +00:00
|
|
|
}
|
|
|
|
|
2022-12-31 13:28:25 +00:00
|
|
|
impl IncomingActivityJobData {
|
2022-12-31 00:06:33 +00:00
|
|
|
pub fn new(activity: &Value, is_authenticated: bool) -> Self {
|
|
|
|
Self {
|
|
|
|
activity: activity.clone(),
|
|
|
|
is_authenticated,
|
|
|
|
failure_count: 0,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-31 13:28:25 +00:00
|
|
|
pub async fn into_job(
|
2022-12-31 00:06:33 +00:00
|
|
|
self,
|
2023-01-17 23:14:18 +00:00
|
|
|
db_client: &impl DatabaseClient,
|
2023-02-24 21:03:50 +00:00
|
|
|
delay: u32,
|
2022-12-31 00:06:33 +00:00
|
|
|
) -> Result<(), DatabaseError> {
|
|
|
|
let job_data = serde_json::to_value(self)
|
|
|
|
.expect("activity should be serializable");
|
2023-02-24 21:03:50 +00:00
|
|
|
let scheduled_for = Utc::now() + Duration::seconds(delay.into());
|
2022-12-31 00:06:33 +00:00
|
|
|
enqueue_job(
|
|
|
|
db_client,
|
|
|
|
&JobType::IncomingActivity,
|
|
|
|
&job_data,
|
|
|
|
&scheduled_for,
|
|
|
|
).await
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-24 21:03:50 +00:00
|
|
|
const INCOMING_QUEUE_BATCH_SIZE: u32 = 10;
|
|
|
|
const INCOMING_QUEUE_RETRIES_MAX: u32 = 2;
|
|
|
|
|
|
|
|
const fn incoming_queue_backoff(_failure_count: u32) -> u32 {
|
|
|
|
// Constant, 10 minutes
|
|
|
|
60 * 10
|
|
|
|
}
|
|
|
|
|
2022-12-11 18:41:08 +00:00
|
|
|
pub async fn process_queued_incoming_activities(
|
2022-12-31 00:06:33 +00:00
|
|
|
config: &Config,
|
2023-01-17 23:14:18 +00:00
|
|
|
db_client: &mut impl DatabaseClient,
|
2022-12-31 00:06:33 +00:00
|
|
|
) -> Result<(), DatabaseError> {
|
|
|
|
let batch = get_job_batch(
|
|
|
|
db_client,
|
|
|
|
&JobType::IncomingActivity,
|
2023-02-24 21:03:50 +00:00
|
|
|
INCOMING_QUEUE_BATCH_SIZE,
|
2022-12-31 00:06:33 +00:00
|
|
|
).await?;
|
|
|
|
for job in batch {
|
2022-12-31 13:28:25 +00:00
|
|
|
let mut job_data: IncomingActivityJobData =
|
2022-12-31 00:06:33 +00:00
|
|
|
serde_json::from_value(job.job_data)
|
|
|
|
.map_err(|_| DatabaseTypeError)?;
|
2023-01-26 13:52:30 +00:00
|
|
|
if let Err(error) = handle_activity(
|
2022-12-31 00:06:33 +00:00
|
|
|
config,
|
|
|
|
db_client,
|
2022-12-31 13:28:25 +00:00
|
|
|
&job_data.activity,
|
|
|
|
job_data.is_authenticated,
|
2022-12-31 00:06:33 +00:00
|
|
|
).await {
|
2023-01-26 13:52:30 +00:00
|
|
|
job_data.failure_count += 1;
|
|
|
|
log::warn!(
|
|
|
|
"failed to process activity ({}) (attempt #{}): {}",
|
|
|
|
error,
|
|
|
|
job_data.failure_count,
|
|
|
|
job_data.activity,
|
|
|
|
);
|
2023-02-24 21:03:50 +00:00
|
|
|
if job_data.failure_count <= INCOMING_QUEUE_RETRIES_MAX &&
|
2023-01-26 13:52:30 +00:00
|
|
|
// Don't retry after fetcher recursion error
|
|
|
|
!matches!(error, HandlerError::FetchError(FetchError::RecursionError))
|
|
|
|
{
|
|
|
|
// Re-queue
|
2023-02-24 21:03:50 +00:00
|
|
|
let retry_after = incoming_queue_backoff(job_data.failure_count);
|
2023-01-26 13:52:30 +00:00
|
|
|
job_data.into_job(db_client, retry_after).await?;
|
2023-02-24 20:17:32 +00:00
|
|
|
log::info!("activity re-queued");
|
2023-01-26 13:52:30 +00:00
|
|
|
};
|
2022-12-31 00:06:33 +00:00
|
|
|
};
|
|
|
|
delete_job_from_queue(db_client, &job.id).await?;
|
|
|
|
};
|
|
|
|
Ok(())
|
|
|
|
}
|
2022-12-11 18:41:08 +00:00
|
|
|
|
|
|
|
#[derive(Deserialize, Serialize)]
|
|
|
|
pub struct OutgoingActivityJobData {
|
|
|
|
pub activity: Value,
|
|
|
|
pub sender_id: Uuid,
|
|
|
|
pub recipients: Vec<Recipient>,
|
2023-02-24 20:17:32 +00:00
|
|
|
pub failure_count: u32,
|
2022-12-11 18:41:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl OutgoingActivityJobData {
|
|
|
|
pub async fn into_job(
|
|
|
|
self,
|
2023-01-17 23:14:18 +00:00
|
|
|
db_client: &impl DatabaseClient,
|
2023-02-24 20:17:32 +00:00
|
|
|
delay: u32,
|
2022-12-11 18:41:08 +00:00
|
|
|
) -> Result<(), DatabaseError> {
|
|
|
|
let job_data = serde_json::to_value(self)
|
|
|
|
.expect("activity should be serializable");
|
2023-02-24 20:17:32 +00:00
|
|
|
let scheduled_for = Utc::now() + Duration::seconds(delay.into());
|
2022-12-11 18:41:08 +00:00
|
|
|
enqueue_job(
|
|
|
|
db_client,
|
|
|
|
&JobType::OutgoingActivity,
|
|
|
|
&job_data,
|
|
|
|
&scheduled_for,
|
|
|
|
).await
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-24 21:03:50 +00:00
|
|
|
const OUTGOING_QUEUE_BATCH_SIZE: u32 = 1;
|
2023-02-24 20:17:32 +00:00
|
|
|
const OUTGOING_QUEUE_RETRIES_MAX: u32 = 2;
|
|
|
|
|
|
|
|
// 30 secs, 5 mins, 50 mins, 8 hours
|
|
|
|
pub fn outgoing_queue_backoff(failure_count: u32) -> u32 {
|
|
|
|
debug_assert!(failure_count > 0);
|
|
|
|
3 * 10_u32.pow(failure_count)
|
|
|
|
}
|
2023-02-24 21:03:50 +00:00
|
|
|
|
2022-12-11 18:41:08 +00:00
|
|
|
pub async fn process_queued_outgoing_activities(
|
|
|
|
config: &Config,
|
2022-12-03 21:06:15 +00:00
|
|
|
db_pool: &DbPool,
|
2022-12-11 18:41:08 +00:00
|
|
|
) -> Result<(), DatabaseError> {
|
2022-12-03 21:06:15 +00:00
|
|
|
let db_client = &**get_database_client(db_pool).await?;
|
2022-12-11 18:41:08 +00:00
|
|
|
let batch = get_job_batch(
|
|
|
|
db_client,
|
|
|
|
&JobType::OutgoingActivity,
|
2023-02-24 21:03:50 +00:00
|
|
|
OUTGOING_QUEUE_BATCH_SIZE,
|
2022-12-11 18:41:08 +00:00
|
|
|
).await?;
|
|
|
|
for job in batch {
|
2023-02-24 20:17:32 +00:00
|
|
|
let mut job_data: OutgoingActivityJobData =
|
2022-12-11 18:41:08 +00:00
|
|
|
serde_json::from_value(job.job_data)
|
|
|
|
.map_err(|_| DatabaseTypeError)?;
|
|
|
|
let sender = get_user_by_id(db_client, &job_data.sender_id).await?;
|
|
|
|
let outgoing_activity = OutgoingActivity {
|
|
|
|
instance: config.instance(),
|
|
|
|
sender,
|
2023-02-24 20:17:32 +00:00
|
|
|
activity: job_data.activity.clone(),
|
2022-12-11 18:41:08 +00:00
|
|
|
recipients: job_data.recipients,
|
|
|
|
};
|
2023-02-24 20:17:32 +00:00
|
|
|
|
|
|
|
let recipients = match outgoing_activity.deliver().await {
|
|
|
|
Ok(recipients) => recipients,
|
|
|
|
Err(error) => {
|
|
|
|
// Unexpected error
|
|
|
|
log::error!("{}", error);
|
|
|
|
delete_job_from_queue(db_client, &job.id).await?;
|
|
|
|
return Ok(());
|
|
|
|
},
|
|
|
|
};
|
|
|
|
log::info!(
|
|
|
|
"delivery job: {} delivered, {} errors (attempt #{})",
|
|
|
|
recipients.iter().filter(|item| item.is_delivered).count(),
|
|
|
|
recipients.iter().filter(|item| !item.is_delivered).count(),
|
|
|
|
job_data.failure_count + 1,
|
|
|
|
);
|
|
|
|
if recipients.iter().any(|recipient| !recipient.is_delivered) &&
|
|
|
|
job_data.failure_count < OUTGOING_QUEUE_RETRIES_MAX
|
|
|
|
{
|
|
|
|
job_data.failure_count += 1;
|
|
|
|
// Re-queue if some deliveries are not successful
|
|
|
|
job_data.recipients = recipients;
|
|
|
|
let retry_after = outgoing_queue_backoff(job_data.failure_count);
|
|
|
|
job_data.into_job(db_client, retry_after).await?;
|
|
|
|
log::info!("delivery job re-queued");
|
|
|
|
} else {
|
|
|
|
// Update inbox status if all deliveries are successful
|
|
|
|
// or if retry limit is reached
|
|
|
|
for recipient in recipients {
|
|
|
|
set_reachability_status(
|
|
|
|
db_client,
|
|
|
|
&recipient.id,
|
|
|
|
recipient.is_delivered,
|
|
|
|
).await?;
|
|
|
|
};
|
|
|
|
};
|
2022-12-11 18:41:08 +00:00
|
|
|
delete_job_from_queue(db_client, &job.id).await?;
|
|
|
|
};
|
|
|
|
Ok(())
|
|
|
|
}
|
2023-02-24 20:17:32 +00:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_outgoing_queue_backoff() {
|
|
|
|
assert_eq!(outgoing_queue_backoff(1), 30);
|
|
|
|
assert_eq!(outgoing_queue_backoff(2), 300);
|
|
|
|
}
|
|
|
|
}
|