taskflow
This commit is contained in:
+111
-58
@@ -6,7 +6,6 @@ use async_trait::async_trait;
|
||||
use diesel::prelude::*;
|
||||
use diesel::result::Error as DieselError;
|
||||
use serde::Deserialize;
|
||||
use tracing::{error, warn};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::jobs::JOB_PURGE_DOCUMENT;
|
||||
@@ -15,7 +14,11 @@ use crate::schema::{document_asset_objects, document_assets, document_versions};
|
||||
use crate::state::AppState;
|
||||
use crate::storage::TenantStorage;
|
||||
|
||||
use super::{JobExecution, JobHandler};
|
||||
use super::{
|
||||
job_execution_from_task_error,
|
||||
taskflow::{BoxedTask, Task, TaskContext, TaskError, TaskExecutor, TaskPlanner, TaskResult},
|
||||
JobExecution, JobHandler,
|
||||
};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct PurgeDocumentPayload {
|
||||
@@ -58,70 +61,120 @@ impl JobHandler for PurgeDocumentJob {
|
||||
}
|
||||
};
|
||||
|
||||
let tenant_id = job.tenant_id;
|
||||
let document_id = payload.document_id;
|
||||
let state_for_prepare = state.clone();
|
||||
let mut context = PurgeTaskContext::new(
|
||||
job.id,
|
||||
JOB_PURGE_DOCUMENT,
|
||||
job.tenant_id,
|
||||
payload.document_id,
|
||||
state.clone(),
|
||||
storage,
|
||||
);
|
||||
|
||||
let planner = PurgePlanner;
|
||||
match TaskExecutor::run(&planner, &mut context).await {
|
||||
Ok(()) => JobExecution::Success,
|
||||
Err(err) => job_execution_from_task_error(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PurgeTaskContext {
|
||||
job_id: Uuid,
|
||||
job_type: &'static str,
|
||||
tenant_id: Uuid,
|
||||
document_id: Uuid,
|
||||
state: Arc<AppState>,
|
||||
storage: TenantStorage,
|
||||
}
|
||||
|
||||
impl PurgeTaskContext {
|
||||
fn new(
|
||||
job_id: Uuid,
|
||||
job_type: &'static str,
|
||||
tenant_id: Uuid,
|
||||
document_id: Uuid,
|
||||
state: Arc<AppState>,
|
||||
storage: TenantStorage,
|
||||
) -> Self {
|
||||
Self {
|
||||
job_id,
|
||||
job_type,
|
||||
tenant_id,
|
||||
document_id,
|
||||
state,
|
||||
storage,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TaskContext for PurgeTaskContext {
|
||||
fn job_id(&self) -> Uuid {
|
||||
self.job_id
|
||||
}
|
||||
|
||||
fn job_type(&self) -> &'static str {
|
||||
self.job_type
|
||||
}
|
||||
}
|
||||
|
||||
struct PurgePlanner;
|
||||
|
||||
#[async_trait]
|
||||
impl TaskPlanner<PurgeTaskContext> for PurgePlanner {
|
||||
async fn plan(
|
||||
&self,
|
||||
_ctx: &mut PurgeTaskContext,
|
||||
) -> TaskResult<Vec<BoxedTask<PurgeTaskContext>>> {
|
||||
Ok(vec![Box::new(PurgeTask)])
|
||||
}
|
||||
}
|
||||
|
||||
struct PurgeTask;
|
||||
|
||||
#[async_trait]
|
||||
impl Task<PurgeTaskContext> for PurgeTask {
|
||||
fn name(&self) -> &'static str {
|
||||
"purge-document"
|
||||
}
|
||||
|
||||
async fn execute(&self, ctx: &mut PurgeTaskContext) -> TaskResult<()> {
|
||||
let tenant_id = ctx.tenant_id;
|
||||
let document_id = ctx.document_id;
|
||||
let state = ctx.state.clone();
|
||||
|
||||
let preparation = tokio::task::spawn_blocking(move || {
|
||||
prepare_purge_context(state_for_prepare, tenant_id, document_id)
|
||||
prepare_purge_context(state, tenant_id, document_id)
|
||||
})
|
||||
.await;
|
||||
.await
|
||||
.map_err(|err| {
|
||||
TaskError::retry(
|
||||
Duration::from_secs(60),
|
||||
format!("purge preparation panicked: {err}"),
|
||||
)
|
||||
})?;
|
||||
|
||||
let context = match preparation {
|
||||
Ok(Ok(Some(ctx))) => ctx,
|
||||
Ok(Ok(None)) => {
|
||||
// Document already gone or restored; nothing to do.
|
||||
return JobExecution::Success;
|
||||
}
|
||||
Ok(Err(err)) => {
|
||||
warn!(job_id = %job.id, error = %err, "purge preparation failed");
|
||||
return JobExecution::Retry {
|
||||
delay: Duration::from_secs(30),
|
||||
error: err,
|
||||
};
|
||||
}
|
||||
Err(join_err) => {
|
||||
error!(job_id = %job.id, error = %join_err, "purge preparation task panicked");
|
||||
return JobExecution::Retry {
|
||||
delay: Duration::from_secs(60),
|
||||
error: format!("purge preparation panicked: {join_err}"),
|
||||
};
|
||||
}
|
||||
let Some(context) =
|
||||
preparation.map_err(|err| TaskError::retry(Duration::from_secs(30), err))?
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
if let Err(err) = delete_storage_objects(&storage, &context).await {
|
||||
warn!(job_id = %job.id, error = %err, "failed to delete storage objects for purge");
|
||||
return JobExecution::Retry {
|
||||
delay: Duration::from_secs(30),
|
||||
error: err,
|
||||
};
|
||||
}
|
||||
delete_storage_objects(&ctx.storage, &context)
|
||||
.await
|
||||
.map_err(|err| TaskError::retry(Duration::from_secs(30), err))?;
|
||||
|
||||
let PurgeContext { document_id, .. } = context;
|
||||
let state_for_finalize = state.clone();
|
||||
let state = ctx.state.clone();
|
||||
tokio::task::spawn_blocking(move || finalize_purge(state, tenant_id, context.document_id))
|
||||
.await
|
||||
.map_err(|err| {
|
||||
TaskError::retry(
|
||||
Duration::from_secs(60),
|
||||
format!("purge finalize panicked: {err}"),
|
||||
)
|
||||
})?
|
||||
.map_err(|err| TaskError::retry(Duration::from_secs(30), err))?;
|
||||
|
||||
let finalize = tokio::task::spawn_blocking(move || {
|
||||
finalize_purge(state_for_finalize, tenant_id, document_id)
|
||||
})
|
||||
.await;
|
||||
|
||||
match finalize {
|
||||
Ok(Ok(())) => JobExecution::Success,
|
||||
Ok(Err(err)) => {
|
||||
warn!(job_id = %job.id, error = %err, "failed to finalize purge");
|
||||
JobExecution::Retry {
|
||||
delay: Duration::from_secs(30),
|
||||
error: err,
|
||||
}
|
||||
}
|
||||
Err(join_err) => {
|
||||
error!(job_id = %job.id, error = %join_err, "purge finalize task panicked");
|
||||
JobExecution::Retry {
|
||||
delay: Duration::from_secs(60),
|
||||
error: format!("purge finalize panicked: {join_err}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user