use super::status::PipelineStatus;
use crate::BackpressureStrategy;
use crate::Counter;
use crate::ParallelConfig;
use crate::RetryStrategy;
use crate::Timer;
use crate::error_handling::BackpressureController;
use crate::error_handling::ErrorHandler;
use crate::metrics::Metrics;
use fluxus_sinks::Sink;
use fluxus_sinks::dummy_sink::DummySink;
use fluxus_sources::Source;
use fluxus_transformers::operator::Operator;
use fluxus_utils::models::Record;
use fluxus_utils::models::StreamResult;
use fluxus_utils::time::current_time;
use fluxus_utils::window::WindowConfig;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::runtime::Handle;
use tokio::time;
use tracing;
pub struct Pipeline<T: Clone> {
source: Box<dyn Source<T>>,
operators: Vec<Box<dyn Operator<T, T>>>,
sink: Box<dyn Sink<T>>,
window_config: Option<WindowConfig>,
parallel_config: ParallelConfig,
status: PipelineStatus,
last_watermark: i64,
metrics: Arc<Metrics>,
process_timer: Arc<Timer>,
records_processed: Arc<Counter>,
records_failed: Arc<Counter>,
error_handler: ErrorHandler,
backpressure: BackpressureController,
}
impl<T: 'static + Send + Clone> Pipeline<T> {
pub fn source<S: Source<T> + 'static>(source: S) -> Self {
let mut metrics = Metrics::new();
let process_timer = metrics.timer("process_time");
let records_processed = metrics.counter("records_processed");
let records_failed = metrics.counter("records_failed");
Self {
source: Box::new(source),
operators: Vec::new(),
sink: Box::new(DummySink::new()),
window_config: None,
parallel_config: ParallelConfig::default(),
status: PipelineStatus::Ready,
last_watermark: 0,
metrics: Arc::new(metrics),
process_timer,
records_processed,
records_failed,
error_handler: ErrorHandler::new(RetryStrategy::exponential(
Duration::from_millis(100),
Duration::from_secs(10),
3,
2.0,
)),
backpressure: BackpressureController::new(BackpressureStrategy::Throttle {
high_watermark: 1000,
low_watermark: 100,
backoff: Duration::from_millis(50),
}),
}
}
pub fn add_operator<O: Operator<T, T> + 'static>(mut self, operator: O) -> Self {
self.operators.push(Box::new(operator));
self
}
pub fn sink<S: Sink<T> + 'static>(mut self, sink: S) -> Self {
self.sink = Box::new(sink);
self
}
pub fn window(mut self, config: WindowConfig) -> Self {
self.window_config = Some(config);
self
}
pub fn parallel(mut self, config: ParallelConfig) -> Self {
self.parallel_config = config;
self
}
pub fn with_retry_strategy(mut self, strategy: RetryStrategy) -> Self {
self.error_handler = ErrorHandler::new(strategy);
self
}
pub fn with_backpressure_strategy(mut self, strategy: BackpressureStrategy) -> Self {
self.backpressure = BackpressureController::new(strategy);
self
}
pub fn status(&self) -> PipelineStatus {
self.status
}
pub fn metrics(&self) -> &Arc<Metrics> {
&self.metrics
}
async fn process_watermark(&mut self) -> StreamResult<()> {
if let Some(window_config) = &self.window_config {
let now = current_time() as i64;
if now - self.last_watermark >= window_config.watermark_delay.as_millis() as i64 {
self.last_watermark = now;
for op in &mut self.operators {
let results = op.on_window_trigger().await?;
for record in results {
self.sink.write(record).await?;
}
}
}
}
Ok(())
}
async fn process_with_retry(
error_handler: &ErrorHandler,
op: &mut Box<dyn Operator<T, T>>,
record: Record<T>,
) -> StreamResult<Vec<Record<T>>> {
let record = record.clone();
let op_ref = &mut **op;
error_handler
.retry(|| {
let rt = Handle::current();
rt.block_on(op_ref.process(record.clone()))
})
.await
}
async fn write_with_retry(
error_handler: &ErrorHandler,
sink: &mut Box<dyn Sink<T>>,
record: Record<T>,
) -> StreamResult<()> {
let record = record.clone();
let sink_ref = &mut **sink;
error_handler
.retry(|| {
let rt = Handle::current();
rt.block_on(sink_ref.write(record.clone()))
})
.await
}
pub async fn execute(mut self) -> StreamResult<()> {
self.status = PipelineStatus::Running;
self.source.init().await?;
for op in &mut self.operators {
op.init().await?;
}
self.sink.init().await?;
let mut watermark_interval = time::interval(Duration::from_millis(100));
loop {
if self.backpressure.should_apply_backpressure() {
if let Some(backoff) = self.backpressure.get_backoff() {
tracing::debug!("Applying backpressure, waiting for {:?}", backoff);
time::sleep(backoff).await;
continue;
}
}
tokio::select! {
result = self.source.next() => {
match result {
Ok(Some(record)) => {
let start = Instant::now();
let mut records = vec![record];
let mut success = true;
for op in &mut self.operators {
let mut next = Vec::new();
let current_records = std::mem::take(&mut records);
for record in current_records {
match Self::process_with_retry(&self.error_handler, op, record).await {
Ok(mut results) => next.append(&mut results),
Err(e) => {
self.records_failed.increment();
success = false;
tracing::error!("Operator error after retries: {}", e);
break;
}
}
}
if !success {
break;
}
records = next;
}
let record_count = records.len();
self.backpressure.update_load(record_count);
if success {
while let Some(record) = records.pop() {
match Self::write_with_retry(&self.error_handler, &mut self.sink, record).await {
Ok(_) => {
self.records_processed.increment();
}
Err(e) => {
self.records_failed.increment();
tracing::error!("Sink error after retries: {}", e);
}
}
}
}
if let Some(timer) = Arc::get_mut(&mut self.process_timer) {
timer.record(start.elapsed());
}
}
Ok(None) => break,
Err(e) => {
self.records_failed.increment();
tracing::error!("Source error: {}", e);
return Err(e);
}
}
}
_ = watermark_interval.tick() => {
if let Err(e) = self.process_watermark().await {
tracing::error!("Watermark error: {}", e);
}
}
}
}
self.sink.flush().await?;
self.sink.close().await?;
self.status = PipelineStatus::Completed;
Ok(())
}
}