Skip to main content

ballista_scheduler/state/
execution_graph.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use 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
56/// Boxed [ExecutionGraph]
57pub type ExecutionGraphBox = Box<dyn ExecutionGraph + Send + Sync>;
58
59/// Represents the DAG for a distributed query plan.
60///
61/// A distributed query plan consists of a set of stages which must be executed sequentially.
62///
63/// Each stage consists of a set of partitions which can be executed in parallel, where each partition
64/// represents a `Task`, which is the basic unit of scheduling in Ballista.
65///
66/// As an example, consider a SQL query which performs a simple aggregation:
67///
68/// `SELECT id, SUM(gmv) FROM some_table GROUP BY id`
69///
70/// This will produce a DataFusion execution plan that looks something like
71///
72/// ```text
73///   CoalesceBatchesExec: target_batch_size=4096
74///     RepartitionExec: partitioning=Hash([Column { name: "id", index: 0 }], 4)
75///       AggregateExec: mode=Partial, gby=[id\@0 as id], aggr=[SUM(some_table.gmv)]
76///         TableScan: some_table
77/// ```
78///
79/// The Ballista `DistributedPlanner` will turn this into a distributed plan by creating a shuffle
80/// boundary (called a "Stage") whenever the underlying plan needs to perform a repartition.
81/// In this case we end up with a distributed plan with two stages:
82///
83/// ```text
84/// ExecutionGraph[job_id=job, session_id=session, available_tasks=1, complete=false]
85/// =========UnResolvedStage[id=2, children=1]=========
86/// Inputs{1: StageOutput { partition_locations: {}, complete: false }}
87/// ShuffleWriterExec: None
88///   AggregateExec: mode=FinalPartitioned, gby=[id\@0 as id], aggr=[SUM(?table?.gmv)]
89///     CoalesceBatchesExec: target_batch_size=4096
90///       UnresolvedShuffleExec
91/// =========ResolvedStage[id=1, partitions=1]=========
92/// ShuffleWriterExec: Some(Hash([Column { name: "id", index: 0 }], 4))
93///   AggregateExec: mode=Partial, gby=[id\@0 as id], aggr=[SUM(?table?.gmv)]
94///     TableScan: some_table
95/// ```
96///
97/// The DAG structure of this `ExecutionGraph` is encoded in the stages. Each stage's `input` field
98/// will indicate which stages it depends on, and each stage's `output_links` will indicate which
99/// stage it needs to publish its output to.
100///
101/// If a stage has `output_links` is empty then it is the final stage in this query, and it should
102/// publish its outputs to the `ExecutionGraph`s `output_locations` representing the final query results.
103pub trait ExecutionGraph: Debug {
104    /// Returns the job ID for this execution graph.
105    fn job_id(&self) -> &JobId;
106
107    /// Returns the job name for this execution graph.
108    fn job_name(&self) -> &str;
109
110    /// Returns the session ID associated with this job.
111    fn session_id(&self) -> &str;
112
113    /// Returns the session config associated with this job.
114    fn session_config(&self) -> Arc<SessionConfig>;
115
116    /// Returns the current status of the job.
117    fn status(&self) -> &JobStatus;
118
119    /// Returns the logical plan as a string, if captured at submission time.
120    fn logical_plan(&self) -> Option<&str>;
121
122    /// Returns the physical plan as a string, if captured at submission time.
123    fn physical_plan(&self) -> Arc<dyn ExecutionPlan>;
124
125    /// Returns the timestamp when this job started execution.
126    fn start_time(&self) -> u64;
127
128    /// Returns the timestamp when this job started execution.
129    fn end_time(&self) -> u64;
130
131    /// Number of completed stages
132    fn completed_stages(&self) -> usize;
133
134    /// An ExecutionGraph is successful if all its stages are successful
135    fn is_successful(&self) -> bool;
136
137    /// Revive the execution graph by converting the resolved stages to running stages
138    /// If any stages are converted, return true; else false.
139    fn revive(&mut self) -> bool;
140
141    /// Update task statuses and task metrics in the graph.
142    /// This will also push shuffle partitions to their respective shuffle read stages.
143    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    /// Returns all the currently running stage IDs.
152    fn running_stages(&self) -> Vec<usize>;
153
154    /// Returns all currently running tasks along with the executor ID on which they are assigned.
155    fn running_tasks(&self) -> Vec<RunningTaskInfo>;
156
157    /// Returns the total number of tasks in this plan that are ready for scheduling.
158    fn available_tasks(&self) -> usize;
159
160    /// Fetches a running stage that has available tasks, excluding stages in the blacklist.
161    ///
162    /// Returns a mutable reference to the running stage and the task ID generator
163    /// if a suitable stage is found.
164    fn fetch_running_stage(
165        &mut self,
166        black_list: &[usize],
167    ) -> Option<(&mut RunningStage, &mut usize)>;
168
169    /// Updates the job status.
170    fn update_status(&mut self, status: JobStatus);
171
172    /// Returns the output partition locations for the final stage results.
173    fn output_locations(&self) -> Vec<PartitionLocation>;
174
175    /// Reset running and successful stages on a given executor
176    /// This will first check the unresolved/resolved/running stages and reset the running tasks and successful tasks.
177    /// Then it will check the successful stage and whether there are running parent stages need to read shuffle from it.
178    /// If yes, reset the successful tasks and roll back the resolved shuffle recursively.
179    ///
180    /// Returns the reset stage ids and running tasks should be killed
181    fn reset_stages_on_lost_executor(
182        &mut self,
183        executor_id: &str,
184    ) -> Result<(HashSet<usize>, Vec<RunningTaskInfo>)>;
185
186    /// Converts an unresolved stage to resolved state.
187    ///
188    /// Returns true if the stage was successfully resolved, false if the stage
189    /// was not found or not in unresolved state.
190    fn resolve_stage(&mut self, stage_id: usize) -> Result<bool>;
191
192    /// Converts a running stage to successful state.
193    ///
194    /// Returns true if the stage was successfully marked as complete.
195    fn succeed_stage(&mut self, stage_id: usize) -> bool;
196
197    /// Converts a running stage to failed state with the given error message.
198    ///
199    /// Returns true if the stage was found and marked as failed.
200    fn fail_stage(&mut self, stage_id: usize, err_msg: String) -> bool;
201
202    /// Convert running stage to be unresolved,
203    /// Returns a Vec of RunningTaskInfo for running tasks in this stage.
204    fn rollback_running_stage(
205        &mut self,
206        stage_id: usize,
207        failure_reasons: HashSet<String>,
208    ) -> Result<Vec<RunningTaskInfo>>;
209
210    /// Convert resolved stage to be unresolved
211    fn rollback_resolved_stage(&mut self, stage_id: usize) -> Result<bool>;
212
213    /// Converts a successful stage back to running state for re-execution.
214    ///
215    /// This is used when some outputs from the stage have been lost and tasks
216    /// need to be re-run.
217    fn rerun_successful_stage(&mut self, stage_id: usize) -> bool;
218
219    /// fail job with error message
220    fn fail_job(&mut self, error: String);
221
222    /// Abort a running job: fail it, transition every running stage to Failed,
223    /// and return the in-flight tasks that should be cancelled. Used for both the
224    /// failure and cancellation teardown paths.
225    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    /// Marks the job as successfully completed.
235    ///
236    /// This should only be called after all stages have completed successfully.
237    /// Returns an error if the job is not in a successful state.
238    fn succeed_job(&mut self) -> Result<()>;
239
240    /// Exposes executions stages and stage id's
241    fn stages(&self) -> &HashMap<usize, ExecutionStage>;
242
243    /// Stage ids of all non-final (intermediate) stages — those whose
244    /// `output_links` is non-empty. The final stage(s) are excluded.
245    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    /// returns next task to run
254    /// (used for testing only)
255    #[cfg(test)]
256    fn pop_next_task(&mut self, executor_id: &str) -> Result<Option<TaskDescription>>;
257
258    /// Returns the total number of stages in this execution graph.
259    fn stage_count(&self) -> usize;
260
261    /// Clones execution graph
262    fn cloned(&self) -> ExecutionGraphBox;
263}
264
265/// [ExecutionGraph] implementation which generates
266/// all stages on job submission time
267#[derive(Clone)]
268pub struct StaticExecutionGraph {
269    /// Curator scheduler name. Can be `None` is `ExecutionGraph` is not currently curated by any scheduler
270    #[allow(dead_code)] // not used at the moment, will be used later
271    scheduler_id: Option<String>,
272    /// ID for this job
273    job_id: JobId,
274    /// Job name, can be empty string
275    job_name: String,
276    /// Session ID for this job
277    session_id: String,
278    /// Status of this job
279    status: JobStatus,
280    /// Timestamp of when this job was submitted
281    queued_at: u64,
282    /// Job start time
283    start_time: u64,
284    /// Job end time
285    end_time: u64,
286    /// Map from Stage ID -> ExecutionStage
287    stages: HashMap<usize, ExecutionStage>,
288    /// Locations of this `ExecutionGraph` final output locations
289    output_locations: Vec<PartitionLocation>,
290    /// Task ID generator, generate unique TID in the execution graph
291    task_id_gen: usize,
292    /// Failed stage attempts, record the failed stage attempts to limit the retry times.
293    /// Map from Stage ID -> Set<Stage_ATTPMPT_NUM>
294    failed_stage_attempts: HashMap<usize, HashSet<usize>>,
295    /// Session config for this job
296    session_config: Arc<SessionConfig>,
297    /// Logical plan as a human-readable string, captured at submission time.
298    logical_plan: Option<String>,
299    /// Physical plan as a human-readable string, captured at submission time.
300    physical_plan: Arc<dyn ExecutionPlan>,
301}
302
303/// Information about a currently running task.
304///
305/// Used to track tasks that are in progress and may need to be cancelled
306/// when an executor is lost or a job is cancelled.
307#[derive(Clone, Debug)]
308pub struct RunningTaskInfo {
309    /// Unique identifier for this task within the execution graph.
310    pub task_id: usize,
311    /// The job ID this task belongs to.
312    pub job_id: JobId,
313    /// The stage ID this task belongs to.
314    pub stage_id: usize,
315    /// The partition this task is processing.
316    pub partition_id: usize,
317    /// The executor ID where this task is running.
318    pub executor_id: String,
319}
320
321impl StaticExecutionGraph {
322    /// Creates a new `ExecutionGraph` from a physical execution plan.
323    ///
324    /// This will use the `DistributedPlanner` to break the plan into stages
325    /// and build the DAG structure needed for distributed execution.
326    #[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    /// Processing stage status update after task status changing
382    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        // Fail the stage and also abort the job
400        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        // Only handle the rollback logic when there are no failed stages
407        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            // If this ExecutionGraph is successful, finish it
436            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    /// Return a Vec of resolvable stage ids
450    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            // If `output_links` is empty, then this is a final stage
461            self.output_locations.extend(locations);
462        } else {
463            for link in output_links.iter() {
464                // If this is an intermediate stage, we need to push its `PartitionLocation`s to the parent stage
465                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 all tasks for this stage are complete, mark the input complete in the parent stage
473                        if is_completed {
474                            linked_unresolved_stage.complete_input(stage_id);
475                        }
476
477                        // If all input partitions are ready, we can resolve any UnresolvedShuffleExec in the parent stage plan
478                        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 no available tasks found in the running stage,
512        // try to find a resolved stage and convert it to the running stage
513        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        // collect the input stages that need to resubmit
530        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        // check the unresolved, resolved and running stages
539        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                // For each stage input, check whether there are input locations match that executor
563                // and calculate the resubmit input stages if the input stages are successful.
564                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        // check and reset the successful stages
601        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    /// Clear the stage failure count for this stage if the stage is finally success
650    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    /// An ExecutionGraph is successful if all its stages are successful
706    fn is_successful(&self) -> bool {
707        self.stages
708            .values()
709            .all(|s| matches!(s, ExecutionStage::Successful(_)))
710    }
711
712    // pub fn is_complete(&self) -> bool {
713    //     self.stages
714    //         .values()
715    //         .all(|s| matches!(s, ExecutionStage::Successful(_)))
716    // }
717
718    /// Revive the execution graph by converting the resolved stages to running stages
719    /// If any stages are converted, return true; else false.
720    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    /// Update task statuses and task metrics in the graph.
747    /// This will also push shuffle partitions to their respective shuffle read stages.
748    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        // First of all, classify the statuses by stages
757        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        // Revive before updating due to some updates not saved
765        // It will be refined later
766        self.revive();
767
768        let current_running_stages: HashSet<usize> =
769            HashSet::from_iter(self.running_stages());
770
771        // Copy the failed stage attempts from self
772        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                                            // There are different removal strategies here.
850                                            // We can choose just remove the map_partition_id in the FetchPartitionError, when resubmit the input stage, there are less tasks
851                                            // need to rerun, but this might miss many more bad input partitions, lead to more stage level retries in following.
852                                            // Here we choose remove all the bad input partitions which match the same executor id in this single input stage.
853                                            // There are other more aggressive approaches, like considering the executor is lost and check all the running stages in this graph.
854                                            // Or count the fetch failure number on executor and mark the executor lost globally.
855                                            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                                            // TODO add new struct to track all the failed task infos
899                                            // The failure TaskInfo is ignored and set to None here
900                                            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                                        // TODO add new struct to track all the failed task infos
914                                        // The failure TaskInfo is ignored and set to None here
915                                        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                            // update task metrics for successfu task
931                            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 this stage is final successful, we want to combine the stage metrics to plan's metric set and print out the plan
953                        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                        // handle delayed failed tasks if the stage's next attempt is still in UnResolved status.
990                        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 the previous other task updates had already mark the map stage success, need to remove it.
1036                                    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        // Update failed stage attempts back to self
1071        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                        // Update the task info to failed
1088                        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    /// Return all the currently running stage ids
1144    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    /// Return all currently running tasks along with the executor ID on which they are assigned
1158    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    /// Total number of tasks in this plan that are ready for scheduling
1184    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    /// Reset running and successful stages on a given executor
1236    /// This will first check the unresolved/resolved/running stages and reset the running tasks and successful tasks.
1237    /// Then it will check the successful stage and whether there are running parent stages need to read shuffle from it.
1238    /// If yes, reset the successful tasks and roll back the resolved shuffle recursively.
1239    ///
1240    /// Returns the reset stage ids and running tasks should be killed
1241    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    /// Convert unresolved stage to be resolved
1259    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    /// Convert running stage to be successful
1279    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    /// Convert running stage to be failed
1296    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    /// Convert running stage to be unresolved,
1312    /// Returns a Vec of RunningTaskInfo for running tasks in this stage.
1313    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    /// Convert resolved stage to be unresolved
1348    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    /// Convert successful stage to be running
1364    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    /// fail job with error message
1380    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    /// Mark the job success
1396    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    /// Get next task that can be assigned to the given executor.
1436    /// This method should only be called when the resulting task is immediately
1437    /// being launched as the status will be set to Running and it will not be
1438    /// available to the scheduler.
1439    /// If the task is not launched the status must be reset to allow the task to
1440    /// be scheduled elsewhere.
1441    #[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                    // Those times will be updated when the task finish
1502                    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                // Set the task info to Running for new task
1512                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 no available tasks found in the running stage,
1529        // try to find a resolved stage and convert it to the running stage
1530        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
1562/// Creates a new `TaskInfo` for a task that is about to be scheduled on an executor.
1563pub 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        // Those times will be updated when the task finish
1571        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
1579/// Utility for building a set of `ExecutionStage`s from
1580/// a list of `ShuffleWriterExec`.
1581///
1582/// This will infer the dependency structure for the stages
1583/// so that we can construct a DAG from the stages.
1584pub(crate) struct ExecutionStageBuilder {
1585    /// Stage ID which is currently being visited
1586    current_stage_id: usize,
1587    /// Map from stage ID -> List of child stage IDs
1588    stage_dependencies: HashMap<usize, Vec<usize>>,
1589    /// Map from Stage ID -> output link
1590    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        // First, build the dependency graph
1610        for stage in &stages {
1611            accept(stage.as_ref(), &mut self)?;
1612        }
1613
1614        // Now, create the execution stages
1615        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        // Handle both ShuffleWriterExec and SortShuffleWriterExec
1658        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/// Represents the basic unit of work for the Ballista executor.
1690///
1691/// A `TaskDescription` contains all the information needed to execute
1692/// one partition of one stage on a single executor task slot.
1693#[derive(Clone)]
1694pub struct TaskDescription {
1695    /// The session ID associated with this task's job.
1696    pub session_id: String,
1697    /// The partition identifier (job_id, stage_id, partition_id).
1698    pub partition: PartitionId,
1699    /// The attempt number for this stage (for retry tracking).
1700    pub stage_attempt_num: usize,
1701    /// Unique task ID within the execution graph.
1702    pub task_id: usize,
1703    /// The attempt number for this specific task (for retry tracking).
1704    pub task_attempt: usize,
1705    /// The physical execution plan to run for this task.
1706    pub plan: Arc<dyn ExecutionPlan>,
1707    /// Session configuration for this task's execution context.
1708    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    /// Returns the number of output partitions this task will produce.
1731    pub fn get_output_partition_number(&self) -> usize {
1732        // Try ShuffleWriterExec first
1733        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        // Try SortShuffleWriterExec
1740        if let Some(shuffle_writer) = self.plan.downcast_ref::<SortShuffleWriterExec>() {
1741            return shuffle_writer
1742                .shuffle_output_partitioning()
1743                .partition_count();
1744        }
1745        // Default fallback
1746        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        // A simple aggregation produces a 2-stage graph: one intermediate
1802        // stage (non-empty output_links) feeding one final stage (empty
1803        // output_links).
1804        let graph = test_aggregation_plan(4).await;
1805
1806        assert_eq!(graph.stages().len(), 2);
1807
1808        // Exactly one final stage.
1809        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        // Intermediate = all - final = exactly one stage, and none of the
1817        // returned ids is a final stage.
1818        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        // With the improvement of https://github.com/apache/arrow-datafusion/pull/4122,
1942        // unnecessary RepartitionExec can be removed
1943        assert_eq!(join_graph.stage_count(), 4);
1944        assert_eq!(join_graph.available_tasks(), 0);
1945
1946        // Call revive to move the two leaf Resolved stages to Running
1947        join_graph.revive();
1948
1949        assert_eq!(join_graph.stage_count(), 4);
1950        assert_eq!(join_graph.available_tasks(), 4);
1951
1952        // Complete the first stage
1953        revive_graph_and_complete_next_stage_with_executor(&mut join_graph, &executor1)?;
1954
1955        // Complete the second stage
1956        revive_graph_and_complete_next_stage_with_executor(&mut join_graph, &executor2)?;
1957
1958        join_graph.revive();
1959        // There are 4 tasks pending schedule for the 3rd stage
1960        assert_eq!(join_graph.available_tasks(), 4);
1961
1962        // Complete 1 task
1963        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        // Mock 1 running task
1968        let _task = join_graph.pop_next_task(&executor1.id)?;
1969
1970        let reset = join_graph.reset_stages_on_lost_executor(&executor1.id)?;
1971
1972        // Two stages were reset, 1 Running stage rollback to Unresolved and 1 Completed stage move to Running
1973        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        // Call revive to move the two leaf Resolved stages to Running
1992        join_graph.revive();
1993
1994        assert_eq!(join_graph.stage_count(), 4);
1995        assert_eq!(join_graph.available_tasks(), 4);
1996
1997        // Complete the first stage
1998        assert_eq!(revive_graph_and_complete_next_stage(&mut join_graph)?, 2);
1999
2000        // Complete the second stage
2001        assert_eq!(
2002            revive_graph_and_complete_next_stage_with_executor(
2003                &mut join_graph,
2004                &executor2
2005            )?,
2006            2
2007        );
2008
2009        // There are 0 tasks pending schedule now
2010        assert_eq!(join_graph.available_tasks(), 0);
2011
2012        let reset = join_graph.reset_stages_on_lost_executor(&executor1.id)?;
2013
2014        // Two stages were reset, 1 Resolved stage rollback to Unresolved and 1 Completed stage move to Running
2015        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        // Call revive to move the leaf Resolved stages to Running
2034        agg_graph.revive();
2035
2036        assert_eq!(agg_graph.stage_count(), 2);
2037        assert_eq!(agg_graph.available_tasks(), 2);
2038
2039        // Complete the first stage
2040        revive_graph_and_complete_next_stage_with_executor(&mut agg_graph, &executor1)?;
2041
2042        // 1st task in the second stage
2043        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        // 2rd task in the second stage
2049        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        // 3rd task in the second stage, scheduled but not completed
2055        let task = agg_graph.pop_next_task(&executor1.id)?;
2056
2057        // There is 1 task pending schedule now
2058        assert_eq!(agg_graph.available_tasks(), 1);
2059
2060        let reset = agg_graph.reset_stages_on_lost_executor(&executor1.id)?;
2061
2062        // 3rd task status update comes later.
2063        let task_status = mock_completed_task(task.unwrap(), &executor1.id);
2064        agg_graph.update_task_status(&executor1, vec![task_status], 1, 1)?;
2065
2066        // Two stages were reset, 1 Running stage rollback to Unresolved and 1 Completed stage move to Running
2067        assert_eq!(reset.0.len(), 2);
2068        assert_eq!(agg_graph.available_tasks(), 2);
2069
2070        // Call the reset again
2071        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        // Call revive to move the leaf Resolved stages to Running
2086        agg_graph.revive();
2087
2088        // Complete the first stage
2089        revive_graph_and_complete_next_stage(&mut agg_graph)?;
2090
2091        // 1st task in the second stage
2092        let task1 = agg_graph.pop_next_task(&executor.id)?.unwrap();
2093        let task_status1 = mock_completed_task(task1, &executor.id);
2094
2095        // 2rd task in the second stage
2096        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        // Call revive to move the leaf Resolved stages to Running
2130        agg_graph.revive();
2131
2132        // Complete the first stage
2133        revive_graph_and_complete_next_stage(&mut agg_graph)?;
2134
2135        // 1st task in the second stage
2136        let task1 = agg_graph.pop_next_task(&executor.id)?.unwrap();
2137        let task_status1 = mock_completed_task(task1, &executor.id);
2138
2139        // 2rd task in the second stage, failed due to IOError
2140        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        // 2rd task's attempts
2162        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    // Aborting a running job (failure or cancellation) must transition every
2209    // running stage to Failed and return its in-flight tasks for cancellation.
2210    // `abort_running` is the shared teardown invoked by `abort_job`.
2211    #[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        // Call revive to move the two leaf Resolved stages to Running
2218        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        // Dispatch a task so there is an in-flight task to cancel
2226        let _task = graph.pop_next_task(&executor.id)?.unwrap();
2227
2228        // Aborting cancels every running stage and returns its in-flight tasks
2229        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        // In-flight tasks of the cancelled stage are recorded as Failed(TaskKilled)
2252        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        // Call revive to move the leaf Resolved stages to Running
2280        agg_graph.revive();
2281
2282        // Complete the Stage 1
2283        revive_graph_and_complete_next_stage_with_executor(&mut agg_graph, &executor1)?;
2284
2285        // 1st task in the Stage 2
2286        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        // 2rd task in the Stage 2
2292        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        // 3rd task in the Stage 2, scheduled on executor 2 but not completed
2298        let task = agg_graph.pop_next_task(&executor2.id)?;
2299
2300        // There is 1 task pending schedule now
2301        assert_eq!(agg_graph.available_tasks(), 1);
2302
2303        // executor 1 lost
2304        let reset = agg_graph.reset_stages_on_lost_executor(&executor1.id)?;
2305
2306        // Two stages were reset, Stage 2 rollback to Unresolved and Stage 1 move to Running
2307        assert_eq!(reset.0.len(), 2);
2308        assert_eq!(agg_graph.available_tasks(), 2);
2309
2310        // Complete the Stage 1 again
2311        revive_graph_and_complete_next_stage_with_executor(&mut agg_graph, &executor1)?;
2312
2313        // Stage 2 move to Running
2314        agg_graph.revive();
2315        assert_eq!(agg_graph.available_tasks(), 4);
2316
2317        // 3rd task in Stage 2 update comes very late due to runtime execution error.
2318        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        // This long delayed failed task should not failure the stage/job and should not trigger any query stage events
2331        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        // Call revive to move the leaf Resolved stages to Running
2347        agg_graph.revive();
2348
2349        // Complete the Stage 1
2350        revive_graph_and_complete_next_stage(&mut agg_graph)?;
2351
2352        // 1st task in the Stage 2
2353        let task1 = agg_graph.pop_next_task(&executor2.id)?.unwrap();
2354        let task_status1 = mock_completed_task(task1, &executor2.id);
2355
2356        // 2nd task in the Stage 2, failed due to FetchPartitionError
2357        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        // Stage 1 is running
2394        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        // Complete the Stage 1
2415        revive_graph_and_complete_next_stage(&mut agg_graph)?;
2416
2417        // Complete the Stage 2, 5 tasks run on executor_2 and 3 tasks run on executor_1
2418        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        // Run Stage 3, 6 tasks failed due to FetchPartitionError on different map partitions on executor_2
2433        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        // The Running stage should be Stage 2 now
2460        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        // Call revive to move the leaf Resolved stages to Running
2476        agg_graph.revive();
2477
2478        for attempt in 0..6 {
2479            revive_graph_and_complete_next_stage(&mut agg_graph)?;
2480
2481            // 1rd task in the Stage 2, failed due to FetchPartitionError
2482            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                    // No JobRunningFailed stage events
2506                    assert_eq!(stage_events.len(), 0);
2507                    // Stage 1 is running
2508                    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                    // Job is failed after exceeds the max_stage_failures
2514                    assert_eq!(stage_events.len(), 1);
2515                    assert!(matches!(
2516                        stage_events[0],
2517                        QueryStageSchedulerEvent::JobRunningFailed { .. }
2518                    ));
2519                    // Stage 2 is still running
2520                    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        // Complete the Stage 1
2548        revive_graph_and_complete_next_stage(&mut agg_graph)?;
2549
2550        // Complete the Stage 2, 5 tasks run on executor_2, 2 tasks run on executor_1, 1 task runs on executor_3
2551        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        //Run Stage 3
2573        // 1st task scheduled
2574        let task_1 = agg_graph.pop_next_task(&executor3.id)?.unwrap();
2575        // 2nd task scheduled
2576        let task_2 = agg_graph.pop_next_task(&executor3.id)?.unwrap();
2577        // 3rd task scheduled
2578        let task_3 = agg_graph.pop_next_task(&executor3.id)?.unwrap();
2579        // 4th task scheduled
2580        let task_4 = agg_graph.pop_next_task(&executor3.id)?.unwrap();
2581        // 5th task scheduled
2582        let task_5 = agg_graph.pop_next_task(&executor3.id)?.unwrap();
2583
2584        // Stage 3, 1st task failed due to FetchPartitionError(executor2)
2585        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        // The Running stage is Stage 2 now
2603        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        // Stage 3, 2nd task failed due to FetchPartitionError(executor2)
2609        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        // This task update should be ignored
2625        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        // Stage 3, 3rd task failed due to FetchPartitionError(executor1)
2632        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        // This task update should be handled because it has a different failure reason
2648        agg_graph.update_task_status(&executor3, vec![task_status_3], 4, 4)?;
2649        // Running stage is still Stage 2, but available tasks changed to 7
2650        assert_eq!(running_stage.len(), 1);
2651        assert_eq!(running_stage[0], 2);
2652        assert_eq!(agg_graph.available_tasks(), 7);
2653
2654        // Finish 4 tasks in Stage 2, to make some progress
2655        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        // Stage 3, 4th task failed due to FetchPartitionError(executor1)
2666        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        // This task update should be ignored because the same failure reason is already handled
2682        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        // Finish the other 3 tasks in Stage 2
2689        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        // Stage 3, the very long delayed 5th task failed due to FetchPartitionError(executor3)
2698        // Although the failure reason is new, but this task should be ignored
2699        // Because its map stage's new attempt is finished and this stage's new attempt is running
2700        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        // Stage 3's new attempt is running
2717        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        // There is one failed stage attempts: Stage 3. Stage 2 does not count to failed attempts
2723        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        // Failed stage attempts are cleaned
2731        assert_eq!(agg_graph.failed_stage_attempts.len(), 0);
2732
2733        Ok(())
2734    }
2735
2736    #[tokio::test]
2737    // This test case covers a race condition in delayed fetch failure handling:
2738    // TaskStatus of input stage's new attempt come together with the parent stage's delayed FetchFailure
2739    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        // Complete the Stage 1
2749        revive_graph_and_complete_next_stage(&mut agg_graph)?;
2750
2751        // Complete the Stage 2, 5 tasks run on executor_2, 3 tasks run on executor_1
2752        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        // Run Stage 3
2769        // 1st task scheduled
2770        let task_1 = agg_graph.pop_next_task(&executor3.id)?.unwrap();
2771        // 2nd task scheduled
2772        let task_2 = agg_graph.pop_next_task(&executor3.id)?.unwrap();
2773
2774        // Stage 3, 1st task failed due to FetchPartitionError(executor2)
2775        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        // The Running stage is Stage 2 now
2793        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        // Complete the 5 tasks in Stage 2's new attempts
2799        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        // Stage 3, 2nd task failed due to FetchPartitionError(executor1)
2807        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        // TaskStatus of Stage 2 come together with Stage 3 delayed FetchFailure update.
2825        // The successful tasks from Stage 2 would try to succeed the Stage2 and the delayed fetch failure try to reset the TaskInfo
2826        agg_graph.update_task_status(&executor3, task_status_vec, 4, 4)?;
2827        //The Running stage is still Stage 2, 3 new pending tasks added due to FetchPartitionError(executor1)
2828        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        // Complete the Stage 1
2849        revive_graph_and_complete_next_stage(&mut agg_graph)?;
2850
2851        // Complete the Stage 2, 5 tasks run on executor_2, 3 tasks run on executor_1
2852        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        // Run Stage 3
2868        // 1rd task in the Stage 3, failed due to FetchPartitionError(executor1)
2869        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        // The Running stage is Stage 2 now
2890        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        // 1rd task in the Stage 2's new attempt, failed due to FetchPartitionError(executor1)
2896        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        // The Running stage is Stage 1 now
2916        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        // There are two failed stage attempts: Stage 2 and Stage 3
2922        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        // Complete the Stage 1
2945        revive_graph_and_complete_next_stage(&mut agg_graph)?;
2946
2947        // 1st task in the Stage 2
2948        let task1 = agg_graph.pop_next_task(&executor2.id)?.unwrap();
2949        let task_status1 = mock_completed_task(task1, &executor2.id);
2950
2951        // 2nd task in the Stage 2, failed due to FetchPartitionError
2952        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        // 3rd task in the Stage 2, failed due to ExecutionError
2970        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    // #[tokio::test]
3007    // async fn test_shuffle_files_should_cleaned_after_fetch_failure() -> Result<()> {
3008    //     todo!()
3009    // }
3010
3011    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}