use crate::metrics::MetricsHandle;
use crate::stages::common::stage_handle::{StageError, StageHandle};
use crate::supervised_base::{BuilderError, HandleError, SupervisorHandle};
use futures::{future::BoxFuture, FutureExt};
use std::collections::VecDeque;
use std::sync::{Arc, Mutex};
use std::task::{Context, Poll};
use crate::supervised_base::publication::{
BoxError, PublicationScope, PublicationSettlement, SharedError,
};
use futures::stream::FuturesUnordered;
use obzenflow_core::EventId;
use std::sync::OnceLock;
pub(crate) type OperationalFailure = Arc<OnceLock<SharedError>>;
type StageJoins = FuturesUnordered<BoxFuture<'static, Result<(), StageError>>>;
#[derive(Default)]
pub(super) enum ProducerTail {
#[default]
Uncaptured,
Reading(Mutex<BoxFuture<'static, Result<Option<EventId>, BoxError>>>),
Through(EventId),
Reached,
}
pub(crate) struct PipelineResources {
pub(super) publications: Arc<PublicationScope>,
pub(super) publication_settlement: Option<PublicationSettlement>,
pub(super) delivery: StageDelivery,
pub(super) stage_joins: Option<Mutex<StageJoins>>,
pub(super) stages_joined: bool,
pub(crate) metrics: Arc<MetricsOwner>,
pub(super) prepared_metrics: Option<crate::metrics::builder::PreparedMetricsAggregator>,
pub(super) metrics_join: Option<Mutex<BoxFuture<'static, Result<(), HandleError>>>>,
pub(super) metrics_joined: bool,
pub(super) producer_tail: ProducerTail,
pub(super) terminal_ack: Arc<OnceLock<std::time::Instant>>,
pub(crate) failure: OperationalFailure,
}
impl Default for PipelineResources {
fn default() -> Self {
Self {
publications: PublicationScope::pipeline(),
publication_settlement: None,
delivery: StageDelivery::default(),
stage_joins: None,
stages_joined: false,
metrics: Arc::new(MetricsOwner::default()),
prepared_metrics: None,
metrics_join: None,
metrics_joined: false,
producer_tail: ProducerTail::Uncaptured,
terminal_ack: Arc::new(OnceLock::new()),
failure: Arc::new(OnceLock::new()),
}
}
}
impl PipelineResources {
pub(super) fn retain_failure(&self, error: BoxError) {
let _ = self.failure.set(SharedError::from(error));
}
pub(super) fn refresh_publications(&mut self) {
self.publication_settlement = Some(self.publications.observe_accepted());
}
}
#[derive(Default)]
struct MetricsSlot {
handle: Option<Arc<MetricsHandle>>,
writer: Option<obzenflow_core::event::WriterId>,
cancelled: bool,
}
#[derive(Default)]
pub(crate) struct MetricsOwner(Mutex<MetricsSlot>);
impl MetricsOwner {
pub(crate) fn start(
&self,
prepared: crate::metrics::builder::PreparedMetricsAggregator,
) -> Result<(), BuilderError> {
let mut slot = self.0.lock().unwrap_or_else(|e| e.into_inner());
if slot.cancelled || slot.handle.is_some() {
return Err(BuilderError::Other(
"metrics child cannot be started twice or after cancellation".into(),
));
}
let writer = prepared.writer_id();
slot.handle = Some(Arc::new(prepared.start()?));
slot.writer = Some(writer);
Ok(())
}
pub(crate) fn handle(&self) -> Option<Arc<MetricsHandle>> {
self.0
.lock()
.unwrap_or_else(|e| e.into_inner())
.handle
.clone()
}
pub(super) fn writer_id(&self) -> Option<obzenflow_core::event::WriterId> {
self.0.lock().unwrap_or_else(|e| e.into_inner()).writer
}
pub(crate) fn request_abort(&self) {
let mut slot = self.0.lock().unwrap_or_else(|e| e.into_inner());
slot.cancelled = true;
if let Some(handle) = &slot.handle {
handle.request_abort();
}
}
pub(crate) async fn abort_and_join(&self) -> Result<(), HandleError> {
self.request_abort();
match self.handle() {
Some(handle) => handle.abort_and_wait().await,
None => Ok(()),
}
}
#[cfg(any(test, feature = "test-support"))]
pub(crate) fn install_for_test(&self, handle: MetricsHandle) {
let mut slot = self.0.lock().unwrap();
assert!(slot.handle.is_none());
if slot.cancelled {
handle.request_abort();
}
slot.handle = Some(Arc::new(handle));
}
}
#[derive(Clone, Copy, Debug)]
pub(super) enum StageCommand {
Initialize,
Ready,
Start,
Drain,
}
#[derive(Default)]
pub(super) struct StageDelivery {
commands: VecDeque<(Arc<dyn StageHandle>, StageCommand)>,
pending: Mutex<Option<BoxFuture<'static, Result<(), StageError>>>>,
}
impl StageDelivery {
pub(super) fn enqueue(
&mut self,
handles: Vec<Arc<dyn StageHandle>>,
commands: &[StageCommand],
stage_count: usize,
) -> Result<(), StageError> {
let count = handles.len().saturating_mul(commands.len());
if self.commands.len() + count > stage_count.saturating_mul(4) {
return Err(StageError::InvalidState(
"pipeline command delivery capacity exceeded".into(),
));
}
for handle in handles {
for command in commands {
self.commands.push_back((handle.clone(), *command));
}
}
Ok(())
}
pub(super) fn cancel(&mut self) {
self.commands.clear();
*self.pending.get_mut().unwrap_or_else(|e| e.into_inner()) = None;
}
pub(super) fn is_empty(&mut self) -> bool {
self.commands.is_empty()
&& self
.pending
.get_mut()
.unwrap_or_else(|e| e.into_inner())
.is_none()
}
pub(super) fn poll(&mut self, cx: &mut Context<'_>) -> Poll<Option<Result<(), StageError>>> {
let pending = self.pending.get_mut().unwrap_or_else(|e| e.into_inner());
if pending.is_none() {
let Some((handle, command)) = self.commands.pop_front() else {
return Poll::Ready(None);
};
*pending = Some(
async move {
match command {
StageCommand::Initialize => handle.initialize().await,
StageCommand::Ready => handle.ready().await,
StageCommand::Start => handle.start().await,
StageCommand::Drain => {
if handle.is_drained() {
return Ok(());
}
match handle.begin_drain().await {
Err(_) if handle.is_drained() => Ok(()),
result => result,
}
}
}
}
.boxed(),
);
}
match pending.as_mut().expect("pending command").as_mut().poll(cx) {
Poll::Pending => Poll::Pending,
Poll::Ready(result) => {
*pending = None;
Poll::Ready(Some(result))
}
}
}
}