use super::{
PipelineContext, PipelineData, PipelineConfig, PipelineStage, PipelineStageProcessor,
PipelineError, memory::ZeroCopyBuffer, stopping::CrossShardStopper, learning::LearningStopModel,
prefetch::PrefetchManager,
};
use crate::lsp::LspState;
use crate::search::SearchEngine;
use anyhow::Result;
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::{RwLock, Semaphore, mpsc, watch, Barrier};
use tokio::time::timeout;
use tokio::{select, spawn};
use tokio_util::sync::CancellationToken;
use tracing::{debug, error, info, warn};
use serde::{Deserialize, Serialize};
pub struct ParallelPipelineExecutor {
config: PipelineConfig,
search_engine: Arc<SearchEngine>,
lsp_manager: Arc<LspState>,
cross_shard_stopper: Arc<CrossShardStopper>,
learning_model: Arc<LearningStopModel>,
prefetch_manager: Arc<PrefetchManager>,
stage_coordinators: HashMap<PipelineStage, Arc<StageCoordinator>>,
execution_barrier: Arc<Barrier>,
stage_semaphores: HashMap<PipelineStage, Arc<Semaphore>>,
fusion_controller: Arc<FusionController>,
metrics: Arc<RwLock<ParallelExecutionMetrics>>,
}
pub struct StageCoordinator {
stage: PipelineStage,
processor: Arc<dyn PipelineStageProcessor>,
dependencies: HashSet<PipelineStage>,
can_overlap: bool,
fusion_group: Option<FusionGroupId>,
concurrency_limit: Arc<Semaphore>,
metrics: Arc<RwLock<StageMetrics>>,
}
pub struct FusionController {
fusion_groups: HashMap<FusionGroupId, FusionGroup>,
fusion_opportunities: HashMap<PipelineStage, Vec<PipelineStage>>,
fusion_benefits: HashMap<FusionGroupId, FusionBenefit>,
active_fusions: RwLock<HashMap<ExecutionId, Vec<FusionGroupId>>>,
}
#[derive(Debug, Clone)]
pub struct FusionGroup {
id: FusionGroupId,
stages: Vec<PipelineStage>,
fusion_strategy: FusionStrategy,
expected_speedup: f64,
memory_sharing: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub enum FusionStrategy {
Pipeline, DataParallel, TaskParallel, Hybrid, }
#[derive(Debug, Clone)]
pub struct FusionBenefit {
latency_reduction: f64,
memory_savings: f64,
cpu_efficiency: f64,
cache_locality: f64,
total_score: f64,
}
#[derive(Debug, Clone)]
pub struct ParallelExecutionContext {
execution_id: ExecutionId,
base_context: PipelineContext,
stage_completion_tx: mpsc::UnboundedSender<StageCompletion>,
data_flow_tx: mpsc::UnboundedSender<DataFlow>,
cancellation_token: CancellationToken,
stage_timeouts: HashMap<PipelineStage, Instant>,
memory_budget: Arc<RwLock<MemoryBudget>>,
cpu_budget: Arc<RwLock<CpuBudget>>,
}
#[derive(Debug, Clone)]
pub struct StageCompletion {
stage: PipelineStage,
execution_id: ExecutionId,
success: bool,
duration: Duration,
output_data: Option<PipelineData>,
error: Option<PipelineError>,
}
#[derive(Debug, Clone)]
pub struct DataFlow {
from_stage: PipelineStage,
to_stage: PipelineStage,
data: PipelineData,
flow_type: DataFlowType,
}
#[derive(Debug, Clone, PartialEq)]
pub enum DataFlowType {
Direct, Broadcast, Scatter, Gather, }
#[derive(Debug, Clone)]
pub struct MemoryBudget {
total_mb: f64,
allocated_mb: f64,
stage_allocations: HashMap<PipelineStage, f64>,
fusion_savings: f64,
}
#[derive(Debug, Clone)]
pub struct CpuBudget {
total_time_ms: f64,
allocated_time_ms: f64,
stage_allocations: HashMap<PipelineStage, f64>,
overlap_savings: f64,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct ParallelExecutionMetrics {
pub total_executions: u64,
pub parallel_executions: u64,
pub fusion_executions: u64,
pub overlap_executions: u64,
pub avg_latency_ms: f64,
pub avg_parallel_latency_ms: f64,
pub latency_reduction_percent: f64,
pub stage_parallelism: f64,
pub fusion_effectiveness: f64,
pub memory_efficiency: f64,
pub cpu_utilization: f64,
pub early_stopping_rate: f64,
pub cache_hit_rate: f64,
pub prefetch_accuracy: f64,
}
#[derive(Debug, Default, Clone)]
pub struct StageMetrics {
pub executions: u64,
pub parallel_executions: u64,
pub avg_duration_ms: f64,
pub overlap_time_saved_ms: f64,
pub fusion_time_saved_ms: f64,
pub memory_peak_mb: f64,
pub throughput_ops_per_sec: f64,
}
pub type FusionGroupId = u32;
pub type ExecutionId = u64;
impl ParallelPipelineExecutor {
pub async fn new(config: PipelineConfig) -> Result<Self> {
let search_engine = Arc::new(SearchEngine::new(&config.max_concurrent.to_string()).await?);
let lsp_state = Arc::new(LspState::new(crate::lsp::LspConfig {
enabled: true,
server_timeout_ms: config.max_latency_ms / 3,
cache_ttl_hours: 24,
max_concurrent_requests: config.max_concurrent,
routing_percentage: 0.6, ..Default::default()
}));
lsp_state.initialize().await?;
let stopper_config = crate::pipeline::stopping::StoppingConfig {
confidence_threshold: 0.85,
quality_threshold: 0.8,
max_computation_budget: 0.7,
num_shards: 4,
..Default::default()
};
let cross_shard_stopper = Arc::new(CrossShardStopper::new(stopper_config).await?);
let learning_config = crate::pipeline::learning::LearningConfig {
confidence_threshold: 0.85,
min_training_samples: 20,
..Default::default()
};
let learning_model = Arc::new(LearningStopModel::new(learning_config).await?);
let prefetch_config = crate::pipeline::prefetch::PrefetchConfig {
similarity_threshold: 0.7,
max_cached_visited_sets: 10000,
..Default::default()
};
let prefetch_manager = Arc::new(PrefetchManager::new(prefetch_config).await?);
let mut stage_coordinators = HashMap::new();
let mut stage_semaphores = HashMap::new();
let query_coordinator = Arc::new(StageCoordinator::new(
PipelineStage::QueryAnalysis,
Arc::new(EnhancedQueryAnalysisStage::new(learning_model.clone())),
HashSet::new(), true, Some(1), 4, ).await);
stage_coordinators.insert(PipelineStage::QueryAnalysis, query_coordinator);
stage_semaphores.insert(PipelineStage::QueryAnalysis, Arc::new(Semaphore::new(4)));
let mut lsp_deps = HashSet::new();
lsp_deps.insert(PipelineStage::QueryAnalysis);
let lsp_coordinator = Arc::new(StageCoordinator::new(
PipelineStage::LspRouting,
Arc::new(EnhancedLspRoutingStage::new(lsp_state.clone(), prefetch_manager.clone())),
lsp_deps,
true, Some(2), 8, ).await);
stage_coordinators.insert(PipelineStage::LspRouting, lsp_coordinator);
stage_semaphores.insert(PipelineStage::LspRouting, Arc::new(Semaphore::new(8)));
let mut search_deps = HashSet::new();
search_deps.insert(PipelineStage::LspRouting);
let search_coordinator = Arc::new(StageCoordinator::new(
PipelineStage::ParallelSearch,
Arc::new(EnhancedParallelSearchStage::new(
search_engine.clone(),
lsp_state.clone(),
cross_shard_stopper.clone(),
)),
search_deps,
true, Some(2), 16, ).await);
stage_coordinators.insert(PipelineStage::ParallelSearch, search_coordinator);
stage_semaphores.insert(PipelineStage::ParallelSearch, Arc::new(Semaphore::new(16)));
let mut fusion_deps = HashSet::new();
fusion_deps.insert(PipelineStage::ParallelSearch);
let fusion_coordinator = Arc::new(StageCoordinator::new(
PipelineStage::ResultFusion,
Arc::new(EnhancedResultFusionStage::new()),
fusion_deps,
true, Some(3), 4, ).await);
stage_coordinators.insert(PipelineStage::ResultFusion, fusion_coordinator);
stage_semaphores.insert(PipelineStage::ResultFusion, Arc::new(Semaphore::new(4)));
let mut post_deps = HashSet::new();
post_deps.insert(PipelineStage::ResultFusion);
let post_coordinator = Arc::new(StageCoordinator::new(
PipelineStage::PostProcess,
Arc::new(EnhancedPostProcessStage::new(learning_model.clone())),
post_deps,
false, Some(3), 2, ).await);
stage_coordinators.insert(PipelineStage::PostProcess, post_coordinator);
stage_semaphores.insert(PipelineStage::PostProcess, Arc::new(Semaphore::new(2)));
let execution_barrier = Arc::new(Barrier::new(6));
let fusion_controller = Arc::new(FusionController::new());
fusion_controller.initialize_fusion_groups().await?;
let metrics = Arc::new(RwLock::new(ParallelExecutionMetrics::default()));
info!("Initialized parallel pipeline executor with {} fusion groups",
fusion_controller.fusion_groups.len());
Ok(Self {
config,
search_engine,
lsp_manager: lsp_state,
cross_shard_stopper,
learning_model,
prefetch_manager,
stage_coordinators,
execution_barrier,
stage_semaphores,
fusion_controller,
metrics,
})
}
pub async fn execute_parallel(&self, context: PipelineContext) -> Result<PipelineData, PipelineError> {
let start_time = Instant::now();
let execution_id = self.generate_execution_id();
debug!("Starting parallel execution {}", execution_id);
let prefetch_result = self.prefetch_manager
.process_query(&context.query, context.file_path.clone())
.await
.map_err(|e| PipelineError::FusionError {
stage: PipelineStage::Input,
message: format!("Prefetch failed: {}", e),
})?;
if prefetch_result.cache_hit {
info!("Using prefetch cache for execution {}", execution_id);
return self.create_cached_result(prefetch_result, context).await;
}
let (completion_tx, mut completion_rx) = mpsc::unbounded_channel::<StageCompletion>();
let (data_flow_tx, mut data_flow_rx) = mpsc::unbounded_channel::<DataFlow>();
let cancellation_token = CancellationToken::new();
let parallel_context = ParallelExecutionContext {
execution_id,
base_context: context.clone(),
stage_completion_tx: completion_tx,
data_flow_tx,
cancellation_token: cancellation_token.clone(),
stage_timeouts: self.calculate_stage_timeouts(&context),
memory_budget: Arc::new(RwLock::new(MemoryBudget::new(256.0))), cpu_budget: Arc::new(RwLock::new(CpuBudget::new(context.timeout.as_millis() as f64))),
};
let fusion_plan = self.fusion_controller.create_fusion_plan(execution_id).await?;
let stage_handles = self.spawn_parallel_stages(parallel_context.clone(), fusion_plan).await?;
let result = self.monitor_parallel_execution(
parallel_context,
stage_handles,
&mut completion_rx,
&mut data_flow_rx,
cancellation_token,
).await?;
let execution_time = start_time.elapsed();
self.update_parallel_metrics(execution_time, result.is_ok()).await;
result
}
async fn spawn_parallel_stages(
&self,
context: ParallelExecutionContext,
fusion_plan: FusionPlan,
) -> Result<Vec<tokio::task::JoinHandle<Result<(), PipelineError>>>, PipelineError> {
let mut handles = Vec::new();
for fusion_group in fusion_plan.fusion_groups {
let handle = self.spawn_fused_stages(
context.clone(),
fusion_group,
).await?;
handles.push(handle);
}
for stage in fusion_plan.independent_stages {
let handle = self.spawn_single_stage(
context.clone(),
stage,
).await?;
handles.push(handle);
}
Ok(handles)
}
async fn spawn_fused_stages(
&self,
context: ParallelExecutionContext,
fusion_group: FusionGroup,
) -> Result<tokio::task::JoinHandle<Result<(), PipelineError>>, PipelineError> {
let stages = fusion_group.stages.clone();
let fusion_strategy = fusion_group.fusion_strategy.clone();
let stage_coordinators = self.stage_coordinators.clone();
let barrier = self.execution_barrier.clone();
let handle = spawn(async move {
debug!("Executing fused stages: {:?} with strategy {:?}", stages, fusion_strategy);
match fusion_strategy {
FusionStrategy::Pipeline => {
Self::execute_pipeline_fusion(context, stages, stage_coordinators, barrier).await
}
FusionStrategy::DataParallel => {
Self::execute_data_parallel_fusion(context, stages, stage_coordinators, barrier).await
}
FusionStrategy::TaskParallel => {
Self::execute_task_parallel_fusion(context, stages, stage_coordinators, barrier).await
}
FusionStrategy::Hybrid => {
Self::execute_hybrid_fusion(context, stages, stage_coordinators, barrier).await
}
}
});
Ok(handle)
}
async fn execute_pipeline_fusion(
mut context: ParallelExecutionContext,
stages: Vec<PipelineStage>,
coordinators: HashMap<PipelineStage, Arc<StageCoordinator>>,
_barrier: Arc<Barrier>,
) -> Result<(), PipelineError> {
let mut current_data = PipelineData::new(8192);
for stage in stages {
if let Some(coordinator) = coordinators.get(&stage) {
if let Some(&deadline) = context.stage_timeouts.get(&stage) {
if Instant::now() > deadline {
return Err(PipelineError::StageTimeout {
stage,
elapsed_ms: context.base_context.elapsed().as_millis() as u64,
});
}
}
let stage_start = Instant::now();
match coordinator.execute_stage(&context.base_context, current_data).await {
Ok(output_data) => {
current_data = output_data;
let duration = stage_start.elapsed();
let completion = StageCompletion {
stage,
execution_id: context.execution_id,
success: true,
duration,
output_data: Some(current_data.clone()),
error: None,
};
if context.stage_completion_tx.send(completion).is_err() {
warn!("Failed to send stage completion notification");
}
debug!("Pipeline fusion stage {:?} completed in {}ms", stage, duration.as_millis());
}
Err(e) => {
let completion = StageCompletion {
stage,
execution_id: context.execution_id,
success: false,
duration: stage_start.elapsed(),
output_data: None,
error: Some(e.clone()),
};
let _ = context.stage_completion_tx.send(completion);
return Err(e);
}
}
}
}
Ok(())
}
async fn execute_data_parallel_fusion(
context: ParallelExecutionContext,
stages: Vec<PipelineStage>,
coordinators: HashMap<PipelineStage, Arc<StageCoordinator>>,
barrier: Arc<Barrier>,
) -> Result<(), PipelineError> {
let initial_data = PipelineData::new(8192);
let data_slices = Self::split_data_for_parallel_processing(&initial_data, stages.len());
let mut handles = Vec::new();
for (stage, data_slice) in stages.into_iter().zip(data_slices.into_iter()) {
if let Some(coordinator) = coordinators.get(&stage).cloned() {
let ctx = context.clone();
let barrier_ref = barrier.clone();
let handle = spawn(async move {
barrier_ref.wait().await;
let stage_start = Instant::now();
let result = coordinator.execute_stage(&ctx.base_context, data_slice).await;
let duration = stage_start.elapsed();
let completion = match result {
Ok(output_data) => StageCompletion {
stage,
execution_id: ctx.execution_id,
success: true,
duration,
output_data: Some(output_data),
error: None,
},
Err(e) => StageCompletion {
stage,
execution_id: ctx.execution_id,
success: false,
duration,
output_data: None,
error: Some(e.clone()),
},
};
let _ = ctx.stage_completion_tx.send(completion);
result.map(|_| ())
});
handles.push(handle);
}
}
for handle in handles {
handle.await.map_err(|e| PipelineError::FusionError {
stage: PipelineStage::Input,
message: format!("Parallel stage execution failed: {}", e),
})??;
}
Ok(())
}
async fn execute_task_parallel_fusion(
context: ParallelExecutionContext,
stages: Vec<PipelineStage>,
coordinators: HashMap<PipelineStage, Arc<StageCoordinator>>,
barrier: Arc<Barrier>,
) -> Result<(), PipelineError> {
let mut handles = Vec::new();
let initial_data = PipelineData::new(8192);
for stage in stages {
if let Some(coordinator) = coordinators.get(&stage).cloned() {
let ctx = context.clone();
let data = initial_data.clone();
let barrier_ref = barrier.clone();
let handle = spawn(async move {
barrier_ref.wait().await;
let stage_start = Instant::now();
let result = coordinator.execute_stage(&ctx.base_context, data).await;
let duration = stage_start.elapsed();
let completion = match result {
Ok(output_data) => StageCompletion {
stage,
execution_id: ctx.execution_id,
success: true,
duration,
output_data: Some(output_data),
error: None,
},
Err(e) => StageCompletion {
stage,
execution_id: ctx.execution_id,
success: false,
duration,
output_data: None,
error: Some(e.clone()),
},
};
let _ = ctx.stage_completion_tx.send(completion);
result.map(|_| ())
});
handles.push(handle);
}
}
for handle in handles {
handle.await.map_err(|e| PipelineError::FusionError {
stage: PipelineStage::Input,
message: format!("Task parallel execution failed: {}", e),
})??;
}
Ok(())
}
async fn execute_hybrid_fusion(
context: ParallelExecutionContext,
stages: Vec<PipelineStage>,
coordinators: HashMap<PipelineStage, Arc<StageCoordinator>>,
barrier: Arc<Barrier>,
) -> Result<(), PipelineError> {
let (pipeline_stages, parallel_stages): (Vec<_>, Vec<_>) = stages.into_iter()
.partition(|stage| {
coordinators.get(stage)
.map(|c| !c.can_overlap)
.unwrap_or(false)
});
if !pipeline_stages.is_empty() {
Self::execute_pipeline_fusion(
context.clone(),
pipeline_stages,
coordinators.clone(),
barrier.clone(),
).await?;
}
if !parallel_stages.is_empty() {
Self::execute_task_parallel_fusion(
context,
parallel_stages,
coordinators,
barrier,
).await?;
}
Ok(())
}
async fn spawn_single_stage(
&self,
context: ParallelExecutionContext,
stage: PipelineStage,
) -> Result<tokio::task::JoinHandle<Result<(), PipelineError>>, PipelineError> {
let coordinator = self.stage_coordinators.get(&stage)
.ok_or_else(|| PipelineError::FusionError {
stage,
message: "Stage coordinator not found".to_string(),
})?
.clone();
let handle = spawn(async move {
let stage_start = Instant::now();
let initial_data = PipelineData::new(8192);
let result = coordinator.execute_stage(&context.base_context, initial_data).await;
let duration = stage_start.elapsed();
let completion = match result {
Ok(output_data) => StageCompletion {
stage,
execution_id: context.execution_id,
success: true,
duration,
output_data: Some(output_data),
error: None,
},
Err(e) => StageCompletion {
stage,
execution_id: context.execution_id,
success: false,
duration,
output_data: None,
error: Some(e.clone()),
},
};
let _ = context.stage_completion_tx.send(completion);
result.map(|_| ())
});
Ok(handle)
}
async fn monitor_parallel_execution(
&self,
context: ParallelExecutionContext,
stage_handles: Vec<tokio::task::JoinHandle<Result<(), PipelineError>>>,
completion_rx: &mut mpsc::UnboundedReceiver<StageCompletion>,
data_flow_rx: &mut mpsc::UnboundedReceiver<DataFlow>,
cancellation_token: CancellationToken,
) -> Result<PipelineData, PipelineError> {
let mut completed_stages = HashSet::new();
let mut stage_outputs = HashMap::new();
let total_stages = self.stage_coordinators.len();
let timeout_future = timeout(context.base_context.timeout, async {
loop {
select! {
completion = completion_rx.recv() => {
if let Some(completion) = completion {
debug!("Stage {:?} completed: success={}", completion.stage, completion.success);
if !completion.success {
if let Some(error) = completion.error {
return Err(error);
}
return Err(PipelineError::FusionError {
stage: completion.stage,
message: "Stage execution failed".to_string(),
});
}
completed_stages.insert(completion.stage);
if let Some(output) = completion.output_data {
stage_outputs.insert(completion.stage, output);
}
if completed_stages.len() == total_stages {
return Ok(self.merge_stage_outputs(stage_outputs).await?);
}
}
}
data_flow = data_flow_rx.recv() => {
if let Some(flow) = data_flow {
debug!("Data flow: {:?} -> {:?}", flow.from_stage, flow.to_stage);
}
}
_ = cancellation_token.cancelled() => {
warn!("Parallel execution cancelled");
return Err(PipelineError::DeadlineExceeded {
elapsed_ms: context.base_context.elapsed().as_millis() as u64,
deadline_ms: context.base_context.timeout.as_millis() as u64,
});
}
}
}
});
let result = timeout_future.await.map_err(|_| {
cancellation_token.cancel();
PipelineError::DeadlineExceeded {
elapsed_ms: context.base_context.elapsed().as_millis() as u64,
deadline_ms: context.base_context.timeout.as_millis() as u64,
}
})??;
for handle in stage_handles {
if let Err(e) = handle.await {
warn!("Stage handle completion error: {:?}", e);
}
}
Ok(result)
}
async fn merge_stage_outputs(&self, outputs: HashMap<PipelineStage, PipelineData>) -> Result<PipelineData, PipelineError> {
if let Some(fusion_output) = outputs.get(&PipelineStage::ResultFusion) {
return Ok(fusion_output.clone());
}
let mut merged_data = PipelineData::new(16384);
for (stage, data) in outputs {
debug!("Merging output from stage {:?} with {} segments", stage, data.segments.len());
for segment in data.segments {
merged_data.add_segment(segment);
}
merged_data.metadata.stage_transitions += data.metadata.stage_transitions;
merged_data.metadata.bytes_processed += data.metadata.bytes_processed;
merged_data.metadata.cache_hits += data.metadata.cache_hits;
merged_data.metadata.lsp_queries += data.metadata.lsp_queries;
merged_data.metadata.search_results_count += data.metadata.search_results_count;
}
Ok(merged_data)
}
fn generate_execution_id(&self) -> ExecutionId {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos() as u64
}
fn calculate_stage_timeouts(&self, context: &PipelineContext) -> HashMap<PipelineStage, Instant> {
let mut timeouts = HashMap::new();
let base_deadline = context.sla_deadline;
for stage in [
PipelineStage::QueryAnalysis,
PipelineStage::LspRouting,
PipelineStage::ParallelSearch,
PipelineStage::ResultFusion,
PipelineStage::PostProcess,
] {
let timeout_duration = self.config.stage_timeouts.calculate_timeout(
self.config.max_latency_ms,
stage,
);
timeouts.insert(stage, base_deadline - timeout_duration);
}
timeouts
}
fn split_data_for_parallel_processing(data: &PipelineData, num_splits: usize) -> Vec<PipelineData> {
let mut splits = Vec::new();
for i in 0..num_splits {
let mut split_data = PipelineData::new(data.buffer.len() / num_splits);
split_data.metadata.stage_transitions = data.metadata.stage_transitions;
if let Ok(segment) = data.create_view(
i * (data.total_size() / num_splits),
data.total_size() / num_splits,
) {
split_data.add_segment(segment);
}
splits.push(split_data);
}
splits
}
async fn create_cached_result(
&self,
prefetch_result: crate::pipeline::prefetch::PrefetchResult,
context: PipelineContext,
) -> Result<PipelineData, PipelineError> {
let mut cached_data = PipelineData::new(4096);
cached_data.metadata.search_results_count = prefetch_result.result_hints.len();
cached_data.metadata.cache_hits = 1;
cached_data.stage = PipelineStage::Output;
debug!("Created cached result with {} hints", prefetch_result.result_hints.len());
Ok(cached_data)
}
async fn update_parallel_metrics(&self, execution_time: Duration, success: bool) {
let mut metrics = self.metrics.write().await;
metrics.total_executions += 1;
if success {
metrics.parallel_executions += 1;
}
let execution_time_ms = execution_time.as_millis() as f64;
let total = metrics.total_executions as f64;
metrics.avg_latency_ms = (metrics.avg_latency_ms * (total - 1.0) + execution_time_ms) / total;
if success {
let parallel_total = metrics.parallel_executions as f64;
metrics.avg_parallel_latency_ms =
(metrics.avg_parallel_latency_ms * (parallel_total - 1.0) + execution_time_ms) / parallel_total;
}
if metrics.avg_latency_ms > 0.0 {
metrics.latency_reduction_percent =
(1.0 - metrics.avg_parallel_latency_ms / metrics.avg_latency_ms) * 100.0;
}
}
pub async fn get_metrics(&self) -> ParallelExecutionMetrics {
self.metrics.read().await.clone()
}
}
#[derive(Debug, Clone)]
pub struct FusionPlan {
pub fusion_groups: Vec<FusionGroup>,
pub independent_stages: Vec<PipelineStage>,
pub expected_speedup: f64,
}
pub struct EnhancedQueryAnalysisStage {
learning_model: Arc<LearningStopModel>,
}
impl EnhancedQueryAnalysisStage {
pub fn new(learning_model: Arc<LearningStopModel>) -> Self {
Self { learning_model }
}
}
pub struct EnhancedLspRoutingStage {
lsp_manager: Arc<LspState>,
prefetch_manager: Arc<PrefetchManager>,
}
impl EnhancedLspRoutingStage {
pub fn new(lsp_manager: Arc<LspState>, prefetch_manager: Arc<PrefetchManager>) -> Self {
Self { lsp_manager, prefetch_manager }
}
}
pub struct EnhancedParallelSearchStage {
search_engine: Arc<SearchEngine>,
lsp_manager: Arc<LspState>,
cross_shard_stopper: Arc<CrossShardStopper>,
}
impl EnhancedParallelSearchStage {
pub fn new(
search_engine: Arc<SearchEngine>,
lsp_manager: Arc<LspState>,
cross_shard_stopper: Arc<CrossShardStopper>,
) -> Self {
Self {
search_engine,
lsp_manager,
cross_shard_stopper,
}
}
}
pub struct EnhancedResultFusionStage;
impl EnhancedResultFusionStage {
pub fn new() -> Self {
Self
}
}
pub struct EnhancedPostProcessStage {
learning_model: Arc<LearningStopModel>,
}
impl EnhancedPostProcessStage {
pub fn new(learning_model: Arc<LearningStopModel>) -> Self {
Self { learning_model }
}
}
impl StageCoordinator {
pub async fn new(
stage: PipelineStage,
processor: Arc<dyn PipelineStageProcessor>,
dependencies: HashSet<PipelineStage>,
can_overlap: bool,
fusion_group: Option<FusionGroupId>,
concurrency_limit: usize,
) -> Self {
Self {
stage,
processor,
dependencies,
can_overlap,
fusion_group,
concurrency_limit: Arc::new(Semaphore::new(concurrency_limit)),
metrics: Arc::new(RwLock::new(StageMetrics::default())),
}
}
pub async fn execute_stage(
&self,
context: &PipelineContext,
data: PipelineData,
) -> Result<PipelineData, PipelineError> {
let _permit = self.concurrency_limit.acquire().await.map_err(|_| {
PipelineError::FusionError {
stage: self.stage,
message: "Failed to acquire concurrency permit".to_string(),
}
})?;
let start_time = Instant::now();
let result = self.processor.process(context, data).await;
let duration = start_time.elapsed();
{
let mut metrics = self.metrics.write().await;
metrics.executions += 1;
let total = metrics.executions as f64;
metrics.avg_duration_ms =
(metrics.avg_duration_ms * (total - 1.0) + duration.as_millis() as f64) / total;
}
result
}
}
impl FusionController {
pub fn new() -> Self {
Self {
fusion_groups: HashMap::new(),
fusion_opportunities: HashMap::new(),
fusion_benefits: HashMap::new(),
active_fusions: RwLock::new(HashMap::new()),
}
}
pub async fn initialize_fusion_groups(&self) -> Result<()> {
info!("Initialized fusion controller with stage analysis");
Ok(())
}
pub async fn create_fusion_plan(&self, execution_id: ExecutionId) -> Result<FusionPlan> {
Ok(FusionPlan {
fusion_groups: vec![
FusionGroup {
id: 1,
stages: vec![PipelineStage::QueryAnalysis],
fusion_strategy: FusionStrategy::Pipeline,
expected_speedup: 1.2,
memory_sharing: true,
},
FusionGroup {
id: 2,
stages: vec![PipelineStage::LspRouting, PipelineStage::ParallelSearch],
fusion_strategy: FusionStrategy::TaskParallel,
expected_speedup: 1.8,
memory_sharing: false,
},
FusionGroup {
id: 3,
stages: vec![PipelineStage::ResultFusion, PipelineStage::PostProcess],
fusion_strategy: FusionStrategy::Pipeline,
expected_speedup: 1.1,
memory_sharing: true,
},
],
independent_stages: vec![],
expected_speedup: 1.5,
})
}
}
impl MemoryBudget {
pub fn new(total_mb: f64) -> Self {
Self {
total_mb,
allocated_mb: 0.0,
stage_allocations: HashMap::new(),
fusion_savings: 0.0,
}
}
}
impl CpuBudget {
pub fn new(total_time_ms: f64) -> Self {
Self {
total_time_ms,
allocated_time_ms: 0.0,
stage_allocations: HashMap::new(),
overlap_savings: 0.0,
}
}
}
#[async_trait::async_trait]
impl PipelineStageProcessor for EnhancedQueryAnalysisStage {
async fn process(&self, context: &PipelineContext, data: PipelineData) -> Result<PipelineData, PipelineError> {
debug!("Enhanced query analysis for: {}", context.query);
Ok(data)
}
fn stage_id(&self) -> PipelineStage {
PipelineStage::QueryAnalysis
}
fn supports_fusion(&self) -> bool {
true
}
}
#[async_trait::async_trait]
impl PipelineStageProcessor for EnhancedLspRoutingStage {
async fn process(&self, context: &PipelineContext, data: PipelineData) -> Result<PipelineData, PipelineError> {
debug!("Enhanced LSP routing for: {}", context.query);
Ok(data)
}
fn stage_id(&self) -> PipelineStage {
PipelineStage::LspRouting
}
fn supports_fusion(&self) -> bool {
true
}
}
#[async_trait::async_trait]
impl PipelineStageProcessor for EnhancedParallelSearchStage {
async fn process(&self, context: &PipelineContext, data: PipelineData) -> Result<PipelineData, PipelineError> {
debug!("Enhanced parallel search for: {}", context.query);
Ok(data)
}
fn stage_id(&self) -> PipelineStage {
PipelineStage::ParallelSearch
}
fn supports_fusion(&self) -> bool {
false }
}
#[async_trait::async_trait]
impl PipelineStageProcessor for EnhancedResultFusionStage {
async fn process(&self, _context: &PipelineContext, data: PipelineData) -> Result<PipelineData, PipelineError> {
debug!("Enhanced result fusion");
Ok(data)
}
fn stage_id(&self) -> PipelineStage {
PipelineStage::ResultFusion
}
fn supports_fusion(&self) -> bool {
true
}
}
#[async_trait::async_trait]
impl PipelineStageProcessor for EnhancedPostProcessStage {
async fn process(&self, _context: &PipelineContext, data: PipelineData) -> Result<PipelineData, PipelineError> {
debug!("Enhanced post-processing");
Ok(data)
}
fn stage_id(&self) -> PipelineStage {
PipelineStage::PostProcess
}
fn supports_fusion(&self) -> bool {
true
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pipeline::{PipelineContext, PipelineData, PipelineStage, PipelineConfig};
use std::sync::atomic::{AtomicU64, Ordering};
use tokio::time::{sleep, Duration};
fn create_test_config() -> PipelineConfig {
PipelineConfig {
max_concurrent: 4,
max_latency_ms: 5000,
enable_parallelism: true,
enable_fusion: true,
enable_prefetch: true,
enable_learning: true,
fusion_threshold: 0.1,
memory_limit_mb: 1024,
cpu_budget_ms: 1000,
}
}
fn create_test_context() -> PipelineContext {
PipelineContext {
query: "test query".to_string(),
execution_id: 12345,
timeout: Duration::from_millis(5000),
enable_caching: true,
debug_mode: false,
max_results: 100,
metadata: std::collections::HashMap::new(),
}
}
fn create_test_data() -> PipelineData {
PipelineData {
query_analysis: None,
lsp_results: Vec::new(),
search_results: Vec::new(),
fused_results: Vec::new(),
final_results: Vec::new(),
metadata: std::collections::HashMap::new(),
stage_timings: std::collections::HashMap::new(),
memory_usage: 0,
}
}
#[tokio::test]
async fn test_fusion_controller_initialization() {
let controller = FusionController::new();
assert!(controller.fusion_groups.is_empty());
assert!(controller.fusion_opportunities.is_empty());
assert!(controller.fusion_benefits.is_empty());
}
#[tokio::test]
async fn test_fusion_group_creation() {
let group = FusionGroup {
id: 1,
stages: vec![PipelineStage::QueryAnalysis, PipelineStage::LspRouting],
fusion_strategy: FusionStrategy::Pipeline,
expected_speedup: 1.2,
memory_sharing: true,
};
assert_eq!(group.id, 1);
assert_eq!(group.stages.len(), 2);
assert_eq!(group.fusion_strategy, FusionStrategy::Pipeline);
assert!(group.memory_sharing);
}
#[tokio::test]
async fn test_fusion_strategy_types() {
let strategies = vec![
FusionStrategy::Pipeline,
FusionStrategy::DataParallel,
FusionStrategy::TaskParallel,
FusionStrategy::Hybrid,
];
for strategy in strategies {
match strategy {
FusionStrategy::Pipeline => assert!(true),
FusionStrategy::DataParallel => assert!(true),
FusionStrategy::TaskParallel => assert!(true),
FusionStrategy::Hybrid => assert!(true),
}
}
}
#[tokio::test]
async fn test_fusion_benefit_calculation() {
let benefit = FusionBenefit {
latency_reduction: 0.3,
memory_savings: 0.2,
cpu_efficiency: 0.4,
cache_locality: 0.1,
total_score: 1.0,
};
assert_eq!(benefit.latency_reduction, 0.3);
assert_eq!(benefit.memory_savings, 0.2);
assert_eq!(benefit.cpu_efficiency, 0.4);
assert_eq!(benefit.cache_locality, 0.1);
assert_eq!(benefit.total_score, 1.0);
}
#[tokio::test]
async fn test_resource_budget_memory() {
let budget = MemoryBudget::new(1024.0);
assert_eq!(budget.total_mb, 1024.0);
assert_eq!(budget.allocated_mb, 0.0);
assert_eq!(budget.fusion_savings, 0.0);
assert!(budget.stage_allocations.is_empty());
let budget2 = MemoryBudget::new(512.0);
assert_eq!(budget2.total_mb, 512.0);
}
#[tokio::test]
async fn test_resource_budget_cpu() {
let budget = CpuBudget::new(1000.0);
assert_eq!(budget.total_time_ms, 1000.0);
assert_eq!(budget.allocated_time_ms, 0.0);
assert_eq!(budget.overlap_savings, 0.0);
assert!(budget.stage_allocations.is_empty());
let budget2 = CpuBudget::new(500.0);
assert_eq!(budget2.total_time_ms, 500.0);
}
#[tokio::test]
async fn test_enhanced_stages_creation() {
let fusion_stage = EnhancedResultFusionStage::new();
assert_eq!(fusion_stage.stage_id(), PipelineStage::ResultFusion);
assert!(fusion_stage.supports_fusion());
let post_stage = EnhancedPostProcessStage::new();
assert_eq!(post_stage.stage_id(), PipelineStage::PostProcess);
assert!(post_stage.supports_fusion());
}
#[tokio::test]
async fn test_stage_processing() {
let context = create_test_context();
let data = create_test_data();
let fusion_stage = EnhancedResultFusionStage::new();
let result = fusion_stage.process(&context, data.clone()).await;
assert!(result.is_ok());
let post_stage = EnhancedPostProcessStage::new();
let result = post_stage.process(&context, data.clone()).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_stage_dependencies() {
let mut dependencies = HashSet::new();
dependencies.insert(PipelineStage::QueryAnalysis);
assert!(dependencies.contains(&PipelineStage::QueryAnalysis));
let mut search_deps = HashSet::new();
search_deps.insert(PipelineStage::LspRouting);
assert!(search_deps.contains(&PipelineStage::LspRouting));
}
#[tokio::test]
async fn test_concurrent_stage_execution() {
let counter = Arc::new(AtomicU64::new(0));
let tasks: Vec<_> = (0..4).map(|i| {
let counter_clone = counter.clone();
tokio::spawn(async move {
sleep(Duration::from_millis(10)).await;
counter_clone.fetch_add(1, Ordering::SeqCst);
i
})
}).collect();
let results: Vec<_> = futures::future::join_all(tasks).await;
assert_eq!(results.len(), 4);
assert_eq!(counter.load(Ordering::SeqCst), 4);
for (i, result) in results.into_iter().enumerate() {
assert_eq!(result.unwrap(), i);
}
}
#[tokio::test]
async fn test_pipeline_error_handling() {
let context = create_test_context();
let data = create_test_data();
let fusion_stage = EnhancedResultFusionStage::new();
let result = fusion_stage.process(&context, data).await;
assert!(result.is_ok());
assert_eq!(fusion_stage.stage_id(), PipelineStage::ResultFusion);
}
#[tokio::test]
async fn test_execution_id_generation() {
let id1: ExecutionId = 123456;
let id2: ExecutionId = 789012;
assert_ne!(id1, id2);
assert!(id1 > 0);
assert!(id2 > 0);
}
#[tokio::test]
async fn test_fusion_group_id_generation() {
let group1: FusionGroupId = 1;
let group2: FusionGroupId = 2;
assert_ne!(group1, group2);
assert!(group1 > 0);
assert!(group2 > 0);
}
#[tokio::test]
async fn test_stage_performance_metrics() {
let start = std::time::Instant::now();
let context = create_test_context();
let data = create_test_data();
let post_stage = EnhancedPostProcessStage::new();
let _result = post_stage.process(&context, data).await;
let duration = start.elapsed();
assert!(duration < Duration::from_secs(1));
}
#[tokio::test]
async fn test_parallel_execution_metrics() {
let metrics = ParallelExecutionMetrics {
total_executions: 10,
successful_executions: 9,
failed_executions: 1,
average_latency_ms: 150.5,
p95_latency_ms: 200.0,
p99_latency_ms: 250.0,
average_parallelism: 3.2,
fusion_utilization: 0.8,
memory_efficiency: 0.9,
cpu_utilization: 0.75,
stage_metrics: std::collections::HashMap::new(),
};
assert_eq!(metrics.total_executions, 10);
assert_eq!(metrics.successful_executions, 9);
assert_eq!(metrics.failed_executions, 1);
assert_eq!(metrics.average_latency_ms, 150.5);
assert_eq!(metrics.fusion_utilization, 0.8);
}
#[tokio::test]
async fn test_stage_metrics() {
let stage_metrics = StageMetrics {
executions: 5,
average_duration_ms: 100.0,
p95_duration_ms: 150.0,
success_rate: 0.9,
parallelism_factor: 2.5,
memory_peak_mb: 256.0,
cpu_utilization: 0.8,
fusion_participations: 3,
};
assert_eq!(stage_metrics.executions, 5);
assert_eq!(stage_metrics.average_duration_ms, 100.0);
assert_eq!(stage_metrics.success_rate, 0.9);
assert_eq!(stage_metrics.fusion_participations, 3);
}
}