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