Skip to main content

risingwave_meta/barrier/
partial_graph.rs

1// Copyright 2026 RisingWave Labs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::collections::Bound::Unbounded;
16use std::collections::hash_map::Entry;
17use std::collections::{HashMap, HashSet};
18use std::mem::take;
19use std::ops::{Bound, RangeBounds};
20use std::sync::Arc;
21use std::time::Instant;
22
23use educe::Educe;
24use itertools::Itertools;
25use risingwave_common::util::epoch::EpochPair;
26use risingwave_pb::common::WorkerNode;
27use risingwave_pb::id::{ActorId, PartialGraphId, TableId, WorkerId};
28use risingwave_pb::stream_plan::barrier_mutation::Mutation;
29use risingwave_pb::stream_service::BarrierCompleteResponse;
30use risingwave_pb::stream_service::streaming_control_stream_response::{
31    ResetPartialGraphResponse, Response,
32};
33use tracing::{debug, warn};
34
35use crate::barrier::BarrierKind;
36use crate::barrier::command::PostCollectCommand;
37use crate::barrier::context::GlobalBarrierWorkerContext;
38use crate::barrier::info::BarrierInfo;
39use crate::barrier::notifier::{CollectionNotifier, NotifierStarter};
40use crate::barrier::rpc::{ControlStreamManager, WorkerNodeEvent};
41use crate::barrier::utils::{BarrierItemCollector, NodeToCollect, is_valid_after_worker_err};
42use crate::manager::MetaSrvEnv;
43use crate::model::StreamJobActorsToCreate;
44use crate::{MetaError, MetaResult};
45
46#[derive(Debug)]
47pub(super) struct PartialGraphBarrierInfo {
48    enqueue_time: Instant,
49    pub(super) post_collect_command: PostCollectCommand,
50    pub(super) barrier_info: BarrierInfo,
51    pub(super) notifier: Option<CollectionNotifier>,
52    pub(super) table_ids_to_commit: HashSet<TableId>,
53}
54
55impl PartialGraphBarrierInfo {
56    pub(super) fn new(
57        post_collect_command: PostCollectCommand,
58        barrier_info: BarrierInfo,
59        notifier: Option<&mut NotifierStarter>,
60        table_ids_to_commit: HashSet<TableId>,
61    ) -> Self {
62        Self {
63            enqueue_time: Instant::now(),
64            post_collect_command,
65            barrier_info,
66            notifier: notifier.map(NotifierStarter::add_notify),
67            table_ids_to_commit,
68        }
69    }
70
71    pub(super) fn elapsed_secs(&self) -> f64 {
72        self.enqueue_time.elapsed().as_secs_f64()
73    }
74}
75
76pub(super) trait PartialGraphStat: Send + Sync + 'static {
77    fn observe_barrier_latency(&self, epoch: EpochPair, barrier_latency_secs: f64);
78    fn observe_barrier_num(&self, inflight_barrier_num: usize, collected_barrier_num: usize);
79}
80
81#[derive(Educe)]
82#[educe(Debug)]
83struct PartialGraphRunningState {
84    barrier_item_collector:
85        BarrierItemCollector<WorkerId, BarrierCompleteResponse, PartialGraphBarrierInfo>,
86    completing_epoch: Option<u64>,
87    #[educe(Debug(ignore))]
88    stat: Box<dyn PartialGraphStat>,
89}
90
91impl PartialGraphRunningState {
92    fn new(stat: Box<dyn PartialGraphStat>) -> Self {
93        Self {
94            barrier_item_collector: BarrierItemCollector::new(true),
95            completing_epoch: None,
96            stat,
97        }
98    }
99
100    fn is_empty(&self) -> bool {
101        self.barrier_item_collector.is_empty() && self.completing_epoch.is_none()
102    }
103
104    fn pending_barrier_num(&self) -> usize {
105        self.barrier_item_collector.inflight_barrier_num()
106            + self.barrier_item_collector.collected_barrier_num()
107            + usize::from(self.completing_epoch.is_some())
108    }
109
110    fn enqueue(&mut self, node_to_collect: NodeToCollect, info: PartialGraphBarrierInfo) {
111        let epoch = info.barrier_info.epoch();
112        assert_ne!(info.barrier_info.kind, BarrierKind::Initial);
113        if info.post_collect_command.should_checkpoint() {
114            assert!(info.barrier_info.kind.is_checkpoint());
115        }
116        self.barrier_item_collector
117            .enqueue(epoch, node_to_collect, info);
118        self.stat.observe_barrier_num(
119            self.barrier_item_collector.inflight_barrier_num(),
120            self.barrier_item_collector.collected_barrier_num(),
121        );
122    }
123
124    fn collect(&mut self, resp: BarrierCompleteResponse) {
125        debug!(
126            epoch = resp.epoch,
127            worker_id = %resp.worker_id,
128            partial_graph_id = %resp.partial_graph_id,
129            "collect barrier from worker"
130        );
131        self.barrier_item_collector
132            .collect(resp.epoch, resp.worker_id, resp);
133    }
134
135    fn barrier_collected<'a>(
136        &mut self,
137        temp_ref: &'a CollectedBarrierTempRef,
138    ) -> Option<CollectedBarrier<'a>> {
139        if let Some((epoch, info)) = self.barrier_item_collector.barrier_collected() {
140            self.stat
141                .observe_barrier_latency(epoch, info.elapsed_secs());
142            self.stat.observe_barrier_num(
143                self.barrier_item_collector.inflight_barrier_num(),
144                self.barrier_item_collector.collected_barrier_num(),
145            );
146            Some(temp_ref.collected_barrier(epoch, self.pending_barrier_num()))
147        } else {
148            None
149        }
150    }
151}
152
153#[derive(Debug)]
154struct ResetPartialGraphCollector {
155    remaining_workers: HashSet<WorkerId>,
156    reset_resps: HashMap<WorkerId, ResetPartialGraphResponse>,
157}
158
159impl ResetPartialGraphCollector {
160    fn collect(&mut self, worker_id: WorkerId, resp: ResetPartialGraphResponse) -> bool {
161        assert!(self.remaining_workers.remove(&worker_id));
162        self.reset_resps
163            .try_insert(worker_id, resp)
164            .expect("non-duplicate");
165        self.remaining_workers.is_empty()
166    }
167}
168
169#[derive(Educe)]
170#[educe(Debug)]
171enum PartialGraphStatus {
172    Running {
173        term_id: String,
174        state: PartialGraphRunningState,
175    },
176    Resetting(ResetPartialGraphCollector),
177    Initializing {
178        term_id: String,
179        epoch: EpochPair,
180        node_to_collect: NodeToCollect,
181        #[educe(Debug(ignore))]
182        stat: Option<Box<dyn PartialGraphStat>>,
183    },
184}
185
186impl PartialGraphStatus {
187    fn collect<'a>(
188        &mut self,
189        worker_id: WorkerId,
190        resp: BarrierCompleteResponse,
191        temp_ref: &'a CollectedBarrierTempRef,
192    ) -> Option<PartialGraphEvent<'a>> {
193        assert_eq!(worker_id, resp.worker_id);
194        match self {
195            PartialGraphStatus::Running { state, .. } => {
196                state.collect(resp);
197                state
198                    .barrier_collected(temp_ref)
199                    .map(PartialGraphEvent::BarrierCollected)
200            }
201            PartialGraphStatus::Resetting(_) => None,
202            PartialGraphStatus::Initializing {
203                term_id,
204                epoch,
205                node_to_collect,
206                stat,
207            } => {
208                assert_eq!(epoch.prev, resp.epoch);
209                assert!(node_to_collect.remove(&worker_id));
210                if node_to_collect.is_empty() {
211                    *self = PartialGraphStatus::Running {
212                        term_id: take(term_id),
213                        state: PartialGraphRunningState::new(
214                            stat.take().expect("should be taken for once"),
215                        ),
216                    };
217                    Some(PartialGraphEvent::Initialized)
218                } else {
219                    None
220                }
221            }
222        }
223    }
224}
225
226/// Holding the reference to a static empty map as a temporary workaround for borrow checker limitation on `CollectedBarrier`.
227/// Mod local method can create a `CollectedBarrierTempRef` locally, and the created `CollectedBarrier` will temporarily reference
228/// to the `CollectedBarrierTempRef`. The method must fill in the correct reference before returning in a mod-pub method.
229///
230/// This struct *must* be private to ensure that the invalid lifetime won't be leaked outside.
231struct CollectedBarrierTempRef {
232    resps: &'static HashMap<WorkerId, BarrierCompleteResponse>,
233}
234
235impl CollectedBarrierTempRef {
236    fn new() -> Self {
237        static EMPTY: std::sync::LazyLock<HashMap<WorkerId, BarrierCompleteResponse>> =
238            std::sync::LazyLock::new(HashMap::new);
239        CollectedBarrierTempRef { resps: &EMPTY }
240    }
241
242    fn collected_barrier(
243        &self,
244        epoch: EpochPair,
245        pending_barrier_num: usize,
246    ) -> CollectedBarrier<'_> {
247        CollectedBarrier {
248            epoch,
249            resps: self.resps,
250            pending_barrier_num,
251        }
252    }
253
254    fn correct_lifetime<'a>(
255        &self,
256        event: PartialGraphManagerEvent<'_>,
257        manager: &'a PartialGraphManager,
258    ) -> PartialGraphManagerEvent<'a> {
259        match event {
260            PartialGraphManagerEvent::PartialGraph(partial_graph_id, event) => {
261                let event = match event {
262                    PartialGraphEvent::BarrierCollected(collected) => {
263                        let pending_barrier_num = collected.pending_barrier_num;
264                        let state = manager.running_graph(partial_graph_id);
265                        let (epoch, resps, _) = state
266                            .barrier_item_collector
267                            .last_collected()
268                            .expect("should exist");
269                        assert_eq!(epoch, collected.epoch);
270                        PartialGraphEvent::BarrierCollected(CollectedBarrier {
271                            epoch,
272                            resps,
273                            pending_barrier_num,
274                        })
275                    }
276                    PartialGraphEvent::Reset(resps) => PartialGraphEvent::Reset(resps),
277                    PartialGraphEvent::Initialized => PartialGraphEvent::Initialized,
278                    PartialGraphEvent::Error(worker_id) => PartialGraphEvent::Error(worker_id),
279                };
280                PartialGraphManagerEvent::PartialGraph(partial_graph_id, event)
281            }
282            PartialGraphManagerEvent::Worker(worker_id, event) => {
283                PartialGraphManagerEvent::Worker(worker_id, event)
284            }
285        }
286    }
287}
288
289#[derive(Debug)]
290pub(super) struct CollectedBarrier<'a> {
291    pub epoch: EpochPair,
292    pub resps: &'a HashMap<WorkerId, BarrierCompleteResponse>,
293    pub pending_barrier_num: usize,
294}
295
296pub(super) enum PartialGraphEvent<'a> {
297    BarrierCollected(CollectedBarrier<'a>),
298    Reset(HashMap<WorkerId, ResetPartialGraphResponse>),
299    Initialized,
300    Error(WorkerId),
301}
302
303pub(super) enum WorkerEvent {
304    WorkerError {
305        err: MetaError,
306        affected_partial_graphs: HashSet<PartialGraphId>,
307    },
308    WorkerConnected,
309}
310
311fn existing_graphs(
312    graphs: &HashMap<PartialGraphId, PartialGraphStatus>,
313) -> impl Iterator<Item = (PartialGraphId, &str)> + '_ {
314    graphs
315        .iter()
316        .filter_map(|(partial_graph_id, status)| match status {
317            PartialGraphStatus::Running { term_id, .. }
318            | PartialGraphStatus::Initializing { term_id, .. } => {
319                Some((*partial_graph_id, term_id.as_str()))
320            }
321            PartialGraphStatus::Resetting(_) => None,
322        })
323}
324
325pub(super) struct PartialGraphManager {
326    control_stream_manager: ControlStreamManager,
327    graphs: HashMap<PartialGraphId, PartialGraphStatus>,
328}
329
330impl PartialGraphManager {
331    pub(super) fn uninitialized(env: MetaSrvEnv) -> Self {
332        Self {
333            control_stream_manager: ControlStreamManager::new(env),
334            graphs: HashMap::new(),
335        }
336    }
337
338    pub(super) async fn recover(
339        env: MetaSrvEnv,
340        nodes: &HashMap<WorkerId, WorkerNode>,
341        context: Arc<impl GlobalBarrierWorkerContext>,
342    ) -> Self {
343        let control_stream_manager = ControlStreamManager::recover(env, nodes, context).await;
344        Self {
345            control_stream_manager,
346            graphs: Default::default(),
347        }
348    }
349
350    pub(super) fn control_stream_manager(&self) -> &ControlStreamManager {
351        &self.control_stream_manager
352    }
353
354    pub(super) async fn add_worker(
355        &mut self,
356        node: WorkerNode,
357        context: Arc<impl GlobalBarrierWorkerContext>,
358    ) {
359        self.control_stream_manager
360            .add_worker(node, existing_graphs(&self.graphs), context)
361            .await
362    }
363
364    pub(super) fn remove_worker(&mut self, node: WorkerNode) {
365        self.control_stream_manager.remove_worker(node);
366    }
367
368    pub(super) fn clear_worker(&mut self) {
369        self.control_stream_manager.clear();
370    }
371
372    pub(crate) fn notify_all_err(&mut self, err: &MetaError) {
373        for (_, graph) in self.graphs.drain() {
374            if let PartialGraphStatus::Running { state, .. } = graph {
375                for info in state.barrier_item_collector.into_infos() {
376                    if let Some(notifier) = info.notifier {
377                        notifier.notify_collection_failed(err.clone());
378                    }
379                }
380            }
381        }
382    }
383}
384
385#[must_use]
386pub(super) struct PartialGraphAdder<'a> {
387    partial_graph_id: PartialGraphId,
388    manager: &'a mut PartialGraphManager,
389    consumed: bool,
390}
391
392impl PartialGraphAdder<'_> {
393    pub(super) fn added(mut self) {
394        self.consumed = true;
395    }
396
397    pub(super) fn failed(mut self) {
398        self.manager.reset_partial_graphs([self.partial_graph_id]);
399        self.consumed = true;
400    }
401
402    pub(super) fn manager(&mut self) -> &mut PartialGraphManager {
403        self.manager
404    }
405}
406
407impl Drop for PartialGraphAdder<'_> {
408    fn drop(&mut self) {
409        debug_assert!(self.consumed, "unconsumed graph adder");
410        if !self.consumed {
411            warn!(partial_graph_id = %self.partial_graph_id, "unconsumed graph adder");
412        }
413    }
414}
415
416impl PartialGraphManager {
417    pub(super) fn add_partial_graph(
418        &mut self,
419        partial_graph_id: PartialGraphId,
420        term_id: &str,
421        stat: impl PartialGraphStat,
422    ) -> PartialGraphAdder<'_> {
423        self.graphs
424            .try_insert(
425                partial_graph_id,
426                PartialGraphStatus::Running {
427                    term_id: term_id.to_owned(),
428                    state: PartialGraphRunningState::new(Box::new(stat)),
429                },
430            )
431            .expect("non-duplicated");
432        self.control_stream_manager
433            .add_partial_graph(partial_graph_id, term_id);
434        PartialGraphAdder {
435            partial_graph_id,
436            manager: self,
437            consumed: false,
438        }
439    }
440
441    pub(super) fn remove_partial_graphs(&mut self, partial_graphs: Vec<PartialGraphId>) {
442        for partial_graph_id in &partial_graphs {
443            let graph = self.graphs.remove(partial_graph_id).expect("should exist");
444            let PartialGraphStatus::Running { state, .. } = graph else {
445                panic!("graph to be explicitly removed should be running");
446            };
447            assert!(state.is_empty());
448        }
449        self.control_stream_manager
450            .remove_partial_graphs(partial_graphs);
451    }
452
453    pub(super) fn reset_partial_graphs(
454        &mut self,
455        partial_graph_ids: impl IntoIterator<Item = PartialGraphId>,
456    ) {
457        let partial_graph_ids = partial_graph_ids.into_iter().collect_vec();
458        let remaining_workers = self
459            .control_stream_manager
460            .reset_partial_graphs(partial_graph_ids.clone());
461        let new_collector = || ResetPartialGraphCollector {
462            remaining_workers: remaining_workers.clone(),
463            reset_resps: Default::default(),
464        };
465        for partial_graph_id in partial_graph_ids {
466            match self.graphs.entry(partial_graph_id) {
467                Entry::Vacant(entry) => {
468                    entry.insert(PartialGraphStatus::Resetting(new_collector()));
469                }
470                Entry::Occupied(mut entry) => {
471                    let graph = entry.get_mut();
472                    match graph {
473                        PartialGraphStatus::Resetting(_) => {
474                            unreachable!("should not reset again")
475                        }
476                        PartialGraphStatus::Running { .. }
477                        | PartialGraphStatus::Initializing { .. } => {
478                            *graph = PartialGraphStatus::Resetting(new_collector());
479                        }
480                    }
481                }
482            }
483        }
484    }
485
486    pub(super) fn assert_resetting(&self, partial_graph_id: PartialGraphId) {
487        let graph = self.graphs.get(&partial_graph_id).expect("should exist");
488        let PartialGraphStatus::Resetting(..) = graph else {
489            panic!("should be at resetting but at {:?}", graph);
490        };
491    }
492
493    pub(super) fn inject_barrier(
494        &mut self,
495        partial_graph_id: PartialGraphId,
496        mutation: Option<Mutation>,
497        node_actors: &HashMap<WorkerId, HashSet<ActorId>>,
498        table_ids_to_sync: impl Iterator<Item = TableId>,
499        nodes_to_sync_table: impl Iterator<Item = WorkerId>,
500        new_actors: Option<StreamJobActorsToCreate>,
501        info: PartialGraphBarrierInfo,
502    ) -> MetaResult<()> {
503        let graph = self
504            .graphs
505            .get_mut(&partial_graph_id)
506            .expect("should exist");
507        let node_to_collect = self.control_stream_manager.inject_barrier(
508            partial_graph_id,
509            mutation,
510            &info.barrier_info,
511            node_actors,
512            table_ids_to_sync,
513            nodes_to_sync_table,
514            new_actors,
515        )?;
516        let PartialGraphStatus::Running { state, .. } = graph else {
517            panic!("should not inject barrier on non-running status: {graph:?}")
518        };
519        state.enqueue(node_to_collect, info);
520        Ok(())
521    }
522
523    fn running_graph(&self, partial_graph_id: PartialGraphId) -> &PartialGraphRunningState {
524        let PartialGraphStatus::Running { state: graph, .. } = &self.graphs[&partial_graph_id]
525        else {
526            unreachable!("should be running")
527        };
528        graph
529    }
530
531    fn running_graph_mut(
532        &mut self,
533        partial_graph_id: PartialGraphId,
534    ) -> &mut PartialGraphRunningState {
535        let PartialGraphStatus::Running { state: graph, .. } = self
536            .graphs
537            .get_mut(&partial_graph_id)
538            .expect("should exist")
539        else {
540            unreachable!("should be running")
541        };
542        graph
543    }
544
545    pub(super) fn pending_barrier_num(&self, partial_graph_id: PartialGraphId) -> usize {
546        self.running_graph(partial_graph_id).pending_barrier_num()
547    }
548
549    pub(super) fn first_inflight_barrier(
550        &self,
551        partial_graph_id: PartialGraphId,
552    ) -> Option<EpochPair> {
553        self.running_graph(partial_graph_id)
554            .barrier_item_collector
555            .first_inflight_epoch()
556    }
557
558    pub(super) fn pending_barrier_infos(
559        &self,
560        partial_graph_id: PartialGraphId,
561    ) -> impl Iterator<Item = &BarrierInfo> {
562        self.running_graph(partial_graph_id)
563            .barrier_item_collector
564            .iter_infos()
565            .map(|info| &info.barrier_info)
566    }
567
568    pub(super) fn start_completing(
569        &mut self,
570        partial_graph_id: PartialGraphId,
571        epoch_end_bound: Bound<u64>,
572        mut on_non_checkpoint_epoch: impl FnMut(
573            EpochPair,
574            HashMap<WorkerId, BarrierCompleteResponse>,
575            PostCollectCommand,
576        ),
577    ) -> Option<(
578        u64,
579        HashMap<WorkerId, BarrierCompleteResponse>,
580        PartialGraphBarrierInfo,
581    )> {
582        let graph = self.running_graph_mut(partial_graph_id);
583        assert!(graph.completing_epoch.is_none());
584        let epoch_range: (Bound<u64>, Bound<u64>) = (Unbounded, epoch_end_bound);
585        while let Some((epoch, resps, info)) = graph
586            .barrier_item_collector
587            .take_collected_if(|epoch| epoch_range.contains(&epoch.prev))
588        {
589            if info.post_collect_command.should_checkpoint() {
590                assert!(info.barrier_info.kind.is_checkpoint());
591            } else if !info.barrier_info.kind.is_checkpoint() {
592                if let Some(notifier) = info.notifier {
593                    notifier.notify_collected();
594                }
595                on_non_checkpoint_epoch(epoch, resps, info.post_collect_command);
596                continue;
597            }
598            let prev_epoch = info.barrier_info.prev_epoch();
599            graph.completing_epoch = Some(prev_epoch);
600            return Some((prev_epoch, resps, info));
601        }
602        None
603    }
604
605    pub(super) fn ack_completed(&mut self, partial_graph_id: PartialGraphId, prev_epoch: u64) {
606        assert_eq!(
607            self.running_graph_mut(partial_graph_id)
608                .completing_epoch
609                .take(),
610            Some(prev_epoch)
611        );
612    }
613
614    pub(super) fn has_pending_checkpoint_barrier(&self, partial_graph_id: PartialGraphId) -> bool {
615        self.running_graph(partial_graph_id)
616            .barrier_item_collector
617            .iter_infos()
618            .any(|info| info.barrier_info.kind.is_checkpoint())
619    }
620
621    pub(super) fn start_recover(&mut self) -> PartialGraphRecoverer<'_> {
622        PartialGraphRecoverer {
623            added_partial_graphs: Default::default(),
624            manager: self,
625            consumed: false,
626        }
627    }
628}
629
630#[must_use]
631pub(super) struct PartialGraphRecoverer<'a> {
632    added_partial_graphs: HashSet<PartialGraphId>,
633    manager: &'a mut PartialGraphManager,
634    consumed: bool,
635}
636
637impl PartialGraphRecoverer<'_> {
638    pub(super) fn recover_graph(
639        &mut self,
640        partial_graph_id: PartialGraphId,
641        term_id: &str,
642        mutation: Mutation,
643        barrier_info: &BarrierInfo,
644        node_actors: &HashMap<WorkerId, HashSet<ActorId>>,
645        table_ids_to_sync: impl Iterator<Item = TableId>,
646        new_actors: StreamJobActorsToCreate,
647        stat: impl PartialGraphStat,
648    ) -> MetaResult<()> {
649        assert!(
650            self.added_partial_graphs.insert(partial_graph_id),
651            "duplicated recover graph {partial_graph_id}"
652        );
653        self.manager
654            .control_stream_manager
655            .add_partial_graph(partial_graph_id, term_id);
656        assert!(barrier_info.kind.is_initial());
657        let node_to_collect = self.manager.control_stream_manager.inject_barrier(
658            partial_graph_id,
659            Some(mutation),
660            barrier_info,
661            node_actors,
662            table_ids_to_sync,
663            node_actors.keys().copied(),
664            Some(new_actors),
665        )?;
666        self.manager
667            .graphs
668            .try_insert(
669                partial_graph_id,
670                PartialGraphStatus::Initializing {
671                    term_id: term_id.to_owned(),
672                    epoch: barrier_info.epoch(),
673                    node_to_collect,
674                    stat: Some(Box::new(stat)),
675                },
676            )
677            .expect("non-duplicated");
678        Ok(())
679    }
680
681    pub(super) fn control_stream_manager(&self) -> &ControlStreamManager {
682        &self.manager.control_stream_manager
683    }
684
685    pub(super) fn all_initializing(mut self) -> HashSet<PartialGraphId> {
686        self.consumed = true;
687        take(&mut self.added_partial_graphs)
688    }
689
690    pub(super) fn failed(mut self) -> HashSet<PartialGraphId> {
691        self.manager
692            .reset_partial_graphs(self.added_partial_graphs.iter().copied());
693        self.consumed = true;
694        take(&mut self.added_partial_graphs)
695    }
696}
697
698impl Drop for PartialGraphRecoverer<'_> {
699    fn drop(&mut self) {
700        debug_assert!(self.consumed, "unconsumed graph recoverer");
701        if !self.consumed {
702            warn!(partial_graph_ids = ?self.added_partial_graphs, "unconsumed graph recoverer");
703        }
704    }
705}
706
707#[must_use]
708pub(super) enum PartialGraphManagerEvent<'a> {
709    PartialGraph(PartialGraphId, PartialGraphEvent<'a>),
710    Worker(WorkerId, WorkerEvent),
711}
712
713impl PartialGraphManager {
714    pub(super) async fn next_event<'a>(
715        &'a mut self,
716        context: &Arc<impl GlobalBarrierWorkerContext>,
717    ) -> PartialGraphManagerEvent<'a> {
718        let temp_ref = CollectedBarrierTempRef::new();
719        let event = self.next_event_inner(context, &temp_ref).await;
720        temp_ref.correct_lifetime(event, self)
721    }
722
723    async fn next_event_inner<'a>(
724        &mut self,
725        context: &Arc<impl GlobalBarrierWorkerContext>,
726        temp_ref: &'a CollectedBarrierTempRef,
727    ) -> PartialGraphManagerEvent<'a> {
728        for (&partial_graph_id, graph) in &mut self.graphs {
729            match graph {
730                PartialGraphStatus::Running { state, .. } => {
731                    if let Some(collected) = state.barrier_collected(temp_ref) {
732                        return PartialGraphManagerEvent::PartialGraph(
733                            partial_graph_id,
734                            PartialGraphEvent::BarrierCollected(collected),
735                        );
736                    }
737                }
738                PartialGraphStatus::Resetting(collector) => {
739                    if collector.remaining_workers.is_empty() {
740                        let resps = take(&mut collector.reset_resps);
741                        self.graphs.remove(&partial_graph_id);
742                        return PartialGraphManagerEvent::PartialGraph(
743                            partial_graph_id,
744                            PartialGraphEvent::Reset(resps),
745                        );
746                    }
747                }
748                PartialGraphStatus::Initializing {
749                    term_id,
750                    node_to_collect,
751                    stat,
752                    ..
753                } => {
754                    if node_to_collect.is_empty() {
755                        *graph = PartialGraphStatus::Running {
756                            term_id: take(term_id),
757                            state: PartialGraphRunningState::new(
758                                stat.take().expect("should be taken once"),
759                            ),
760                        };
761                        return PartialGraphManagerEvent::PartialGraph(
762                            partial_graph_id,
763                            PartialGraphEvent::Initialized,
764                        );
765                    }
766                }
767            }
768        }
769        loop {
770            let (worker_id, event) = self.control_stream_manager.next_event(context).await;
771            match event {
772                WorkerNodeEvent::Response(result) => match result {
773                    Ok(resp) => match resp {
774                        Response::CompleteBarrier(resp) => {
775                            let partial_graph_id = resp.partial_graph_id;
776                            if let Some(event) = self
777                                .graphs
778                                .get_mut(&partial_graph_id)
779                                .expect("should exist")
780                                .collect(worker_id, resp, temp_ref)
781                            {
782                                return PartialGraphManagerEvent::PartialGraph(
783                                    partial_graph_id,
784                                    event,
785                                );
786                            }
787                        }
788                        Response::ReportPartialGraphFailure(resp) => {
789                            let partial_graph_id = resp.partial_graph_id;
790                            let graph = self
791                                .graphs
792                                .get_mut(&partial_graph_id)
793                                .expect("should exist");
794                            match graph {
795                                PartialGraphStatus::Resetting(_) => {
796                                    // ignore reported error when resetting
797                                }
798                                PartialGraphStatus::Running { .. }
799                                | PartialGraphStatus::Initializing { .. } => {
800                                    return PartialGraphManagerEvent::PartialGraph(
801                                        partial_graph_id,
802                                        PartialGraphEvent::Error(worker_id),
803                                    );
804                                }
805                            }
806                        }
807                        Response::ResetPartialGraph(resp) => {
808                            let partial_graph_id = resp.partial_graph_id;
809                            let graph = self
810                                .graphs
811                                .get_mut(&partial_graph_id)
812                                .expect("should exist");
813                            match graph {
814                                PartialGraphStatus::Running { .. }
815                                | PartialGraphStatus::Initializing { .. } => {
816                                    if cfg!(debug_assertions) {
817                                        unreachable!(
818                                            "should not have reset request when not in resetting state"
819                                        )
820                                    } else {
821                                        warn!(
822                                            ?resp,
823                                            "ignore reset resp when not in Resetting state"
824                                        );
825                                    }
826                                }
827                                PartialGraphStatus::Resetting(collector) => {
828                                    if collector.collect(worker_id, resp) {
829                                        let resps = take(&mut collector.reset_resps);
830                                        self.graphs.remove(&partial_graph_id);
831                                        return PartialGraphManagerEvent::PartialGraph(
832                                            partial_graph_id,
833                                            PartialGraphEvent::Reset(resps),
834                                        );
835                                    }
836                                }
837                            }
838                        }
839                        Response::Init(_) | Response::Shutdown(_) => {
840                            unreachable!("should be handled in control stream manager")
841                        }
842                    },
843                    Err(error) => {
844                        let affected_partial_graphs = self
845                            .graphs
846                            .iter_mut()
847                            .filter_map(|(partial_graph_id, graph)| match graph {
848                                PartialGraphStatus::Running { state, .. } => state
849                                    .barrier_item_collector
850                                    .iter_to_collect()
851                                    .any(|to_collect| {
852                                        !is_valid_after_worker_err(to_collect, worker_id)
853                                    })
854                                    .then_some(*partial_graph_id),
855                                PartialGraphStatus::Resetting(collector) => {
856                                    collector.remaining_workers.remove(&worker_id);
857                                    None
858                                }
859                                PartialGraphStatus::Initializing {
860                                    node_to_collect, ..
861                                } => (!is_valid_after_worker_err(node_to_collect, worker_id))
862                                    .then_some(*partial_graph_id),
863                            })
864                            .collect();
865                        return PartialGraphManagerEvent::Worker(
866                            worker_id,
867                            WorkerEvent::WorkerError {
868                                err: error,
869                                affected_partial_graphs,
870                            },
871                        );
872                    }
873                },
874                WorkerNodeEvent::Connected(connected) => {
875                    connected.initialize(existing_graphs(&self.graphs));
876                    return PartialGraphManagerEvent::Worker(
877                        worker_id,
878                        WorkerEvent::WorkerConnected,
879                    );
880                }
881            }
882        }
883    }
884}
885
886#[cfg(test)]
887mod tests {
888    use risingwave_common::util::epoch::Epoch;
889
890    use super::*;
891    use crate::barrier::TracedEpoch;
892
893    struct TestPartialGraphStat;
894
895    impl PartialGraphStat for TestPartialGraphStat {
896        fn observe_barrier_latency(&self, _epoch: EpochPair, _barrier_latency_secs: f64) {}
897
898        fn observe_barrier_num(&self, _inflight_barrier_num: usize, _collected_barrier_num: usize) {
899        }
900    }
901
902    fn barrier_info(prev_epoch: u64, curr_epoch: u64) -> PartialGraphBarrierInfo {
903        PartialGraphBarrierInfo::new(
904            PostCollectCommand::barrier(),
905            BarrierInfo {
906                prev_epoch: TracedEpoch::new(Epoch(prev_epoch)),
907                curr_epoch: TracedEpoch::new(Epoch(curr_epoch)),
908                kind: BarrierKind::Barrier,
909            },
910            None,
911            HashSet::new(),
912        )
913    }
914
915    #[test]
916    fn test_pending_barrier_num_includes_collected_and_completing() {
917        let mut state = PartialGraphRunningState::new(Box::new(TestPartialGraphStat));
918        let worker_id: WorkerId = 1.into();
919        state.barrier_item_collector.enqueue(
920            EpochPair::new(2, 1),
921            HashSet::from([worker_id]),
922            barrier_info(1, 2),
923        );
924        state.barrier_item_collector.enqueue(
925            EpochPair::new(3, 2),
926            HashSet::from([worker_id]),
927            barrier_info(2, 3),
928        );
929        assert_eq!(state.pending_barrier_num(), 2);
930
931        state
932            .barrier_item_collector
933            .collect(1, worker_id, BarrierCompleteResponse::default());
934        state.barrier_item_collector.barrier_collected();
935        assert_eq!(state.pending_barrier_num(), 2);
936
937        state
938            .barrier_item_collector
939            .take_collected_if(|epoch| epoch.prev == 1)
940            .expect("the first barrier should be collected");
941        state.completing_epoch = Some(1);
942        assert_eq!(state.pending_barrier_num(), 2);
943    }
944}