1use std::collections::{HashMap, HashSet};
19use std::convert::TryInto;
20use std::fmt::{Debug, Formatter};
21use std::iter::FromIterator;
22use std::sync::Arc;
23use std::time::{SystemTime, UNIX_EPOCH};
24
25use datafusion::physical_plan::display::DisplayableExecutionPlan;
26use datafusion::physical_plan::{ExecutionPlan, ExecutionPlanVisitor, accept};
27use datafusion::prelude::SessionConfig;
28use log::{debug, error, info, warn};
29
30use ballista_core::JobId;
31use ballista_core::error::{BallistaError, Result};
32use ballista_core::execution_plans::{
33 ShuffleWriter, ShuffleWriterExec, SortShuffleWriterExec, UnresolvedShuffleExec,
34};
35use ballista_core::serde::protobuf::failed_task::FailedReason;
36use ballista_core::serde::protobuf::job_status::Status;
37use ballista_core::serde::protobuf::{FailedJob, ShuffleWritePartition, job_status};
38use ballista_core::serde::protobuf::{
39 FailedTask, JobStatus, ResultLost, RunningJob, SuccessfulJob, TaskStatus,
40};
41use ballista_core::serde::protobuf::{RunningTask, task_status};
42use ballista_core::serde::scheduler::{
43 ExecutorMetadata, PartitionId, PartitionLocation, PartitionStats,
44};
45
46use crate::display::print_stage_metrics;
47use crate::planner::DistributedPlanner;
48use crate::scheduler_server::event::QueryStageSchedulerEvent;
49use crate::scheduler_server::timestamp_millis;
50use crate::state::execution_stage::RunningStage;
51pub(crate) use crate::state::execution_stage::{
52 ExecutionStage, ResolvedStage, StageOutput, TaskInfo, UnresolvedStage,
53};
54use crate::state::task_manager::UpdatedStages;
55
56pub type ExecutionGraphBox = Box<dyn ExecutionGraph + Send + Sync>;
58
59pub trait ExecutionGraph: Debug {
104 fn job_id(&self) -> &JobId;
106
107 fn job_name(&self) -> &str;
109
110 fn session_id(&self) -> &str;
112
113 fn session_config(&self) -> Arc<SessionConfig>;
115
116 fn status(&self) -> &JobStatus;
118
119 fn logical_plan(&self) -> Option<&str>;
121
122 fn physical_plan(&self) -> Arc<dyn ExecutionPlan>;
124
125 fn start_time(&self) -> u64;
127
128 fn end_time(&self) -> u64;
130
131 fn completed_stages(&self) -> usize;
133
134 fn is_successful(&self) -> bool;
136
137 fn revive(&mut self) -> bool;
140
141 fn update_task_status(
144 &mut self,
145 executor: &ExecutorMetadata,
146 task_statuses: Vec<TaskStatus>,
147 max_task_failures: usize,
148 max_stage_failures: usize,
149 ) -> Result<Vec<QueryStageSchedulerEvent>>;
150
151 fn running_stages(&self) -> Vec<usize>;
153
154 fn running_tasks(&self) -> Vec<RunningTaskInfo>;
156
157 fn available_tasks(&self) -> usize;
159
160 fn fetch_running_stage(
165 &mut self,
166 black_list: &[usize],
167 ) -> Option<(&mut RunningStage, &mut usize)>;
168
169 fn update_status(&mut self, status: JobStatus);
171
172 fn output_locations(&self) -> Vec<PartitionLocation>;
174
175 fn reset_stages_on_lost_executor(
182 &mut self,
183 executor_id: &str,
184 ) -> Result<(HashSet<usize>, Vec<RunningTaskInfo>)>;
185
186 fn resolve_stage(&mut self, stage_id: usize) -> Result<bool>;
191
192 fn succeed_stage(&mut self, stage_id: usize) -> bool;
196
197 fn fail_stage(&mut self, stage_id: usize, err_msg: String) -> bool;
201
202 fn rollback_running_stage(
205 &mut self,
206 stage_id: usize,
207 failure_reasons: HashSet<String>,
208 ) -> Result<Vec<RunningTaskInfo>>;
209
210 fn rollback_resolved_stage(&mut self, stage_id: usize) -> Result<bool>;
212
213 fn rerun_successful_stage(&mut self, stage_id: usize) -> bool;
218
219 fn fail_job(&mut self, error: String);
221
222 fn abort_running(&mut self, error: String) -> Vec<RunningTaskInfo> {
226 let running_tasks = self.running_tasks();
227 self.fail_job(error.clone());
228 for stage_id in self.running_stages() {
229 self.fail_stage(stage_id, error.clone());
230 }
231 running_tasks
232 }
233
234 fn succeed_job(&mut self) -> Result<()>;
239
240 fn stages(&self) -> &HashMap<usize, ExecutionStage>;
242
243 fn intermediate_stage_ids(&self) -> Vec<u32> {
246 self.stages()
247 .iter()
248 .filter(|(_, stage)| !stage.output_links().is_empty())
249 .map(|(stage_id, _)| *stage_id as u32)
250 .collect()
251 }
252
253 #[cfg(test)]
256 fn pop_next_task(&mut self, executor_id: &str) -> Result<Option<TaskDescription>>;
257
258 fn stage_count(&self) -> usize;
260
261 fn cloned(&self) -> ExecutionGraphBox;
263}
264
265#[derive(Clone)]
268pub struct StaticExecutionGraph {
269 #[allow(dead_code)] scheduler_id: Option<String>,
272 job_id: JobId,
274 job_name: String,
276 session_id: String,
278 status: JobStatus,
280 queued_at: u64,
282 start_time: u64,
284 end_time: u64,
286 stages: HashMap<usize, ExecutionStage>,
288 output_locations: Vec<PartitionLocation>,
290 task_id_gen: usize,
292 failed_stage_attempts: HashMap<usize, HashSet<usize>>,
295 session_config: Arc<SessionConfig>,
297 logical_plan: Option<String>,
299 physical_plan: Arc<dyn ExecutionPlan>,
301}
302
303#[derive(Clone, Debug)]
308pub struct RunningTaskInfo {
309 pub task_id: usize,
311 pub job_id: JobId,
313 pub stage_id: usize,
315 pub partition_id: usize,
317 pub executor_id: String,
319}
320
321impl StaticExecutionGraph {
322 #[allow(clippy::too_many_arguments)]
327 pub fn new(
328 scheduler_id: &str,
329 job_id: &JobId,
330 job_name: &str,
331 session_id: &str,
332 plan: Arc<dyn ExecutionPlan>,
333 queued_at: u64,
334 session_config: Arc<SessionConfig>,
335 planner: &mut dyn DistributedPlanner,
336 logical_plan: Option<String>,
337 ) -> Result<Self> {
338 let shuffle_stages =
339 planner.plan_query_stages(job_id, plan.clone(), session_config.options())?;
340
341 let builder = ExecutionStageBuilder::new(session_config.clone());
342 let stages = builder.build(shuffle_stages)?;
343
344 let started_at = timestamp_millis();
345
346 Ok(Self {
347 scheduler_id: Some(scheduler_id.to_string()),
348 job_id: job_id.to_owned(),
349 job_name: job_name.to_owned(),
350 session_id: session_id.to_string(),
351
352 status: JobStatus {
353 job_id: job_id.to_string(),
354 job_name: job_name.to_string(),
355 status: Some(Status::Running(RunningJob {
356 queued_at,
357 started_at,
358 scheduler: scheduler_id.to_string(),
359 })),
360 },
361 queued_at,
362 start_time: started_at,
363 end_time: 0,
364 stages,
365 output_locations: vec![],
366 task_id_gen: 0,
367 failed_stage_attempts: HashMap::new(),
368 session_config,
369 logical_plan,
370 physical_plan: plan,
371 })
372 }
373
374 #[cfg(test)]
375 fn next_task_id(&mut self) -> usize {
376 let new_tid = self.task_id_gen;
377 self.task_id_gen += 1;
378 new_tid
379 }
380
381 fn processing_stages_update(
383 &mut self,
384 updated_stages: UpdatedStages,
385 ) -> Result<Vec<QueryStageSchedulerEvent>> {
386 let job_id = self.job_id().to_owned();
387 let mut has_resolved = false;
388 let mut job_err_msg = "".to_owned();
389
390 for stage_id in updated_stages.resolved_stages {
391 self.resolve_stage(stage_id)?;
392 has_resolved = true;
393 }
394
395 for stage_id in updated_stages.successful_stages {
396 self.succeed_stage(stage_id);
397 }
398
399 for (stage_id, err_msg) in &updated_stages.failed_stages {
401 job_err_msg =
402 format!("Job failed due to stage {stage_id} failed: {err_msg}\n");
403 }
404
405 let mut events = vec![];
406 if updated_stages.failed_stages.is_empty() {
408 let mut running_tasks_to_cancel = vec![];
409 for (stage_id, failure_reasons) in updated_stages.rollback_running_stages {
410 let tasks = self.rollback_running_stage(stage_id, failure_reasons)?;
411 running_tasks_to_cancel.extend(tasks);
412 }
413
414 for stage_id in updated_stages.resubmit_successful_stages {
415 self.rerun_successful_stage(stage_id);
416 }
417
418 if !running_tasks_to_cancel.is_empty() {
419 events.push(QueryStageSchedulerEvent::CancelTasks(
420 running_tasks_to_cancel,
421 ));
422 }
423 }
424
425 if !updated_stages.failed_stages.is_empty() {
426 info!("Job {job_id} is failed");
427 self.fail_job(job_err_msg.clone());
428 events.push(QueryStageSchedulerEvent::JobRunningFailed {
429 job_id,
430 fail_message: job_err_msg,
431 queued_at: self.queued_at,
432 failed_at: timestamp_millis(),
433 });
434 } else if self.is_successful() {
435 debug!("Job {job_id} is success, finalizing job output ...");
437 self.succeed_job()?;
438 events.push(QueryStageSchedulerEvent::JobFinished {
439 job_id,
440 queued_at: self.queued_at,
441 completed_at: timestamp_millis(),
442 });
443 } else if has_resolved {
444 events.push(QueryStageSchedulerEvent::JobUpdated(job_id))
445 }
446 Ok(events)
447 }
448
449 fn update_stage_output_links(
451 &mut self,
452 stage_id: usize,
453 is_completed: bool,
454 locations: Vec<PartitionLocation>,
455 output_links: Vec<usize>,
456 ) -> Result<Vec<usize>> {
457 let mut resolved_stages = vec![];
458 let job_id = &self.job_id;
459 if output_links.is_empty() {
460 self.output_locations.extend(locations);
462 } else {
463 for link in output_links.iter() {
464 if let Some(linked_stage) = self.stages.get_mut(link) {
466 if let ExecutionStage::UnResolved(linked_unresolved_stage) =
467 linked_stage
468 {
469 linked_unresolved_stage
470 .add_input_partitions(stage_id, locations.clone())?;
471
472 if is_completed {
474 linked_unresolved_stage.complete_input(stage_id);
475 }
476
477 if linked_unresolved_stage.resolvable() {
479 resolved_stages.push(linked_unresolved_stage.stage_id);
480 }
481 } else {
482 return Err(BallistaError::Internal(format!(
483 "Error updating job {job_id}: The stage {link} as the output link of stage {stage_id} should be unresolved"
484 )));
485 }
486 } else {
487 return Err(BallistaError::Internal(format!(
488 "Error updating job {job_id}: Invalid output link {stage_id} for stage {link}"
489 )));
490 }
491 }
492 }
493 Ok(resolved_stages)
494 }
495
496 fn get_running_stage_id(&mut self, black_list: &[usize]) -> Option<usize> {
497 let mut running_stage_id = self.stages.iter().find_map(|(stage_id, stage)| {
498 if black_list.contains(stage_id) {
499 None
500 } else if let ExecutionStage::Running(stage) = stage {
501 if stage.available_tasks() > 0 {
502 Some(*stage_id)
503 } else {
504 None
505 }
506 } else {
507 None
508 }
509 });
510
511 if running_stage_id.is_none() {
514 if self.revive() {
515 running_stage_id = self.get_running_stage_id(black_list);
516 } else {
517 running_stage_id = None;
518 }
519 }
520
521 running_stage_id
522 }
523
524 fn reset_stages_internal(
525 &mut self,
526 executor_id: &str,
527 ) -> Result<(HashSet<usize>, Vec<RunningTaskInfo>)> {
528 let job_id = self.job_id.clone();
529 let mut resubmit_inputs: HashSet<usize> = HashSet::new();
531
532 let mut reset_running_stage = HashSet::new();
533 let mut rollback_resolved_stages = HashSet::new();
534 let mut rollback_running_stages = HashSet::new();
535 let mut resubmit_successful_stages = HashSet::new();
536
537 let mut empty_inputs: HashMap<usize, StageOutput> = HashMap::new();
538 self.stages
540 .iter_mut()
541 .for_each(|(stage_id, stage)| {
542 let stage_inputs = match stage {
543 ExecutionStage::UnResolved(stage) => {
544 &mut stage.inputs
545 }
546 ExecutionStage::Resolved(stage) => {
547 &mut stage.inputs
548 }
549 ExecutionStage::Running(stage) => {
550 let reset = stage.reset_tasks(executor_id);
551 if reset > 0 {
552 warn!(
553 "Reset {reset} tasks for running job/stage {job_id}/{stage_id} on lost Executor {executor_id}"
554 );
555 reset_running_stage.insert(*stage_id);
556 }
557 &mut stage.inputs
558 }
559 _ => &mut empty_inputs
560 };
561
562 let mut rollback_stage = false;
565 stage_inputs.iter_mut().for_each(|(input_stage_id, stage_output)| {
566 let mut match_found = false;
567 stage_output.partition_locations.iter_mut().for_each(
568 |(_partition, locs)| {
569 let before_len = locs.len();
570 locs.retain(|loc| loc.executor_meta.id != executor_id);
571 if locs.len() < before_len {
572 match_found = true;
573 }
574 },
575 );
576 if match_found {
577 stage_output.complete = false;
578 rollback_stage = true;
579 resubmit_inputs.insert(*input_stage_id);
580 }
581 });
582
583 if rollback_stage {
584 match stage {
585 ExecutionStage::Resolved(_) => {
586 rollback_resolved_stages.insert(*stage_id);
587 warn!(
588 "Roll back resolved job/stage {job_id}/{stage_id} and change ShuffleReaderExec back to UnresolvedShuffleExec");
589 }
590 ExecutionStage::Running(_) => {
591 rollback_running_stages.insert(*stage_id);
592 warn!(
593 "Roll back running job/stage {job_id}/{stage_id} and change ShuffleReaderExec back to UnresolvedShuffleExec");
594 }
595 _ => {}
596 }
597 }
598 });
599
600 if !resubmit_inputs.is_empty() {
602 self.stages
603 .iter_mut()
604 .filter(|(stage_id, _stage)| resubmit_inputs.contains(stage_id))
605 .filter_map(|(_stage_id, stage)| {
606 if let ExecutionStage::Successful(success) = stage {
607 Some(success)
608 } else {
609 None
610 }
611 })
612 .for_each(|stage| {
613 let reset = stage.reset_tasks(executor_id);
614 if reset > 0 {
615 resubmit_successful_stages.insert(stage.stage_id);
616 warn!(
617 "Reset {} tasks for successful job/stage {}/{} on lost Executor {}",
618 reset, job_id, stage.stage_id, executor_id
619 )
620 }
621 });
622 }
623
624 for stage_id in rollback_resolved_stages.iter() {
625 self.rollback_resolved_stage(*stage_id)?;
626 }
627
628 let mut all_running_tasks = vec![];
629 for stage_id in rollback_running_stages.iter() {
630 let tasks = self.rollback_running_stage(
631 *stage_id,
632 HashSet::from([executor_id.to_owned()]),
633 )?;
634 all_running_tasks.extend(tasks);
635 }
636
637 for stage_id in resubmit_successful_stages.iter() {
638 self.rerun_successful_stage(*stage_id);
639 }
640
641 let mut reset_stage = HashSet::new();
642 reset_stage.extend(reset_running_stage);
643 reset_stage.extend(rollback_resolved_stages);
644 reset_stage.extend(rollback_running_stages);
645 reset_stage.extend(resubmit_successful_stages);
646 Ok((reset_stage, all_running_tasks))
647 }
648
649 fn clear_stage_failure(&mut self, stage_id: usize) {
651 self.failed_stage_attempts.remove(&stage_id);
652 }
653}
654
655impl ExecutionGraph for StaticExecutionGraph {
656 fn cloned(&self) -> ExecutionGraphBox {
657 Box::new(self.clone())
658 }
659
660 fn job_id(&self) -> &JobId {
661 &self.job_id
662 }
663
664 fn job_name(&self) -> &str {
665 self.job_name.as_str()
666 }
667
668 fn session_id(&self) -> &str {
669 self.session_id.as_str()
670 }
671
672 fn session_config(&self) -> Arc<SessionConfig> {
673 self.session_config.clone()
674 }
675
676 fn status(&self) -> &JobStatus {
677 &self.status
678 }
679
680 fn logical_plan(&self) -> Option<&str> {
681 self.logical_plan.as_deref()
682 }
683
684 fn physical_plan(&self) -> Arc<dyn ExecutionPlan> {
685 self.physical_plan.clone()
686 }
687
688 fn start_time(&self) -> u64 {
689 self.start_time
690 }
691
692 fn end_time(&self) -> u64 {
693 self.end_time
694 }
695
696 fn completed_stages(&self) -> usize {
697 let mut completed_stages = 0;
698 for stage in self.stages.values() {
699 if let ExecutionStage::Successful(_) = stage {
700 completed_stages += 1;
701 }
702 }
703 completed_stages
704 }
705 fn is_successful(&self) -> bool {
707 self.stages
708 .values()
709 .all(|s| matches!(s, ExecutionStage::Successful(_)))
710 }
711
712 fn revive(&mut self) -> bool {
721 let running_stages = self
722 .stages
723 .values()
724 .filter_map(|stage| {
725 if let ExecutionStage::Resolved(resolved_stage) = stage {
726 Some(resolved_stage.to_running())
727 } else {
728 None
729 }
730 })
731 .collect::<Vec<_>>();
732
733 if running_stages.is_empty() {
734 false
735 } else {
736 for running_stage in running_stages {
737 self.stages.insert(
738 running_stage.stage_id,
739 ExecutionStage::Running(running_stage),
740 );
741 }
742 true
743 }
744 }
745
746 fn update_task_status(
749 &mut self,
750 executor: &ExecutorMetadata,
751 task_statuses: Vec<TaskStatus>,
752 max_task_failures: usize,
753 max_stage_failures: usize,
754 ) -> Result<Vec<QueryStageSchedulerEvent>> {
755 let job_id = self.job_id().to_owned();
756 let mut job_task_statuses: HashMap<usize, Vec<TaskStatus>> = HashMap::new();
758 for task_status in task_statuses {
759 let stage_id = task_status.stage_id as usize;
760 let stage_task_statuses = job_task_statuses.entry(stage_id).or_default();
761 stage_task_statuses.push(task_status);
762 }
763
764 self.revive();
767
768 let current_running_stages: HashSet<usize> =
769 HashSet::from_iter(self.running_stages());
770
771 let mut failed_stage_attempts: HashMap<usize, HashSet<usize>> = HashMap::new();
773 for (stage_id, attempts) in self.failed_stage_attempts.iter() {
774 failed_stage_attempts
775 .insert(*stage_id, HashSet::from_iter(attempts.iter().copied()));
776 }
777
778 let mut resolved_stages = HashSet::new();
779 let mut successful_stages = HashSet::new();
780 let mut failed_stages = HashMap::new();
781 let mut rollback_running_stages = HashMap::new();
782 let mut resubmit_successful_stages: HashMap<usize, HashSet<usize>> =
783 HashMap::new();
784 let mut reset_running_stages: HashMap<usize, HashSet<usize>> = HashMap::new();
785
786 for (stage_id, stage_task_statuses) in job_task_statuses {
787 if let Some(stage) = self.stages.get_mut(&stage_id) {
788 if let ExecutionStage::Running(running_stage) = stage {
789 let mut locations = vec![];
790 for task_status in stage_task_statuses.into_iter() {
791 let task_stage_attempt_num =
792 task_status.stage_attempt_num as usize;
793 if task_stage_attempt_num < running_stage.stage_attempt_num {
794 warn!(
795 "Ignore TaskStatus update with TID {} as it's from Stage {}.{} and there is a more recent stage attempt {}.{} running",
796 task_status.task_id,
797 stage_id,
798 task_stage_attempt_num,
799 stage_id,
800 running_stage.stage_attempt_num
801 );
802 continue;
803 }
804 let partition_id = task_status.partition_id as usize;
805 let task_identity = format!(
806 "TID {} {}/{}.{}/{}",
807 task_status.task_id,
808 job_id,
809 stage_id,
810 task_stage_attempt_num,
811 partition_id
812 );
813 if !running_stage.update_task_info(partition_id, &task_status) {
814 continue;
815 }
816
817 let TaskStatus {
818 status,
819 metrics: operator_metrics,
820 ..
821 } = task_status;
822
823 if let Some(task_status::Status::Failed(failed_task)) = status {
824 let failed_reason = failed_task.failed_reason;
825
826 match failed_reason {
827 Some(FailedReason::FetchPartitionError(
828 fetch_partiton_error,
829 )) => {
830 let failed_attempts = failed_stage_attempts
831 .entry(stage_id)
832 .or_default();
833 failed_attempts.insert(task_stage_attempt_num);
834 if failed_attempts.len() < max_stage_failures {
835 let map_stage_id =
836 fetch_partiton_error.map_stage_id as usize;
837 let map_partition_id = fetch_partiton_error
838 .map_partition_id
839 as usize;
840 let executor_id =
841 fetch_partiton_error.executor_id;
842
843 if !failed_stages.is_empty() {
844 let error_msg = format!(
845 "Stages was marked failed, ignore FetchPartitionError from task {task_identity}"
846 );
847 warn!("{error_msg}");
848 } else {
849 let removed_map_partitions = running_stage
856 .remove_input_partitions(
857 map_stage_id,
858 map_partition_id,
859 &executor_id,
860 )?;
861
862 let failure_reasons = rollback_running_stages
863 .entry(stage_id)
864 .or_insert_with(HashSet::new);
865 failure_reasons.insert(executor_id);
866
867 let missing_inputs =
868 resubmit_successful_stages
869 .entry(map_stage_id)
870 .or_default();
871 missing_inputs.extend(removed_map_partitions);
872 warn!(
873 "Need to resubmit the current running Stage {stage_id} and its map Stage {map_stage_id} due to FetchPartitionError from task {task_identity}"
874 )
875 }
876 } else {
877 let error_msg = format!(
878 "Stage {} has failed {} times, \
879 most recent failure reason: {:?}",
880 stage_id,
881 max_stage_failures,
882 failed_task.error
883 );
884 error!("{error_msg}");
885 failed_stages.insert(stage_id, error_msg);
886 }
887 }
888 Some(FailedReason::ExecutionError(_)) => {
889 failed_stages.insert(stage_id, failed_task.error);
890 }
891 Some(_) => {
892 if failed_task.retryable
893 && failed_task.count_to_failures
894 {
895 if running_stage.task_failure_number(partition_id)
896 < max_task_failures
897 {
898 running_stage.reset_task_info(partition_id);
901 } else {
902 let error_msg = format!(
903 "Task {} in Stage {} failed {} times, fail the stage, most recent failure reason: {:?}",
904 partition_id,
905 stage_id,
906 max_task_failures,
907 failed_task.error
908 );
909 error!("{error_msg}");
910 failed_stages.insert(stage_id, error_msg);
911 }
912 } else if failed_task.retryable {
913 running_stage.reset_task_info(partition_id);
916 }
917 }
918 None => {
919 let error_msg = format!(
920 "Task {partition_id} in Stage {stage_id} failed with unknown failure reasons, fail the stage"
921 );
922 error!("{error_msg}");
923 failed_stages.insert(stage_id, error_msg);
924 }
925 }
926 } else if let Some(task_status::Status::Successful(
927 successful_task,
928 )) = status
929 {
930 running_stage
932 .update_task_metrics(partition_id, operator_metrics)?;
933
934 locations.append(&mut partition_to_location(
935 &job_id,
936 partition_id,
937 stage_id,
938 executor,
939 successful_task.partitions,
940 ));
941 } else {
942 warn!(
943 "The task {task_identity}'s status is invalid for updating"
944 );
945 }
946 }
947
948 let is_final_successful = running_stage.is_successful()
949 && !reset_running_stages.contains_key(&stage_id);
950 if is_final_successful {
951 successful_stages.insert(stage_id);
952 if let Some(stage_metrics) = running_stage.stage_metrics.as_ref()
954 {
955 print_stage_metrics(
956 &job_id,
957 stage_id,
958 running_stage.plan.as_ref(),
959 stage_metrics,
960 );
961 }
962 }
963
964 let output_links = running_stage.output_links.clone();
965 resolved_stages.extend(
966 &mut self
967 .update_stage_output_links(
968 stage_id,
969 is_final_successful,
970 locations,
971 output_links,
972 )?
973 .into_iter(),
974 );
975 } else if let ExecutionStage::UnResolved(unsolved_stage) = stage {
976 for task_status in stage_task_statuses.into_iter() {
977 let task_stage_attempt_num =
978 task_status.stage_attempt_num as usize;
979 let partition_id = task_status.partition_id as usize;
980 let task_identity = format!(
981 "TID {} {}/{}.{}/{}",
982 task_status.task_id,
983 job_id,
984 stage_id,
985 task_stage_attempt_num,
986 partition_id
987 );
988 let mut should_ignore = true;
989 if let Some(task_status::Status::Failed(failed_task)) =
991 task_status.status
992 && unsolved_stage.stage_attempt_num - task_stage_attempt_num
993 == 1
994 {
995 let failed_reason = failed_task.failed_reason;
996 match failed_reason {
997 Some(FailedReason::ExecutionError(_)) => {
998 should_ignore = false;
999 failed_stages.insert(stage_id, failed_task.error);
1000 }
1001 Some(FailedReason::FetchPartitionError(
1002 fetch_partiton_error,
1003 )) if failed_stages.is_empty()
1004 && current_running_stages.contains(
1005 &(fetch_partiton_error.map_stage_id as usize),
1006 )
1007 && !unsolved_stage
1008 .last_attempt_failure_reasons
1009 .contains(&fetch_partiton_error.executor_id) =>
1010 {
1011 should_ignore = false;
1012 unsolved_stage
1013 .last_attempt_failure_reasons
1014 .insert(fetch_partiton_error.executor_id.clone());
1015 let map_stage_id =
1016 fetch_partiton_error.map_stage_id as usize;
1017 let map_partition_id =
1018 fetch_partiton_error.map_partition_id as usize;
1019 let executor_id = fetch_partiton_error.executor_id;
1020 let removed_map_partitions = unsolved_stage
1021 .remove_input_partitions(
1022 map_stage_id,
1023 map_partition_id,
1024 &executor_id,
1025 )?;
1026
1027 let missing_inputs = reset_running_stages
1028 .entry(map_stage_id)
1029 .or_default();
1030 missing_inputs.extend(removed_map_partitions);
1031 warn!(
1032 "Need to reset the current running Stage {map_stage_id} due to late come FetchPartitionError from its parent stage {stage_id} of task {task_identity}"
1033 );
1034
1035 if successful_stages.contains(&map_stage_id) {
1037 successful_stages.remove(&map_stage_id);
1038 }
1039 if resolved_stages.contains(&stage_id) {
1040 resolved_stages.remove(&stage_id);
1041 }
1042 }
1043 _ => {}
1044 }
1045 }
1046 if should_ignore {
1047 warn!(
1048 "Ignore TaskStatus update of task with TID {task_identity} as the Stage {job_id}/{stage_id} is in UnResolved status"
1049 );
1050 }
1051 }
1052 } else {
1053 warn!(
1054 "Stage {}/{} is not in running when updating the status of tasks {:?}",
1055 job_id,
1056 stage_id,
1057 stage_task_statuses
1058 .into_iter()
1059 .map(|task_status| task_status.partition_id)
1060 .collect::<Vec<_>>(),
1061 );
1062 }
1063 } else {
1064 return Err(BallistaError::Internal(format!(
1065 "Invalid stage ID {stage_id} for job {job_id}"
1066 )));
1067 }
1068 }
1069
1070 for (stage_id, attempts) in failed_stage_attempts.iter() {
1072 self.failed_stage_attempts
1073 .insert(*stage_id, HashSet::from_iter(attempts.iter().copied()));
1074 }
1075
1076 for (stage_id, missing_parts) in &resubmit_successful_stages {
1077 if let Some(stage) = self.stages.get_mut(stage_id) {
1078 if let ExecutionStage::Successful(success_stage) = stage {
1079 for partition in missing_parts {
1080 if *partition > success_stage.partitions {
1081 return Err(BallistaError::Internal(format!(
1082 "Invalid partition ID {} in map stage {}",
1083 *partition, stage_id
1084 )));
1085 }
1086 let task_info = &mut success_stage.task_infos[*partition];
1087 task_info.task_status = task_status::Status::Failed(FailedTask {
1089 error: "FetchPartitionError in parent stage".to_owned(),
1090 retryable: true,
1091 count_to_failures: false,
1092 failed_reason: Some(FailedReason::ResultLost(ResultLost {})),
1093 });
1094 }
1095 } else {
1096 warn!(
1097 "Stage {job_id}/{stage_id} is not in Successful state when try to resubmit this stage. "
1098 );
1099 }
1100 } else {
1101 return Err(BallistaError::Internal(format!(
1102 "Invalid stage ID {stage_id} for job {job_id}"
1103 )));
1104 }
1105 }
1106
1107 for (stage_id, missing_parts) in &reset_running_stages {
1108 if let Some(stage) = self.stages.get_mut(stage_id) {
1109 if let ExecutionStage::Running(running_stage) = stage {
1110 for partition in missing_parts {
1111 if *partition > running_stage.partitions {
1112 return Err(BallistaError::Internal(format!(
1113 "Invalid partition ID {} in map stage {}",
1114 *partition, stage_id
1115 )));
1116 }
1117 running_stage.reset_task_info(*partition);
1118 }
1119 } else {
1120 warn!(
1121 "Stage {job_id}/{stage_id} is not in Running state when try to reset the running task. "
1122 );
1123 }
1124 } else {
1125 return Err(BallistaError::Internal(format!(
1126 "Invalid stage ID {stage_id} for job {job_id}"
1127 )));
1128 }
1129 }
1130
1131 self.processing_stages_update(UpdatedStages {
1132 resolved_stages,
1133 successful_stages,
1134 failed_stages,
1135 rollback_running_stages,
1136 resubmit_successful_stages: resubmit_successful_stages
1137 .keys()
1138 .cloned()
1139 .collect(),
1140 })
1141 }
1142
1143 fn running_stages(&self) -> Vec<usize> {
1145 self.stages
1146 .iter()
1147 .filter_map(|(stage_id, stage)| {
1148 if let ExecutionStage::Running(_running) = stage {
1149 Some(*stage_id)
1150 } else {
1151 None
1152 }
1153 })
1154 .collect::<Vec<_>>()
1155 }
1156
1157 fn running_tasks(&self) -> Vec<RunningTaskInfo> {
1159 self.stages
1160 .values()
1161 .flat_map(|stage| {
1162 if let ExecutionStage::Running(stage) = stage {
1163 stage
1164 .running_tasks()
1165 .into_iter()
1166 .map(|(task_id, stage_id, partition_id, executor_id)| {
1167 RunningTaskInfo {
1168 task_id,
1169 job_id: self.job_id.clone(),
1170 stage_id,
1171 partition_id,
1172 executor_id,
1173 }
1174 })
1175 .collect::<Vec<RunningTaskInfo>>()
1176 } else {
1177 vec![]
1178 }
1179 })
1180 .collect::<Vec<RunningTaskInfo>>()
1181 }
1182
1183 fn available_tasks(&self) -> usize {
1185 self.stages
1186 .values()
1187 .map(|stage| {
1188 if let ExecutionStage::Running(stage) = stage {
1189 stage.available_tasks()
1190 } else {
1191 0
1192 }
1193 })
1194 .sum()
1195 }
1196
1197 fn fetch_running_stage(
1198 &mut self,
1199 black_list: &[usize],
1200 ) -> Option<(&mut RunningStage, &mut usize)> {
1201 if matches!(
1202 self.status,
1203 JobStatus {
1204 status: Some(job_status::Status::Failed(_)),
1205 ..
1206 }
1207 ) {
1208 debug!("Call fetch_runnable_stage on failed Job");
1209 return None;
1210 }
1211
1212 let running_stage_id = self.get_running_stage_id(black_list);
1213 if let Some(running_stage_id) = running_stage_id {
1214 if let Some(ExecutionStage::Running(running_stage)) =
1215 self.stages.get_mut(&running_stage_id)
1216 {
1217 Some((running_stage, &mut self.task_id_gen))
1218 } else {
1219 warn!("Fail to find running stage with id {running_stage_id}");
1220 None
1221 }
1222 } else {
1223 None
1224 }
1225 }
1226
1227 fn update_status(&mut self, status: JobStatus) {
1228 self.status = status;
1229 }
1230
1231 fn output_locations(&self) -> Vec<PartitionLocation> {
1232 self.output_locations.clone()
1233 }
1234
1235 fn reset_stages_on_lost_executor(
1242 &mut self,
1243 executor_id: &str,
1244 ) -> Result<(HashSet<usize>, Vec<RunningTaskInfo>)> {
1245 let mut reset = HashSet::new();
1246 let mut tasks_to_cancel = vec![];
1247 loop {
1248 let reset_stage = self.reset_stages_internal(executor_id)?;
1249 if !reset_stage.0.is_empty() {
1250 reset.extend(reset_stage.0.iter());
1251 tasks_to_cancel.extend(reset_stage.1)
1252 } else {
1253 return Ok((reset, tasks_to_cancel));
1254 }
1255 }
1256 }
1257
1258 fn resolve_stage(&mut self, stage_id: usize) -> Result<bool> {
1260 if let Some(ExecutionStage::UnResolved(stage)) = self.stages.remove(&stage_id) {
1261 self.stages.insert(
1262 stage_id,
1263 ExecutionStage::Resolved(
1264 stage.to_resolved(self.session_config.options())?,
1265 ),
1266 );
1267 Ok(true)
1268 } else {
1269 warn!(
1270 "Fail to find a unresolved stage {}/{} to resolve",
1271 self.job_id(),
1272 stage_id
1273 );
1274 Ok(false)
1275 }
1276 }
1277
1278 fn succeed_stage(&mut self, stage_id: usize) -> bool {
1280 if let Some(ExecutionStage::Running(stage)) = self.stages.remove(&stage_id) {
1281 self.stages
1282 .insert(stage_id, ExecutionStage::Successful(stage.to_successful()));
1283 self.clear_stage_failure(stage_id);
1284 true
1285 } else {
1286 warn!(
1287 "Fail to find a running stage {}/{} to make it success",
1288 self.job_id(),
1289 stage_id
1290 );
1291 false
1292 }
1293 }
1294
1295 fn fail_stage(&mut self, stage_id: usize, err_msg: String) -> bool {
1297 if let Some(ExecutionStage::Running(stage)) = self.stages.remove(&stage_id) {
1298 self.stages
1299 .insert(stage_id, ExecutionStage::Failed(stage.to_failed(err_msg)));
1300 true
1301 } else {
1302 info!(
1303 "Fail to find a running stage {}/{} to fail",
1304 self.job_id(),
1305 stage_id
1306 );
1307 false
1308 }
1309 }
1310
1311 fn rollback_running_stage(
1314 &mut self,
1315 stage_id: usize,
1316 failure_reasons: HashSet<String>,
1317 ) -> Result<Vec<RunningTaskInfo>> {
1318 if let Some(ExecutionStage::Running(stage)) = self.stages.remove(&stage_id) {
1319 let running_tasks = stage
1320 .running_tasks()
1321 .into_iter()
1322 .map(
1323 |(task_id, stage_id, partition_id, executor_id)| RunningTaskInfo {
1324 task_id,
1325 job_id: self.job_id.clone(),
1326 stage_id,
1327 partition_id,
1328 executor_id,
1329 },
1330 )
1331 .collect();
1332 self.stages.insert(
1333 stage_id,
1334 ExecutionStage::UnResolved(stage.to_unresolved(failure_reasons)?),
1335 );
1336 Ok(running_tasks)
1337 } else {
1338 warn!(
1339 "Fail to find a running stage {}/{} to rollback",
1340 self.job_id(),
1341 stage_id
1342 );
1343 Ok(vec![])
1344 }
1345 }
1346
1347 fn rollback_resolved_stage(&mut self, stage_id: usize) -> Result<bool> {
1349 if let Some(ExecutionStage::Resolved(stage)) = self.stages.remove(&stage_id) {
1350 self.stages
1351 .insert(stage_id, ExecutionStage::UnResolved(stage.to_unresolved()?));
1352 Ok(true)
1353 } else {
1354 warn!(
1355 "Fail to find a resolved stage {}/{} to rollback",
1356 self.job_id(),
1357 stage_id
1358 );
1359 Ok(false)
1360 }
1361 }
1362
1363 fn rerun_successful_stage(&mut self, stage_id: usize) -> bool {
1365 if let Some(ExecutionStage::Successful(stage)) = self.stages.remove(&stage_id) {
1366 self.stages
1367 .insert(stage_id, ExecutionStage::Running(stage.to_running()));
1368 true
1369 } else {
1370 warn!(
1371 "Fail to find a successful stage {}/{} to rerun",
1372 self.job_id(),
1373 stage_id
1374 );
1375 false
1376 }
1377 }
1378
1379 fn fail_job(&mut self, error: String) {
1381 self.end_time = timestamp_millis();
1382
1383 self.status = JobStatus {
1384 job_id: self.job_id.clone().into(),
1385 job_name: self.job_name.clone(),
1386 status: Some(Status::Failed(FailedJob {
1387 error,
1388 queued_at: self.queued_at,
1389 started_at: self.start_time,
1390 ended_at: self.end_time,
1391 })),
1392 };
1393 }
1394
1395 fn succeed_job(&mut self) -> Result<()> {
1397 if !self.is_successful() {
1398 return Err(BallistaError::Internal(format!(
1399 "Attempt to finalize an incomplete job {}",
1400 self.job_id()
1401 )));
1402 }
1403
1404 let partition_location = self
1405 .output_locations()
1406 .into_iter()
1407 .map(|l| l.try_into())
1408 .collect::<Result<Vec<_>>>()?;
1409
1410 self.end_time = timestamp_millis();
1411
1412 self.status = JobStatus {
1413 job_id: self.job_id.clone().into(),
1414 job_name: self.job_name.clone(),
1415 status: Some(job_status::Status::Successful(SuccessfulJob {
1416 partition_location,
1417
1418 queued_at: self.queued_at,
1419 started_at: self.start_time,
1420 ended_at: self.end_time,
1421 })),
1422 };
1423
1424 Ok(())
1425 }
1426
1427 fn stages(&self) -> &HashMap<usize, ExecutionStage> {
1428 &self.stages
1429 }
1430
1431 fn stage_count(&self) -> usize {
1432 self.stages.len()
1433 }
1434
1435 #[cfg(test)]
1442 fn pop_next_task(&mut self, executor_id: &str) -> Result<Option<TaskDescription>> {
1443 if matches!(
1444 self.status,
1445 JobStatus {
1446 status: Some(job_status::Status::Failed(_)),
1447 ..
1448 }
1449 ) {
1450 warn!("Call pop_next_task on failed Job");
1451 return Ok(None);
1452 }
1453
1454 let job_id = self.job_id.clone();
1455 let session_id = self.session_id.clone();
1456
1457 let find_candidate = self.stages.iter().any(|(_stage_id, stage)| {
1458 if let ExecutionStage::Running(stage) = stage {
1459 stage.available_tasks() > 0
1460 } else {
1461 false
1462 }
1463 });
1464 let next_task_id = if find_candidate {
1465 Some(self.next_task_id())
1466 } else {
1467 None
1468 };
1469
1470 let mut next_task = self.stages.iter_mut().find(|(_stage_id, stage)| {
1471 if let ExecutionStage::Running(stage) = stage {
1472 stage.available_tasks() > 0
1473 } else {
1474 false
1475 }
1476 }).map(|(stage_id, stage)| {
1477 if let ExecutionStage::Running(stage) = stage {
1478 let (partition_id, _) = stage
1479 .task_infos
1480 .iter()
1481 .enumerate()
1482 .find(|(_partition, info)| info.is_none())
1483 .ok_or_else(|| {
1484 BallistaError::Internal(format!("Error getting next task for job {job_id}: Stage {stage_id} is ready but has no pending tasks"))
1485 })?;
1486
1487 let partition = PartitionId {
1488 job_id,
1489 stage_id: *stage_id,
1490 partition_id,
1491 };
1492
1493 let task_id = next_task_id.unwrap();
1494 let task_attempt = stage.task_failure_numbers[partition_id];
1495 let task_info = TaskInfo {
1496 task_id,
1497 scheduled_time: SystemTime::now()
1498 .duration_since(UNIX_EPOCH)
1499 .unwrap()
1500 .as_millis(),
1501 launch_time: 0,
1503 start_exec_time: 0,
1504 end_exec_time: 0,
1505 finish_time: 0,
1506 task_status: task_status::Status::Running(RunningTask {
1507 executor_id: executor_id.to_owned()
1508 }),
1509 };
1510
1511 stage.task_infos[partition_id] = Some(task_info);
1513
1514 Ok(TaskDescription {
1515 session_id,
1516 partition,
1517 stage_attempt_num: stage.stage_attempt_num,
1518 task_id,
1519 task_attempt,
1520 plan: stage.plan.clone(),
1521 session_config: self.session_config.clone()
1522 })
1523 } else {
1524 Err(BallistaError::General(format!("Stage {stage_id} is not a running stage")))
1525 }
1526 }).transpose()?;
1527
1528 if next_task.is_none() {
1531 if self.revive() {
1532 next_task = self.pop_next_task(executor_id)?;
1533 } else {
1534 next_task = None;
1535 }
1536 }
1537
1538 Ok(next_task)
1539 }
1540}
1541
1542impl Debug for StaticExecutionGraph {
1543 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1544 let stages = self
1545 .stages
1546 .values()
1547 .map(|stage| format!("{stage:?}"))
1548 .collect::<Vec<String>>()
1549 .join("");
1550 write!(
1551 f,
1552 "ExecutionGraph[job_id={}, session_id={}, available_tasks={}, is_successful={}]\n{}",
1553 self.job_id,
1554 self.session_id,
1555 self.available_tasks(),
1556 self.is_successful(),
1557 stages
1558 )
1559 }
1560}
1561
1562pub fn create_task_info(executor_id: String, task_id: usize) -> TaskInfo {
1564 TaskInfo {
1565 task_id,
1566 scheduled_time: SystemTime::now()
1567 .duration_since(UNIX_EPOCH)
1568 .unwrap()
1569 .as_millis(),
1570 launch_time: 0,
1572 start_exec_time: 0,
1573 end_exec_time: 0,
1574 finish_time: 0,
1575 task_status: task_status::Status::Running(RunningTask { executor_id }),
1576 }
1577}
1578
1579pub(crate) struct ExecutionStageBuilder {
1585 current_stage_id: usize,
1587 stage_dependencies: HashMap<usize, Vec<usize>>,
1589 output_links: HashMap<usize, Vec<usize>>,
1591 session_config: Arc<SessionConfig>,
1592}
1593
1594impl ExecutionStageBuilder {
1595 pub fn new(session_config: Arc<SessionConfig>) -> Self {
1596 Self {
1597 current_stage_id: 0,
1598 stage_dependencies: HashMap::new(),
1599 output_links: HashMap::new(),
1600 session_config,
1601 }
1602 }
1603
1604 pub fn build(
1605 mut self,
1606 stages: Vec<Arc<dyn ShuffleWriter>>,
1607 ) -> Result<HashMap<usize, ExecutionStage>> {
1608 let mut execution_stages: HashMap<usize, ExecutionStage> = HashMap::new();
1609 for stage in &stages {
1611 accept(stage.as_ref(), &mut self)?;
1612 }
1613
1614 for stage in stages {
1616 let stage_id = stage.stage_id();
1617 let output_links = self.output_links.remove(&stage_id).unwrap_or_default();
1618
1619 let child_stages = self
1620 .stage_dependencies
1621 .remove(&stage_id)
1622 .unwrap_or_default();
1623
1624 let stage = if child_stages.is_empty() {
1625 ExecutionStage::Resolved(ResolvedStage::new(
1626 stage_id,
1627 0,
1628 stage,
1629 output_links,
1630 HashMap::new(),
1631 HashSet::new(),
1632 self.session_config.clone(),
1633 ))
1634 } else {
1635 ExecutionStage::UnResolved(UnresolvedStage::new(
1636 stage_id,
1637 stage,
1638 output_links,
1639 child_stages,
1640 self.session_config.clone(),
1641 ))
1642 };
1643 execution_stages.insert(stage_id, stage);
1644 }
1645
1646 Ok(execution_stages)
1647 }
1648}
1649
1650impl ExecutionPlanVisitor for ExecutionStageBuilder {
1651 type Error = BallistaError;
1652
1653 fn pre_visit(
1654 &mut self,
1655 plan: &dyn ExecutionPlan,
1656 ) -> std::result::Result<bool, Self::Error> {
1657 if let Some(shuffle_write) = plan.downcast_ref::<ShuffleWriterExec>() {
1659 self.current_stage_id = shuffle_write.stage_id();
1660 } else if let Some(shuffle_write) = plan.downcast_ref::<SortShuffleWriterExec>() {
1661 self.current_stage_id = shuffle_write.stage_id();
1662 } else if let Some(unresolved_shuffle) =
1663 plan.downcast_ref::<UnresolvedShuffleExec>()
1664 {
1665 if let Some(output_links) =
1666 self.output_links.get_mut(&unresolved_shuffle.stage_id)
1667 {
1668 if !output_links.contains(&self.current_stage_id) {
1669 output_links.push(self.current_stage_id);
1670 }
1671 } else {
1672 self.output_links
1673 .insert(unresolved_shuffle.stage_id, vec![self.current_stage_id]);
1674 }
1675
1676 if let Some(deps) = self.stage_dependencies.get_mut(&self.current_stage_id) {
1677 if !deps.contains(&unresolved_shuffle.stage_id) {
1678 deps.push(unresolved_shuffle.stage_id);
1679 }
1680 } else {
1681 self.stage_dependencies
1682 .insert(self.current_stage_id, vec![unresolved_shuffle.stage_id]);
1683 }
1684 }
1685 Ok(true)
1686 }
1687}
1688
1689#[derive(Clone)]
1694pub struct TaskDescription {
1695 pub session_id: String,
1697 pub partition: PartitionId,
1699 pub stage_attempt_num: usize,
1701 pub task_id: usize,
1703 pub task_attempt: usize,
1705 pub plan: Arc<dyn ExecutionPlan>,
1707 pub session_config: Arc<SessionConfig>,
1709}
1710
1711impl Debug for TaskDescription {
1712 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1713 let plan = DisplayableExecutionPlan::new(self.plan.as_ref()).indent(false);
1714 write!(
1715 f,
1716 "TaskDescription[session_id: {},job: {}, stage: {}.{}, partition: {} task_id {}, task attempt {}]\n{}",
1717 self.session_id,
1718 self.partition.job_id,
1719 self.partition.stage_id,
1720 self.stage_attempt_num,
1721 self.partition.partition_id,
1722 self.task_id,
1723 self.task_attempt,
1724 plan
1725 )
1726 }
1727}
1728
1729impl TaskDescription {
1730 pub fn get_output_partition_number(&self) -> usize {
1732 if let Some(shuffle_writer) = self.plan.downcast_ref::<ShuffleWriterExec>() {
1734 return shuffle_writer
1735 .shuffle_output_partitioning()
1736 .map(|partitioning| partitioning.partition_count())
1737 .unwrap_or(1);
1738 }
1739 if let Some(shuffle_writer) = self.plan.downcast_ref::<SortShuffleWriterExec>() {
1741 return shuffle_writer
1742 .shuffle_output_partitioning()
1743 .partition_count();
1744 }
1745 1
1747 }
1748}
1749
1750pub(crate) fn partition_to_location(
1751 job_id: &JobId,
1752 map_partition_id: usize,
1753 stage_id: usize,
1754 executor: &ExecutorMetadata,
1755 shuffles: Vec<ShuffleWritePartition>,
1756) -> Vec<PartitionLocation> {
1757 shuffles
1758 .into_iter()
1759 .map(|shuffle| PartitionLocation {
1760 map_partition_id,
1761 partition_id: PartitionId {
1762 job_id: job_id.to_owned(),
1763 stage_id,
1764 partition_id: shuffle.partition_id as usize,
1765 },
1766 executor_meta: executor.clone(),
1767 partition_stats: PartitionStats::new(
1768 Some(shuffle.num_rows),
1769 Some(shuffle.num_batches),
1770 Some(shuffle.num_bytes),
1771 ),
1772 file_id: shuffle.file_id,
1773 is_sort_shuffle: shuffle.is_sort_shuffle,
1774 })
1775 .collect()
1776}
1777
1778#[cfg(test)]
1779mod test {
1780 use std::collections::HashSet;
1781
1782 use crate::scheduler_server::event::QueryStageSchedulerEvent;
1783 use ballista_core::error::Result;
1784 use ballista_core::serde::protobuf::{
1785 self, ExecutionError, FailedTask, FetchPartitionError, IoError, JobStatus,
1786 TaskKilled, failed_task, job_status, task_status,
1787 };
1788
1789 use crate::state::execution_graph::ExecutionGraph;
1790 use crate::state::execution_stage::ExecutionStage;
1791 use crate::test_utils::{
1792 mock_completed_task, mock_executor, mock_failed_task,
1793 revive_graph_and_complete_next_stage,
1794 revive_graph_and_complete_next_stage_with_executor, test_aggregation_plan,
1795 test_coalesce_plan, test_join_plan, test_two_aggregations_plan,
1796 test_union_all_plan, test_union_plan,
1797 };
1798
1799 #[tokio::test]
1800 async fn test_intermediate_stage_ids() {
1801 let graph = test_aggregation_plan(4).await;
1805
1806 assert_eq!(graph.stages().len(), 2);
1807
1808 let final_count = graph
1810 .stages()
1811 .values()
1812 .filter(|s| s.output_links().is_empty())
1813 .count();
1814 assert_eq!(final_count, 1);
1815
1816 let intermediate = graph.intermediate_stage_ids();
1819 assert_eq!(intermediate.len(), 1);
1820 for id in &intermediate {
1821 let stage = graph.stages().get(&(*id as usize)).unwrap();
1822 assert!(!stage.output_links().is_empty());
1823 }
1824 }
1825
1826 #[tokio::test]
1827 async fn test_fail_job_sets_end_time_and_failed_metadata() -> Result<()> {
1828 let mut graph = test_aggregation_plan(4).await;
1829 let start = graph.start_time();
1830 assert_eq!(graph.end_time(), 0);
1831
1832 ExecutionGraph::fail_job(&mut graph, "test failure".to_string());
1833
1834 assert!(
1835 matches!(
1836 graph.status().status.as_ref(),
1837 Some(job_status::Status::Failed(f)) if f.error == "test failure"
1838 ),
1839 "expected FailedJob status after fail_job"
1840 );
1841 assert!(
1842 graph.end_time() >= start,
1843 "end_time ({}) should be set and >= start_time ({})",
1844 graph.end_time(),
1845 start
1846 );
1847
1848 if let Some(job_status::Status::Failed(failed)) = &graph.status().status {
1849 assert_eq!(failed.started_at, start);
1850 assert_eq!(failed.ended_at, graph.end_time());
1851 } else {
1852 panic!("missing FailedJob");
1853 }
1854
1855 Ok(())
1856 }
1857
1858 #[tokio::test]
1859 async fn test_drain_tasks() -> Result<()> {
1860 let mut agg_graph = test_aggregation_plan(4).await;
1861
1862 println!("Graph: {agg_graph:?}");
1863
1864 drain_tasks(&mut agg_graph)?;
1865
1866 assert!(
1867 agg_graph.is_successful(),
1868 "Failed to complete aggregation plan"
1869 );
1870
1871 let mut coalesce_graph = test_coalesce_plan(4).await;
1872
1873 drain_tasks(&mut coalesce_graph)?;
1874
1875 assert!(
1876 coalesce_graph.is_successful(),
1877 "Failed to complete coalesce plan"
1878 );
1879
1880 let mut join_graph = test_join_plan(4).await;
1881
1882 drain_tasks(&mut join_graph)?;
1883
1884 println!("{join_graph:?}");
1885
1886 assert!(join_graph.is_successful(), "Failed to complete join plan");
1887
1888 let mut union_all_graph = test_union_all_plan(4).await;
1889
1890 drain_tasks(&mut union_all_graph)?;
1891
1892 println!("{union_all_graph:?}");
1893
1894 assert!(
1895 union_all_graph.is_successful(),
1896 "Failed to complete union plan"
1897 );
1898
1899 let mut union_graph = test_union_plan(4).await;
1900
1901 drain_tasks(&mut union_graph)?;
1902
1903 println!("{union_graph:?}");
1904
1905 assert!(union_graph.is_successful(), "Failed to complete union plan");
1906
1907 Ok(())
1908 }
1909
1910 #[tokio::test]
1911 async fn test_finalize() -> Result<()> {
1912 let mut agg_graph = test_aggregation_plan(4).await;
1913
1914 drain_tasks(&mut agg_graph)?;
1915
1916 let status = agg_graph.status();
1917
1918 assert!(matches!(
1919 status,
1920 protobuf::JobStatus {
1921 status: Some(job_status::Status::Successful(_)),
1922 ..
1923 }
1924 ));
1925
1926 let outputs = agg_graph.output_locations();
1927
1928 for location in outputs {
1929 assert_eq!(location.executor_meta.host, "localhost2".to_owned());
1930 }
1931
1932 Ok(())
1933 }
1934
1935 #[tokio::test]
1936 async fn test_reset_completed_stage_executor_lost() -> Result<()> {
1937 let executor1 = mock_executor("executor-id1".to_string());
1938 let executor2 = mock_executor("executor-id2".to_string());
1939 let mut join_graph = test_join_plan(4).await;
1940
1941 assert_eq!(join_graph.stage_count(), 4);
1944 assert_eq!(join_graph.available_tasks(), 0);
1945
1946 join_graph.revive();
1948
1949 assert_eq!(join_graph.stage_count(), 4);
1950 assert_eq!(join_graph.available_tasks(), 4);
1951
1952 revive_graph_and_complete_next_stage_with_executor(&mut join_graph, &executor1)?;
1954
1955 revive_graph_and_complete_next_stage_with_executor(&mut join_graph, &executor2)?;
1957
1958 join_graph.revive();
1959 assert_eq!(join_graph.available_tasks(), 4);
1961
1962 if let Some(task) = join_graph.pop_next_task(&executor1.id)? {
1964 let task_status = mock_completed_task(task, &executor1.id);
1965 join_graph.update_task_status(&executor1, vec![task_status], 1, 1)?;
1966 }
1967 let _task = join_graph.pop_next_task(&executor1.id)?;
1969
1970 let reset = join_graph.reset_stages_on_lost_executor(&executor1.id)?;
1971
1972 assert_eq!(reset.0.len(), 2);
1974 assert_eq!(join_graph.available_tasks(), 2);
1975
1976 drain_tasks(&mut join_graph)?;
1977 assert!(join_graph.is_successful(), "Failed to complete join plan");
1978
1979 Ok(())
1980 }
1981
1982 #[tokio::test]
1983 async fn test_reset_resolved_stage_executor_lost() -> Result<()> {
1984 let executor1 = mock_executor("executor-id1".to_string());
1985 let executor2 = mock_executor("executor-id2".to_string());
1986 let mut join_graph = test_join_plan(4).await;
1987
1988 assert_eq!(join_graph.stage_count(), 4);
1989 assert_eq!(join_graph.available_tasks(), 0);
1990
1991 join_graph.revive();
1993
1994 assert_eq!(join_graph.stage_count(), 4);
1995 assert_eq!(join_graph.available_tasks(), 4);
1996
1997 assert_eq!(revive_graph_and_complete_next_stage(&mut join_graph)?, 2);
1999
2000 assert_eq!(
2002 revive_graph_and_complete_next_stage_with_executor(
2003 &mut join_graph,
2004 &executor2
2005 )?,
2006 2
2007 );
2008
2009 assert_eq!(join_graph.available_tasks(), 0);
2011
2012 let reset = join_graph.reset_stages_on_lost_executor(&executor1.id)?;
2013
2014 assert_eq!(reset.0.len(), 2);
2016 assert_eq!(join_graph.available_tasks(), 2);
2017
2018 drain_tasks(&mut join_graph)?;
2019 assert!(join_graph.is_successful(), "Failed to complete join plan");
2020
2021 Ok(())
2022 }
2023
2024 #[tokio::test]
2025 async fn test_task_update_after_reset_stage() -> Result<()> {
2026 let executor1 = mock_executor("executor-id1".to_string());
2027 let executor2 = mock_executor("executor-id2".to_string());
2028 let mut agg_graph = test_aggregation_plan(4).await;
2029
2030 assert_eq!(agg_graph.stage_count(), 2);
2031 assert_eq!(agg_graph.available_tasks(), 0);
2032
2033 agg_graph.revive();
2035
2036 assert_eq!(agg_graph.stage_count(), 2);
2037 assert_eq!(agg_graph.available_tasks(), 2);
2038
2039 revive_graph_and_complete_next_stage_with_executor(&mut agg_graph, &executor1)?;
2041
2042 if let Some(task) = agg_graph.pop_next_task(&executor2.id)? {
2044 let task_status = mock_completed_task(task, &executor2.id);
2045 agg_graph.update_task_status(&executor2, vec![task_status], 1, 1)?;
2046 }
2047
2048 if let Some(task) = agg_graph.pop_next_task(&executor1.id)? {
2050 let task_status = mock_completed_task(task, &executor1.id);
2051 agg_graph.update_task_status(&executor1, vec![task_status], 1, 1)?;
2052 }
2053
2054 let task = agg_graph.pop_next_task(&executor1.id)?;
2056
2057 assert_eq!(agg_graph.available_tasks(), 1);
2059
2060 let reset = agg_graph.reset_stages_on_lost_executor(&executor1.id)?;
2061
2062 let task_status = mock_completed_task(task.unwrap(), &executor1.id);
2064 agg_graph.update_task_status(&executor1, vec![task_status], 1, 1)?;
2065
2066 assert_eq!(reset.0.len(), 2);
2068 assert_eq!(agg_graph.available_tasks(), 2);
2069
2070 let reset = agg_graph.reset_stages_on_lost_executor(&executor1.id)?;
2072 assert_eq!(reset.0.len(), 0);
2073 assert_eq!(agg_graph.available_tasks(), 2);
2074
2075 drain_tasks(&mut agg_graph)?;
2076 assert!(agg_graph.is_successful(), "Failed to complete agg plan");
2077
2078 Ok(())
2079 }
2080
2081 #[tokio::test]
2082 async fn test_do_not_retry_killed_task() -> Result<()> {
2083 let executor = mock_executor("executor-id-123".to_string());
2084 let mut agg_graph = test_aggregation_plan(4).await;
2085 agg_graph.revive();
2087
2088 revive_graph_and_complete_next_stage(&mut agg_graph)?;
2090
2091 let task1 = agg_graph.pop_next_task(&executor.id)?.unwrap();
2093 let task_status1 = mock_completed_task(task1, &executor.id);
2094
2095 let task2 = agg_graph.pop_next_task(&executor.id)?.unwrap();
2097 let task_status2 = mock_failed_task(
2098 task2,
2099 FailedTask {
2100 error: "Killed".to_string(),
2101 retryable: false,
2102 count_to_failures: false,
2103 failed_reason: Some(failed_task::FailedReason::TaskKilled(TaskKilled {})),
2104 },
2105 );
2106
2107 agg_graph.update_task_status(
2108 &executor,
2109 vec![task_status1, task_status2],
2110 4,
2111 4,
2112 )?;
2113
2114 assert_eq!(agg_graph.available_tasks(), 2);
2115 drain_tasks(&mut agg_graph)?;
2116 assert_eq!(agg_graph.available_tasks(), 0);
2117
2118 assert!(
2119 !agg_graph.is_successful(),
2120 "Expected the agg graph can not complete"
2121 );
2122 Ok(())
2123 }
2124
2125 #[tokio::test]
2126 async fn test_max_task_failed_count() -> Result<()> {
2127 let executor = mock_executor("executor-id2".to_string());
2128 let mut agg_graph = test_aggregation_plan(2).await;
2129 agg_graph.revive();
2131
2132 revive_graph_and_complete_next_stage(&mut agg_graph)?;
2134
2135 let task1 = agg_graph.pop_next_task(&executor.id)?.unwrap();
2137 let task_status1 = mock_completed_task(task1, &executor.id);
2138
2139 let task2 = agg_graph.pop_next_task(&executor.id)?.unwrap();
2141 let task_status2 = mock_failed_task(
2142 task2.clone(),
2143 FailedTask {
2144 error: "IOError".to_string(),
2145 retryable: true,
2146 count_to_failures: true,
2147 failed_reason: Some(failed_task::FailedReason::IoError(IoError {})),
2148 },
2149 );
2150
2151 agg_graph.update_task_status(
2152 &executor,
2153 vec![task_status1, task_status2],
2154 4,
2155 4,
2156 )?;
2157
2158 assert_eq!(agg_graph.available_tasks(), 1);
2159
2160 let mut last_attempt = 0;
2161 for attempt in 1..5 {
2163 if let Some(task2_attempt) = agg_graph.pop_next_task(&executor.id)? {
2164 assert_eq!(
2165 task2_attempt.partition.partition_id,
2166 task2.partition.partition_id
2167 );
2168 assert_eq!(task2_attempt.task_attempt, attempt);
2169 last_attempt = task2_attempt.task_attempt;
2170 let task_status = mock_failed_task(
2171 task2_attempt.clone(),
2172 FailedTask {
2173 error: "IOError".to_string(),
2174 retryable: true,
2175 count_to_failures: true,
2176 failed_reason: Some(failed_task::FailedReason::IoError(
2177 IoError {},
2178 )),
2179 },
2180 );
2181 agg_graph.update_task_status(&executor, vec![task_status], 4, 4)?;
2182 }
2183 }
2184
2185 assert!(
2186 matches!(
2187 agg_graph.status(),
2188 JobStatus {
2189 status: Some(job_status::Status::Failed(_)),
2190 ..
2191 }
2192 ),
2193 "Expected job status to be Failed"
2194 );
2195
2196 assert_eq!(last_attempt, 3);
2197
2198 let failure_reason = format!("{:?}", agg_graph.status);
2199 assert!(failure_reason.contains(
2200 "Task 1 in Stage 2 failed 4 times, fail the stage, most recent failure reason"
2201 ));
2202 assert!(failure_reason.contains("IOError"));
2203 assert!(!agg_graph.is_successful());
2204
2205 Ok(())
2206 }
2207
2208 #[tokio::test]
2212 async fn test_abort_running_cancels_stages_and_returns_inflight_tasks() -> Result<()>
2213 {
2214 let executor = mock_executor("executor-id1".to_string());
2215 let mut graph = test_join_plan(2).await;
2216
2217 graph.revive();
2219 assert!(
2220 graph.running_stages().len() >= 2,
2221 "expected two concurrently running leaf stages, found {:?}",
2222 graph.running_stages()
2223 );
2224
2225 let _task = graph.pop_next_task(&executor.id)?.unwrap();
2227
2228 let cancelled = graph.abort_running("job aborted".to_string());
2230
2231 assert!(
2232 !cancelled.is_empty(),
2233 "abort_running must return the in-flight tasks to cancel"
2234 );
2235 assert!(
2236 graph.running_stages().is_empty(),
2237 "every running stage must be cancelled, found {:?}",
2238 graph.running_stages()
2239 );
2240 assert!(
2241 matches!(
2242 graph.status(),
2243 JobStatus {
2244 status: Some(job_status::Status::Failed(_)),
2245 ..
2246 }
2247 ),
2248 "the job must be Failed after abort"
2249 );
2250
2251 let has_killed_task = graph.stages.values().any(|stage| match stage {
2253 ExecutionStage::Failed(failed) => {
2254 failed.task_infos.iter().flatten().any(|info| {
2255 matches!(
2256 &info.task_status,
2257 task_status::Status::Failed(FailedTask {
2258 failed_reason: Some(failed_task::FailedReason::TaskKilled(_)),
2259 ..
2260 })
2261 )
2262 })
2263 }
2264 _ => false,
2265 });
2266 assert!(
2267 has_killed_task,
2268 "in-flight tasks must be recorded as Failed(TaskKilled) after abort"
2269 );
2270
2271 Ok(())
2272 }
2273
2274 #[tokio::test]
2275 async fn test_long_delayed_failed_task_after_executor_lost() -> Result<()> {
2276 let executor1 = mock_executor("executor-id1".to_string());
2277 let executor2 = mock_executor("executor-id2".to_string());
2278 let mut agg_graph = test_aggregation_plan(4).await;
2279 agg_graph.revive();
2281
2282 revive_graph_and_complete_next_stage_with_executor(&mut agg_graph, &executor1)?;
2284
2285 if let Some(task) = agg_graph.pop_next_task(&executor2.id)? {
2287 let task_status = mock_completed_task(task, &executor2.id);
2288 agg_graph.update_task_status(&executor2, vec![task_status], 1, 1)?;
2289 }
2290
2291 if let Some(task) = agg_graph.pop_next_task(&executor1.id)? {
2293 let task_status = mock_completed_task(task, &executor1.id);
2294 agg_graph.update_task_status(&executor1, vec![task_status], 1, 1)?;
2295 }
2296
2297 let task = agg_graph.pop_next_task(&executor2.id)?;
2299
2300 assert_eq!(agg_graph.available_tasks(), 1);
2302
2303 let reset = agg_graph.reset_stages_on_lost_executor(&executor1.id)?;
2305
2306 assert_eq!(reset.0.len(), 2);
2308 assert_eq!(agg_graph.available_tasks(), 2);
2309
2310 revive_graph_and_complete_next_stage_with_executor(&mut agg_graph, &executor1)?;
2312
2313 agg_graph.revive();
2315 assert_eq!(agg_graph.available_tasks(), 4);
2316
2317 let task_status = mock_failed_task(
2319 task.unwrap(),
2320 FailedTask {
2321 error: "ExecutionError".to_string(),
2322 retryable: false,
2323 count_to_failures: false,
2324 failed_reason: Some(failed_task::FailedReason::ExecutionError(
2325 ExecutionError {},
2326 )),
2327 },
2328 );
2329
2330 let query_stage_events =
2332 agg_graph.update_task_status(&executor1, vec![task_status], 4, 4)?;
2333 assert!(query_stage_events.is_empty());
2334
2335 drain_tasks(&mut agg_graph)?;
2336 assert!(agg_graph.is_successful(), "Failed to complete agg plan");
2337
2338 Ok(())
2339 }
2340
2341 #[tokio::test]
2342 async fn test_normal_fetch_failure() -> Result<()> {
2343 let executor1 = mock_executor("executor-id1".to_string());
2344 let executor2 = mock_executor("executor-id2".to_string());
2345 let mut agg_graph = test_aggregation_plan(4).await;
2346 agg_graph.revive();
2348
2349 revive_graph_and_complete_next_stage(&mut agg_graph)?;
2351
2352 let task1 = agg_graph.pop_next_task(&executor2.id)?.unwrap();
2354 let task_status1 = mock_completed_task(task1, &executor2.id);
2355
2356 let task2 = agg_graph.pop_next_task(&executor2.id)?.unwrap();
2358 let task_status2 = mock_failed_task(
2359 task2,
2360 FailedTask {
2361 error: "FetchPartitionError".to_string(),
2362 retryable: false,
2363 count_to_failures: false,
2364 failed_reason: Some(failed_task::FailedReason::FetchPartitionError(
2365 FetchPartitionError {
2366 executor_id: executor1.id.clone(),
2367 map_stage_id: 1,
2368 map_partition_id: 0,
2369 },
2370 )),
2371 },
2372 );
2373
2374 let mut running_task_count = 0;
2375 while let Some(_task) = agg_graph.pop_next_task(&executor2.id)? {
2376 running_task_count += 1;
2377 }
2378 assert_eq!(running_task_count, 2);
2379
2380 let stage_events = agg_graph.update_task_status(
2381 &executor2,
2382 vec![task_status1, task_status2],
2383 4,
2384 4,
2385 )?;
2386
2387 assert_eq!(stage_events.len(), 1);
2388 assert!(matches!(
2389 stage_events[0],
2390 QueryStageSchedulerEvent::CancelTasks(_)
2391 ));
2392
2393 let running_stage = agg_graph.running_stages();
2395 assert_eq!(running_stage.len(), 1);
2396 assert_eq!(running_stage[0], 1);
2397 assert_eq!(agg_graph.available_tasks(), 2);
2398
2399 drain_tasks(&mut agg_graph)?;
2400 assert!(agg_graph.is_successful(), "Failed to complete agg plan");
2401 Ok(())
2402 }
2403
2404 #[tokio::test]
2405 async fn test_many_fetch_failures_in_one_stage() -> Result<()> {
2406 let executor1 = mock_executor("executor-id1".to_string());
2407 let executor2 = mock_executor("executor-id2".to_string());
2408 let executor3 = mock_executor("executor-id3".to_string());
2409 let mut agg_graph = test_two_aggregations_plan(8).await;
2410
2411 agg_graph.revive();
2412 assert_eq!(agg_graph.stage_count(), 3);
2413
2414 revive_graph_and_complete_next_stage(&mut agg_graph)?;
2416
2417 for _i in 0..5 {
2419 if let Some(task) = agg_graph.pop_next_task(&executor2.id)? {
2420 let task_status = mock_completed_task(task, &executor2.id);
2421 agg_graph.update_task_status(&executor2, vec![task_status], 4, 4)?;
2422 }
2423 }
2424 assert_eq!(agg_graph.available_tasks(), 3);
2425 for _i in 0..3 {
2426 if let Some(task) = agg_graph.pop_next_task(&executor1.id)? {
2427 let task_status = mock_completed_task(task, &executor1.id);
2428 agg_graph.update_task_status(&executor1, vec![task_status], 4, 4)?;
2429 }
2430 }
2431
2432 let mut many_fetch_failure_status = vec![];
2434 for part in 2..8 {
2435 if let Some(task) = agg_graph.pop_next_task(&executor3.id)? {
2436 let task_status = mock_failed_task(
2437 task,
2438 FailedTask {
2439 error: "FetchPartitionError".to_string(),
2440 retryable: false,
2441 count_to_failures: false,
2442 failed_reason: Some(
2443 failed_task::FailedReason::FetchPartitionError(
2444 FetchPartitionError {
2445 executor_id: executor2.id.clone(),
2446 map_stage_id: 2,
2447 map_partition_id: part,
2448 },
2449 ),
2450 ),
2451 },
2452 );
2453 many_fetch_failure_status.push(task_status);
2454 }
2455 }
2456 assert_eq!(many_fetch_failure_status.len(), 6);
2457 agg_graph.update_task_status(&executor3, many_fetch_failure_status, 4, 4)?;
2458
2459 let running_stage = agg_graph.running_stages();
2461 assert_eq!(running_stage.len(), 1);
2462 assert_eq!(running_stage[0], 2);
2463 assert_eq!(agg_graph.available_tasks(), 5);
2464
2465 drain_tasks(&mut agg_graph)?;
2466 assert!(agg_graph.is_successful(), "Failed to complete agg plan");
2467 Ok(())
2468 }
2469
2470 #[tokio::test]
2471 async fn test_many_consecutive_stage_fetch_failures() -> Result<()> {
2472 let executor1 = mock_executor("executor-id1".to_string());
2473 let executor2 = mock_executor("executor-id2".to_string());
2474 let mut agg_graph = test_aggregation_plan(4).await;
2475 agg_graph.revive();
2477
2478 for attempt in 0..6 {
2479 revive_graph_and_complete_next_stage(&mut agg_graph)?;
2480
2481 if let Some(task1) = agg_graph.pop_next_task(&executor2.id)? {
2483 let task_status1 = mock_failed_task(
2484 task1.clone(),
2485 FailedTask {
2486 error: "FetchPartitionError".to_string(),
2487 retryable: false,
2488 count_to_failures: false,
2489 failed_reason: Some(
2490 failed_task::FailedReason::FetchPartitionError(
2491 FetchPartitionError {
2492 executor_id: executor1.id.clone(),
2493 map_stage_id: 1,
2494 map_partition_id: 0,
2495 },
2496 ),
2497 ),
2498 },
2499 );
2500
2501 let stage_events =
2502 agg_graph.update_task_status(&executor2, vec![task_status1], 4, 4)?;
2503
2504 if attempt < 3 {
2505 assert_eq!(stage_events.len(), 0);
2507 let running_stage = agg_graph.running_stages();
2509 assert_eq!(running_stage.len(), 1);
2510 assert_eq!(running_stage[0], 1);
2511 assert_eq!(agg_graph.available_tasks(), 2);
2512 } else {
2513 assert_eq!(stage_events.len(), 1);
2515 assert!(matches!(
2516 stage_events[0],
2517 QueryStageSchedulerEvent::JobRunningFailed { .. }
2518 ));
2519 let running_stage = agg_graph.running_stages();
2521 assert_eq!(running_stage.len(), 1);
2522 assert_eq!(running_stage[0], 2);
2523 }
2524 }
2525 }
2526
2527 drain_tasks(&mut agg_graph)?;
2528 assert!(!agg_graph.is_successful(), "Expect to fail the agg plan");
2529
2530 let failure_reason = format!("{:?}", agg_graph.status());
2531 assert!(failure_reason.contains("Job failed due to stage 2 failed: Stage 2 has failed 4 times, most recent failure reason"));
2532 assert!(failure_reason.contains("FetchPartitionError"));
2533
2534 Ok(())
2535 }
2536
2537 #[tokio::test]
2538 async fn test_long_delayed_fetch_failures() -> Result<()> {
2539 let executor1 = mock_executor("executor-id1".to_string());
2540 let executor2 = mock_executor("executor-id2".to_string());
2541 let executor3 = mock_executor("executor-id3".to_string());
2542 let mut agg_graph = test_two_aggregations_plan(8).await;
2543
2544 agg_graph.revive();
2545 assert_eq!(agg_graph.stage_count(), 3);
2546
2547 revive_graph_and_complete_next_stage(&mut agg_graph)?;
2549
2550 for _i in 0..5 {
2552 if let Some(task) = agg_graph.pop_next_task(&executor2.id)? {
2553 let task_status = mock_completed_task(task, &executor2.id);
2554 agg_graph.update_task_status(&executor2, vec![task_status], 4, 4)?;
2555 }
2556 }
2557 assert_eq!(agg_graph.available_tasks(), 3);
2558
2559 for _i in 0..2 {
2560 if let Some(task) = agg_graph.pop_next_task(&executor1.id)? {
2561 let task_status = mock_completed_task(task, &executor1.id);
2562 agg_graph.update_task_status(&executor1, vec![task_status], 4, 4)?;
2563 }
2564 }
2565
2566 if let Some(task) = agg_graph.pop_next_task(&executor3.id)? {
2567 let task_status = mock_completed_task(task, &executor3.id);
2568 agg_graph.update_task_status(&executor3, vec![task_status], 4, 4)?;
2569 }
2570 assert_eq!(agg_graph.available_tasks(), 0);
2571
2572 let task_1 = agg_graph.pop_next_task(&executor3.id)?.unwrap();
2575 let task_2 = agg_graph.pop_next_task(&executor3.id)?.unwrap();
2577 let task_3 = agg_graph.pop_next_task(&executor3.id)?.unwrap();
2579 let task_4 = agg_graph.pop_next_task(&executor3.id)?.unwrap();
2581 let task_5 = agg_graph.pop_next_task(&executor3.id)?.unwrap();
2583
2584 let task_status_1 = mock_failed_task(
2586 task_1,
2587 FailedTask {
2588 error: "FetchPartitionError".to_string(),
2589 retryable: false,
2590 count_to_failures: false,
2591 failed_reason: Some(failed_task::FailedReason::FetchPartitionError(
2592 FetchPartitionError {
2593 executor_id: executor2.id.clone(),
2594 map_stage_id: 2,
2595 map_partition_id: 0,
2596 },
2597 )),
2598 },
2599 );
2600 agg_graph.update_task_status(&executor3, vec![task_status_1], 4, 4)?;
2601
2602 let running_stage = agg_graph.running_stages();
2604 assert_eq!(running_stage.len(), 1);
2605 assert_eq!(running_stage[0], 2);
2606 assert_eq!(agg_graph.available_tasks(), 5);
2607
2608 let task_status_2 = mock_failed_task(
2610 task_2,
2611 FailedTask {
2612 error: "FetchPartitionError".to_string(),
2613 retryable: false,
2614 count_to_failures: false,
2615 failed_reason: Some(failed_task::FailedReason::FetchPartitionError(
2616 FetchPartitionError {
2617 executor_id: executor2.id.clone(),
2618 map_stage_id: 2,
2619 map_partition_id: 1,
2620 },
2621 )),
2622 },
2623 );
2624 agg_graph.update_task_status(&executor3, vec![task_status_2], 4, 4)?;
2626 let running_stage = agg_graph.running_stages();
2627 assert_eq!(running_stage.len(), 1);
2628 assert_eq!(running_stage[0], 2);
2629 assert_eq!(agg_graph.available_tasks(), 5);
2630
2631 let task_status_3 = mock_failed_task(
2633 task_3,
2634 FailedTask {
2635 error: "FetchPartitionError".to_string(),
2636 retryable: false,
2637 count_to_failures: false,
2638 failed_reason: Some(failed_task::FailedReason::FetchPartitionError(
2639 FetchPartitionError {
2640 executor_id: executor1.id.clone(),
2641 map_stage_id: 2,
2642 map_partition_id: 1,
2643 },
2644 )),
2645 },
2646 );
2647 agg_graph.update_task_status(&executor3, vec![task_status_3], 4, 4)?;
2649 assert_eq!(running_stage.len(), 1);
2651 assert_eq!(running_stage[0], 2);
2652 assert_eq!(agg_graph.available_tasks(), 7);
2653
2654 for _i in 0..4 {
2656 if let Some(task) = agg_graph.pop_next_task(&executor1.id)? {
2657 let task_status = mock_completed_task(task, &executor1.id);
2658 agg_graph.update_task_status(&executor1, vec![task_status], 4, 4)?;
2659 }
2660 }
2661 assert_eq!(running_stage.len(), 1);
2662 assert_eq!(running_stage[0], 2);
2663 assert_eq!(agg_graph.available_tasks(), 3);
2664
2665 let task_status_4 = mock_failed_task(
2667 task_4,
2668 FailedTask {
2669 error: "FetchPartitionError".to_string(),
2670 retryable: false,
2671 count_to_failures: false,
2672 failed_reason: Some(failed_task::FailedReason::FetchPartitionError(
2673 FetchPartitionError {
2674 executor_id: executor1.id.clone(),
2675 map_stage_id: 2,
2676 map_partition_id: 1,
2677 },
2678 )),
2679 },
2680 );
2681 agg_graph.update_task_status(&executor3, vec![task_status_4], 4, 4)?;
2683 let running_stage = agg_graph.running_stages();
2684 assert_eq!(running_stage.len(), 1);
2685 assert_eq!(running_stage[0], 2);
2686 assert_eq!(agg_graph.available_tasks(), 3);
2687
2688 for _i in 0..3 {
2690 if let Some(task) = agg_graph.pop_next_task(&executor1.id)? {
2691 let task_status = mock_completed_task(task, &executor1.id);
2692 agg_graph.update_task_status(&executor1, vec![task_status], 4, 4)?;
2693 }
2694 }
2695 assert_eq!(agg_graph.available_tasks(), 0);
2696
2697 let task_status_5 = mock_failed_task(
2701 task_5,
2702 FailedTask {
2703 error: "FetchPartitionError".to_string(),
2704 retryable: false,
2705 count_to_failures: false,
2706 failed_reason: Some(failed_task::FailedReason::FetchPartitionError(
2707 FetchPartitionError {
2708 executor_id: executor3.id.clone(),
2709 map_stage_id: 2,
2710 map_partition_id: 1,
2711 },
2712 )),
2713 },
2714 );
2715 agg_graph.update_task_status(&executor3, vec![task_status_5], 4, 4)?;
2716 let running_stage = agg_graph.running_stages();
2718 assert_eq!(running_stage.len(), 1);
2719 assert_eq!(running_stage[0], 3);
2720 assert_eq!(agg_graph.available_tasks(), 8);
2721
2722 assert_eq!(agg_graph.failed_stage_attempts.len(), 1);
2724 assert_eq!(
2725 agg_graph.failed_stage_attempts.get(&3).cloned(),
2726 Some(HashSet::from([0]))
2727 );
2728 drain_tasks(&mut agg_graph)?;
2729 assert!(agg_graph.is_successful(), "Failed to complete agg plan");
2730 assert_eq!(agg_graph.failed_stage_attempts.len(), 0);
2732
2733 Ok(())
2734 }
2735
2736 #[tokio::test]
2737 async fn test_long_delayed_fetch_failures_race_condition() -> Result<()> {
2740 let executor1 = mock_executor("executor-id1".to_string());
2741 let executor2 = mock_executor("executor-id2".to_string());
2742 let executor3 = mock_executor("executor-id3".to_string());
2743 let mut agg_graph = test_two_aggregations_plan(8).await;
2744
2745 agg_graph.revive();
2746 assert_eq!(agg_graph.stage_count(), 3);
2747
2748 revive_graph_and_complete_next_stage(&mut agg_graph)?;
2750
2751 for _i in 0..5 {
2753 if let Some(task) = agg_graph.pop_next_task(&executor2.id)? {
2754 let task_status = mock_completed_task(task, &executor2.id);
2755 agg_graph.update_task_status(&executor2, vec![task_status], 4, 4)?;
2756 }
2757 }
2758 assert_eq!(agg_graph.available_tasks(), 3);
2759
2760 for _i in 0..3 {
2761 if let Some(task) = agg_graph.pop_next_task(&executor1.id)? {
2762 let task_status = mock_completed_task(task, &executor1.id);
2763 agg_graph.update_task_status(&executor1, vec![task_status], 4, 4)?;
2764 }
2765 }
2766 assert_eq!(agg_graph.available_tasks(), 0);
2767
2768 let task_1 = agg_graph.pop_next_task(&executor3.id)?.unwrap();
2771 let task_2 = agg_graph.pop_next_task(&executor3.id)?.unwrap();
2773
2774 let task_status_1 = mock_failed_task(
2776 task_1,
2777 FailedTask {
2778 error: "FetchPartitionError".to_string(),
2779 retryable: false,
2780 count_to_failures: false,
2781 failed_reason: Some(failed_task::FailedReason::FetchPartitionError(
2782 FetchPartitionError {
2783 executor_id: executor2.id.clone(),
2784 map_stage_id: 2,
2785 map_partition_id: 0,
2786 },
2787 )),
2788 },
2789 );
2790 agg_graph.update_task_status(&executor3, vec![task_status_1], 4, 4)?;
2791
2792 let running_stage = agg_graph.running_stages();
2794 assert_eq!(running_stage.len(), 1);
2795 assert_eq!(running_stage[0], 2);
2796 assert_eq!(agg_graph.available_tasks(), 5);
2797
2798 let mut task_status_vec = vec![];
2800 for _i in 0..5 {
2801 if let Some(task) = agg_graph.pop_next_task(&executor1.id)? {
2802 task_status_vec.push(mock_completed_task(task, &executor1.id))
2803 }
2804 }
2805
2806 let task_status_2 = mock_failed_task(
2808 task_2,
2809 FailedTask {
2810 error: "FetchPartitionError".to_string(),
2811 retryable: false,
2812 count_to_failures: false,
2813 failed_reason: Some(failed_task::FailedReason::FetchPartitionError(
2814 FetchPartitionError {
2815 executor_id: executor1.id.clone(),
2816 map_stage_id: 2,
2817 map_partition_id: 1,
2818 },
2819 )),
2820 },
2821 );
2822 task_status_vec.push(task_status_2);
2823
2824 agg_graph.update_task_status(&executor3, task_status_vec, 4, 4)?;
2827 assert_eq!(running_stage.len(), 1);
2829 assert_eq!(running_stage[0], 2);
2830 assert_eq!(agg_graph.available_tasks(), 3);
2831
2832 drain_tasks(&mut agg_graph)?;
2833 assert!(agg_graph.is_successful(), "Failed to complete agg plan");
2834
2835 Ok(())
2836 }
2837
2838 #[tokio::test]
2839 async fn test_fetch_failures_in_different_stages() -> Result<()> {
2840 let executor1 = mock_executor("executor-id1".to_string());
2841 let executor2 = mock_executor("executor-id2".to_string());
2842 let executor3 = mock_executor("executor-id3".to_string());
2843 let mut agg_graph = test_two_aggregations_plan(8).await;
2844
2845 agg_graph.revive();
2846 assert_eq!(agg_graph.stage_count(), 3);
2847
2848 revive_graph_and_complete_next_stage(&mut agg_graph)?;
2850
2851 for _i in 0..5 {
2853 if let Some(task) = agg_graph.pop_next_task(&executor2.id)? {
2854 let task_status = mock_completed_task(task, &executor2.id);
2855 agg_graph.update_task_status(&executor2, vec![task_status], 4, 4)?;
2856 }
2857 }
2858 assert_eq!(agg_graph.available_tasks(), 3);
2859 for _i in 0..3 {
2860 if let Some(task) = agg_graph.pop_next_task(&executor1.id)? {
2861 let task_status = mock_completed_task(task, &executor1.id);
2862 agg_graph.update_task_status(&executor1, vec![task_status], 4, 4)?;
2863 }
2864 }
2865 assert_eq!(agg_graph.available_tasks(), 0);
2866
2867 if let Some(task1) = agg_graph.pop_next_task(&executor3.id)? {
2870 let task_status1 = mock_failed_task(
2871 task1,
2872 FailedTask {
2873 error: "FetchPartitionError".to_string(),
2874 retryable: false,
2875 count_to_failures: false,
2876 failed_reason: Some(failed_task::FailedReason::FetchPartitionError(
2877 FetchPartitionError {
2878 executor_id: executor1.id.clone(),
2879 map_stage_id: 2,
2880 map_partition_id: 0,
2881 },
2882 )),
2883 },
2884 );
2885
2886 let _stage_events =
2887 agg_graph.update_task_status(&executor3, vec![task_status1], 4, 4)?;
2888 }
2889 let running_stage = agg_graph.running_stages();
2891 assert_eq!(running_stage.len(), 1);
2892 assert_eq!(running_stage[0], 2);
2893 assert_eq!(agg_graph.available_tasks(), 3);
2894
2895 if let Some(task1) = agg_graph.pop_next_task(&executor3.id)? {
2897 let task_status1 = mock_failed_task(
2898 task1,
2899 FailedTask {
2900 error: "FetchPartitionError".to_string(),
2901 retryable: false,
2902 count_to_failures: false,
2903 failed_reason: Some(failed_task::FailedReason::FetchPartitionError(
2904 FetchPartitionError {
2905 executor_id: executor1.id.clone(),
2906 map_stage_id: 1,
2907 map_partition_id: 0,
2908 },
2909 )),
2910 },
2911 );
2912 let _stage_events =
2913 agg_graph.update_task_status(&executor3, vec![task_status1], 4, 4)?;
2914 }
2915 let running_stage = agg_graph.running_stages();
2917 assert_eq!(running_stage.len(), 1);
2918 assert_eq!(running_stage[0], 1);
2919 assert_eq!(agg_graph.available_tasks(), 2);
2920
2921 assert_eq!(agg_graph.failed_stage_attempts.len(), 2);
2923 assert_eq!(
2924 agg_graph.failed_stage_attempts.get(&2).cloned(),
2925 Some(HashSet::from([1]))
2926 );
2927 assert_eq!(
2928 agg_graph.failed_stage_attempts.get(&3).cloned(),
2929 Some(HashSet::from([0]))
2930 );
2931
2932 drain_tasks(&mut agg_graph)?;
2933 assert!(agg_graph.is_successful(), "Failed to complete agg plan");
2934 assert_eq!(agg_graph.failed_stage_attempts.len(), 0);
2935 Ok(())
2936 }
2937
2938 #[tokio::test]
2939 async fn test_fetch_failure_with_normal_task_failure() -> Result<()> {
2940 let executor1 = mock_executor("executor-id1".to_string());
2941 let executor2 = mock_executor("executor-id2".to_string());
2942 let mut agg_graph = test_aggregation_plan(4).await;
2943
2944 revive_graph_and_complete_next_stage(&mut agg_graph)?;
2946
2947 let task1 = agg_graph.pop_next_task(&executor2.id)?.unwrap();
2949 let task_status1 = mock_completed_task(task1, &executor2.id);
2950
2951 let task2 = agg_graph.pop_next_task(&executor2.id)?.unwrap();
2953 let task_status2 = mock_failed_task(
2954 task2,
2955 FailedTask {
2956 error: "FetchPartitionError".to_string(),
2957 retryable: false,
2958 count_to_failures: false,
2959 failed_reason: Some(failed_task::FailedReason::FetchPartitionError(
2960 FetchPartitionError {
2961 executor_id: executor1.id.clone(),
2962 map_stage_id: 1,
2963 map_partition_id: 0,
2964 },
2965 )),
2966 },
2967 );
2968
2969 let task3 = agg_graph.pop_next_task(&executor2.id)?.unwrap();
2971 let task_status3 = mock_failed_task(
2972 task3,
2973 FailedTask {
2974 error: "ExecutionError".to_string(),
2975 retryable: false,
2976 count_to_failures: false,
2977 failed_reason: Some(failed_task::FailedReason::ExecutionError(
2978 ExecutionError {},
2979 )),
2980 },
2981 );
2982
2983 let stage_events = agg_graph.update_task_status(
2984 &executor2,
2985 vec![task_status1, task_status2, task_status3],
2986 4,
2987 4,
2988 )?;
2989
2990 assert_eq!(stage_events.len(), 1);
2991 assert!(matches!(
2992 stage_events[0],
2993 QueryStageSchedulerEvent::JobRunningFailed { .. }
2994 ));
2995
2996 drain_tasks(&mut agg_graph)?;
2997 assert!(!agg_graph.is_successful(), "Expect to fail the agg plan");
2998
2999 let failure_reason = format!("{:?}", agg_graph.status);
3000 assert!(failure_reason.contains("Job failed due to stage 2 failed"));
3001 assert!(failure_reason.contains("ExecutionError"));
3002
3003 Ok(())
3004 }
3005
3006 fn drain_tasks(graph: &mut dyn ExecutionGraph) -> Result<()> {
3012 let executor = mock_executor("executor-id1".to_string());
3013 while let Some(task) = graph.pop_next_task(&executor.id)? {
3014 let task_status = mock_completed_task(task, &executor.id);
3015 graph.update_task_status(&executor, vec![task_status], 1, 1)?;
3016 }
3017
3018 Ok(())
3019 }
3020}