mod processor;
mod push_delivery;
pub use push_delivery::timeout_outcome;
mod state_machine;
use std::sync::Arc;
use a2a_protocol_types::error::A2aResult;
use a2a_protocol_types::task::TaskId;
use tokio::sync::mpsc;
use super::super::RequestHandler;
use crate::streaming::event_queue::terminal_gate::CloseOnDrop;
use processor::Processor;
use state_machine::BackgroundDeps;
impl RequestHandler {
#[allow(clippy::too_many_lines)]
pub(crate) fn spawn_background_event_processor(
&self,
task_id: TaskId,
executor_handle: tokio::task::JoinHandle<()>,
persistence_rx: Option<mpsc::Receiver<A2aResult<crate::streaming::StreamEvent>>>,
initial_task: a2a_protocol_types::task::Task,
links: super::ProcessorLinks,
) {
let task_store = Arc::clone(&self.task_store);
let push_config_store = Arc::clone(&self.push_config_store);
let push_sender = self.push_sender.clone();
let limits = self.limits.clone();
let metrics = Arc::clone(&self.metrics);
let tenant = crate::store::tenant::TenantContext::current();
self.in_flight
.background()
.spawn(crate::rpc_span::in_child_span(
"a2a.process_events",
crate::store::tenant::TenantContext::scope(tenant, async move {
let super::ProcessorLinks { cancel, gate } = links;
let _gate_guard = gate.clone().map(CloseOnDrop);
let Some(mut persistence_reader) = persistence_rx else {
trace_warn!(
task_id = %task_id,
"background event processor: no persistence channel provided"
);
return;
};
let last_task = match task_store.get(&task_id).await {
Ok(Some(task)) => task,
Ok(None) => {
trace_warn!(
task_id = %task_id,
"background processor: task missing at start; \
re-asserting from the send-path snapshot"
);
if let Err(e) = task_store.save(&initial_task).await {
trace_error!(
task_id = %task_id,
error = %e,
"background processor: failed to re-assert evicted task"
);
metrics.on_persistence_error(
crate::metrics::persistence_operation::TASK_SNAPSHOT,
e.metric_label(),
);
}
initial_task
}
Err(_e) => {
trace_warn!(
task_id = %task_id,
"background processor: store read failed at start; \
continuing from the send-path snapshot"
);
initial_task
}
};
let mut run = Processor::new(
&task_id,
BackgroundDeps {
task_store: &*task_store,
push_config_store: &*push_config_store,
push_sender: push_sender.as_deref(),
limits: &limits,
metrics: &*metrics,
},
last_task,
cancel,
gate.as_deref(),
);
let mut executor_done = false;
let mut handle_fuse = executor_handle;
loop {
if executor_done {
match persistence_reader.recv().await {
Some(event) => run.handle(event).await,
None => break,
}
} else {
tokio::select! {
biased;
event = persistence_reader.recv() => {
match event {
Some(event) => run.handle(event).await,
None => break,
}
}
result = &mut handle_fuse => {
executor_done = true;
if result.is_err() {
trace_error!(
task_id = %task_id,
"executor task panicked (background processor)"
);
run.executor_panicked().await;
}
}
}
}
}
}),
));
}
}
pub(in crate::handler::event_processing) async fn record_event(
task_store: &dyn crate::store::TaskStore,
task_id: &a2a_protocol_types::task::TaskId,
event: &a2a_protocol_types::error::A2aResult<crate::streaming::StreamEvent>,
metrics: &dyn crate::metrics::Metrics,
) {
if !task_store.supports_event_log() {
return;
}
let Ok(event) = event else {
return;
};
let Some(seq) = event.seq else {
return;
};
if let Err(e) = task_store.append_event(task_id, seq, &event.event).await {
trace_warn!(
task_id = %task_id,
seq = seq,
error = %e,
"background processor: event log append failed; the history skips this position"
);
metrics.on_persistence_error(
crate::metrics::persistence_operation::EVENT_APPEND,
e.metric_label(),
);
}
}