Skip to main content

risingwave_stream/task/barrier_worker/
managed_state.rs

1// Copyright 2025 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::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque};
16use std::fmt::{Debug, Display, Formatter};
17use std::future::{Future, pending, poll_fn};
18use std::mem::replace;
19use std::sync::Arc;
20use std::task::{Context, Poll};
21use std::time::{Duration, Instant};
22
23use anyhow::anyhow;
24use futures::future::BoxFuture;
25use futures::stream::{FuturesOrdered, FuturesUnordered};
26use futures::{FutureExt, StreamExt};
27use prometheus::HistogramTimer;
28use risingwave_common::catalog::TableId;
29use risingwave_common::id::{SinkId, SourceId};
30use risingwave_common::metrics::{LabelGuardedHistogram, LabelGuardedIntCounter};
31use risingwave_common::util::epoch::EpochPair;
32use risingwave_pb::connector_service::SinkMetadata;
33use risingwave_pb::stream_plan::barrier::BarrierKind;
34use risingwave_pb::stream_service::barrier_complete_response::{
35    IcebergPkIndexSinkMetadata as PbIcebergPkIndexSinkMetadata, PbCdcSourceOffsetUpdated,
36    PbCdcTableBackfillProgress, PbCreateMviewProgress, PbListFinishedSource, PbLoadFinishedSource,
37};
38use risingwave_storage::StateStoreImpl;
39use tokio::sync::mpsc;
40use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender, unbounded_channel};
41use tokio::task::JoinHandle;
42
43use crate::error::{StreamError, StreamResult};
44use crate::executor::Barrier;
45use crate::executor::monitor::StreamingMetrics;
46use crate::task::progress::BackfillState;
47use crate::task::{
48    ActorId, LocalBarrierEvent, LocalBarrierManager, NewOutputRequest, PartialGraphId,
49    StreamActorManager, UpDownActorIds,
50};
51
52struct IssuedState {
53    /// Actor ids remaining to be collected.
54    pub remaining_actors: BTreeSet<ActorId>,
55
56    pub barrier_inflight_latency: HistogramTimer,
57}
58
59impl Debug for IssuedState {
60    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
61        f.debug_struct("IssuedState")
62            .field("remaining_actors", &self.remaining_actors)
63            .finish()
64    }
65}
66
67/// The state machine of local barrier manager.
68#[derive(Debug)]
69enum ManagedBarrierStateInner {
70    /// Meta service has issued a `send_barrier` request. We're collecting barriers now.
71    Issued(IssuedState),
72
73    /// The barrier has been collected by all remaining actors
74    AllCollected {
75        create_mview_progress: Vec<PbCreateMviewProgress>,
76        list_finished_source_ids: Vec<PbListFinishedSource>,
77        load_finished_source_ids: Vec<PbLoadFinishedSource>,
78        cdc_table_backfill_progress: Vec<PbCdcTableBackfillProgress>,
79        cdc_source_offset_updated: Vec<PbCdcSourceOffsetUpdated>,
80        iceberg_pk_index_sink_metadata: Vec<PbIcebergPkIndexSinkMetadata>,
81        truncate_tables: Vec<TableId>,
82        refresh_finished_tables: Vec<TableId>,
83    },
84}
85
86#[derive(Debug)]
87struct BarrierState {
88    barrier: Barrier,
89    /// Only be `Some(_)` when `barrier.kind` is `Checkpoint`
90    table_ids: Option<HashSet<TableId>>,
91    inner: ManagedBarrierStateInner,
92}
93
94use risingwave_common::must_match;
95use risingwave_pb::id::FragmentId;
96use risingwave_pb::stream_service::{InjectBarrierRequest, PbIcebergPkIndexSinkRole};
97
98use crate::executor::exchange::permit;
99use crate::task::barrier_worker::await_epoch_completed_future::AwaitEpochCompletedFuture;
100use crate::task::barrier_worker::{ScoredStreamError, TakeReceiverRequest};
101use crate::task::cdc_progress::CdcTableBackfillState;
102
103pub(super) struct ManagedBarrierStateDebugInfo<'a> {
104    running_actors: BTreeSet<ActorId>,
105    graph_state: &'a PartialGraphManagedBarrierState,
106}
107
108impl Display for ManagedBarrierStateDebugInfo<'_> {
109    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
110        write!(f, "running_actors: ")?;
111        for actor_id in &self.running_actors {
112            write!(f, "{}, ", actor_id)?;
113        }
114        {
115            writeln!(f, "graph states")?;
116            write!(f, "{}", self.graph_state)?;
117        }
118        Ok(())
119    }
120}
121
122impl Display for &'_ PartialGraphManagedBarrierState {
123    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
124        let mut prev_epoch = 0u64;
125        for (epoch, barrier_state) in &self.epoch_barrier_state_map {
126            write!(f, "> Epoch {}: ", epoch)?;
127            match &barrier_state.inner {
128                ManagedBarrierStateInner::Issued(state) => {
129                    write!(
130                        f,
131                        "Issued [{:?}]. Remaining actors: [",
132                        barrier_state.barrier.kind
133                    )?;
134                    let mut is_prev_epoch_issued = false;
135                    if prev_epoch != 0 {
136                        let bs = &self.epoch_barrier_state_map[&prev_epoch];
137                        if let ManagedBarrierStateInner::Issued(IssuedState {
138                            remaining_actors: remaining_actors_prev,
139                            ..
140                        }) = &bs.inner
141                        {
142                            // Only show the actors that are not in the previous epoch.
143                            is_prev_epoch_issued = true;
144                            let mut duplicates = 0usize;
145                            for actor_id in &state.remaining_actors {
146                                if !remaining_actors_prev.contains(actor_id) {
147                                    write!(f, "{}, ", actor_id)?;
148                                } else {
149                                    duplicates += 1;
150                                }
151                            }
152                            if duplicates > 0 {
153                                write!(f, "...and {} actors in prev epoch", duplicates)?;
154                            }
155                        }
156                    }
157                    if !is_prev_epoch_issued {
158                        for actor_id in &state.remaining_actors {
159                            write!(f, "{}, ", actor_id)?;
160                        }
161                    }
162                    write!(f, "]")?;
163                }
164                ManagedBarrierStateInner::AllCollected { .. } => {
165                    write!(f, "AllCollected")?;
166                }
167            }
168            prev_epoch = *epoch;
169            writeln!(f)?;
170        }
171
172        if !self.create_mview_progress.is_empty() {
173            writeln!(f, "Create MView Progress:")?;
174            for (epoch, progress) in &self.create_mview_progress {
175                write!(f, "> Epoch {}:", epoch)?;
176                for (actor_id, (_, state)) in progress {
177                    write!(f, ">> Actor {}: {}, ", actor_id, state)?;
178                }
179            }
180        }
181
182        Ok(())
183    }
184}
185
186enum InflightActorStatus {
187    /// The actor has been issued some barriers, but has not collected the first barrier
188    IssuedFirst(Vec<Barrier>),
189    /// The actor has been issued some barriers, and has collected the first barrier
190    Running(u64),
191}
192
193impl InflightActorStatus {
194    fn max_issued_epoch(&self) -> u64 {
195        match self {
196            InflightActorStatus::Running(epoch) => *epoch,
197            InflightActorStatus::IssuedFirst(issued_barriers) => {
198                issued_barriers.last().expect("non-empty").epoch.prev
199            }
200        }
201    }
202}
203
204pub(crate) struct InflightActorState {
205    actor_id: ActorId,
206    barrier_senders: Vec<mpsc::UnboundedSender<Barrier>>,
207    /// `prev_epoch`. `push_back` and `pop_front`
208    pub(in crate::task) inflight_barriers: VecDeque<u64>,
209    status: InflightActorStatus,
210    /// Whether the actor has been issued a stop barrier
211    is_stopping: bool,
212
213    new_output_request_tx: UnboundedSender<(ActorId, NewOutputRequest)>,
214    join_handle: JoinHandle<()>,
215    monitor_task_handle: Option<JoinHandle<()>>,
216}
217
218impl InflightActorState {
219    pub(super) fn start(
220        actor_id: ActorId,
221        initial_barrier: &Barrier,
222        new_output_request_tx: UnboundedSender<(ActorId, NewOutputRequest)>,
223        join_handle: JoinHandle<()>,
224        monitor_task_handle: Option<JoinHandle<()>>,
225    ) -> Self {
226        Self {
227            actor_id,
228            barrier_senders: vec![],
229            inflight_barriers: VecDeque::from_iter([initial_barrier.epoch.prev]),
230            status: InflightActorStatus::IssuedFirst(vec![initial_barrier.clone()]),
231            is_stopping: false,
232            new_output_request_tx,
233            join_handle,
234            monitor_task_handle,
235        }
236    }
237
238    pub(super) fn issue_barrier(&mut self, barrier: &Barrier, is_stop: bool) -> StreamResult<()> {
239        assert!(barrier.epoch.prev > self.status.max_issued_epoch());
240
241        for barrier_sender in &self.barrier_senders {
242            barrier_sender.send(barrier.clone()).map_err(|_| {
243                StreamError::barrier_send(
244                    barrier.clone(),
245                    self.actor_id,
246                    "failed to send to registered sender",
247                )
248            })?;
249        }
250
251        if let Some(prev_epoch) = self.inflight_barriers.back() {
252            assert!(*prev_epoch < barrier.epoch.prev);
253        }
254        self.inflight_barriers.push_back(barrier.epoch.prev);
255
256        match &mut self.status {
257            InflightActorStatus::IssuedFirst(pending_barriers) => {
258                pending_barriers.push(barrier.clone());
259            }
260            InflightActorStatus::Running(prev_epoch) => {
261                *prev_epoch = barrier.epoch.prev;
262            }
263        };
264
265        if is_stop {
266            assert!(!self.is_stopping, "stopped actor should not issue barrier");
267            self.is_stopping = true;
268        }
269        Ok(())
270    }
271
272    pub(super) fn collect(&mut self, epoch: EpochPair) -> bool {
273        let prev_epoch = self.inflight_barriers.pop_front().expect("should exist");
274        assert_eq!(prev_epoch, epoch.prev);
275        match &self.status {
276            InflightActorStatus::IssuedFirst(pending_barriers) => {
277                assert_eq!(
278                    prev_epoch,
279                    pending_barriers.first().expect("non-empty").epoch.prev
280                );
281                self.status = InflightActorStatus::Running(
282                    pending_barriers.last().expect("non-empty").epoch.prev,
283                );
284            }
285            InflightActorStatus::Running(_) => {}
286        }
287
288        self.inflight_barriers.is_empty() && self.is_stopping
289    }
290}
291
292/// Part of [`PartialGraphState`]
293pub(crate) struct PartialGraphManagedBarrierState {
294    /// Record barrier state for each epoch of concurrent checkpoints.
295    ///
296    /// The key is `prev_epoch`, and the first value is `curr_epoch`
297    epoch_barrier_state_map: BTreeMap<u64, BarrierState>,
298
299    prev_barrier_table_ids: Option<(EpochPair, HashSet<TableId>)>,
300
301    /// Record the progress updates of creating mviews for each epoch of concurrent checkpoints.
302    ///
303    /// The process of progress reporting is as follows:
304    /// 1. updated by [`crate::task::barrier_manager::CreateMviewProgressReporter::update`]
305    /// 2. converted to [`ManagedBarrierStateInner`] in [`Self::may_have_collected_all`]
306    /// 3. handled by [`Self::pop_barrier_to_complete`]
307    /// 4. put in [`crate::task::barrier_worker::BarrierCompleteResult`] and reported to meta.
308    pub(crate) create_mview_progress: HashMap<u64, HashMap<ActorId, (FragmentId, BackfillState)>>,
309
310    /// Record the source list finished reports for each epoch of concurrent checkpoints.
311    /// Used for refreshable batch source. The map key is epoch and the value is
312    /// a list of pb messages reported by actors.
313    pub(crate) list_finished_source_ids: HashMap<u64, Vec<PbListFinishedSource>>,
314
315    /// Record the source load finished reports for each epoch of concurrent checkpoints.
316    /// Used for refreshable batch source. The map key is epoch and the value is
317    /// a list of pb messages reported by actors.
318    pub(crate) load_finished_source_ids: HashMap<u64, Vec<PbLoadFinishedSource>>,
319
320    pub(crate) cdc_table_backfill_progress: HashMap<u64, HashMap<ActorId, CdcTableBackfillState>>,
321
322    /// Record CDC source offset updated reports for each epoch of concurrent checkpoints.
323    /// Used to track when CDC sources have updated their offset at least once.
324    pub(crate) cdc_source_offset_updated: HashMap<u64, Vec<PbCdcSourceOffsetUpdated>>,
325
326    /// Record Iceberg pk-index sink metadata reports per epoch for concurrent checkpoints.
327    pub(crate) iceberg_pk_index_sink_metadata: HashMap<u64, Vec<PbIcebergPkIndexSinkMetadata>>,
328
329    /// Record the tables to truncate for each epoch of concurrent checkpoints.
330    pub(crate) truncate_tables: HashMap<u64, HashSet<TableId>>,
331    /// Record the tables that have finished refresh for each epoch of concurrent checkpoints.
332    /// Used for materialized view refresh completion reporting.
333    pub(crate) refresh_finished_tables: HashMap<u64, HashSet<TableId>>,
334
335    state_store: StateStoreImpl,
336
337    barrier_inflight_latency: LabelGuardedHistogram,
338    barrier_sync_latency: LabelGuardedHistogram,
339    barrier_manager_progress: LabelGuardedIntCounter,
340}
341
342impl PartialGraphManagedBarrierState {
343    pub(super) fn new(
344        actor_manager: &StreamActorManager,
345        partial_graph_id: PartialGraphId,
346    ) -> Self {
347        Self::new_inner(
348            actor_manager.env.state_store(),
349            actor_manager.streaming_metrics.clone(),
350            partial_graph_id,
351        )
352    }
353
354    fn new_inner(
355        state_store: StateStoreImpl,
356        streaming_metrics: Arc<StreamingMetrics>,
357        partial_graph_id: PartialGraphId,
358    ) -> Self {
359        fn partial_graph_name(partial_graph_id: PartialGraphId) -> String {
360            // HACK: The partial graph ID encoding is owned by meta and intentionally not exposed to
361            // compute nodes. Decode it locally only for the human-readable metrics label.
362            let raw_partial_graph_id = partial_graph_id.as_raw_id();
363            let database_id = raw_partial_graph_id >> 32;
364            let raw_job_id = raw_partial_graph_id as u32;
365            if raw_job_id == u32::MAX {
366                format!("database {database_id}")
367            } else {
368                format!("database {database_id} job {raw_job_id}")
369            }
370        }
371
372        let partial_graph_name = partial_graph_name(partial_graph_id);
373        let barrier_inflight_latency = streaming_metrics
374            .barrier_inflight_latency
375            .with_guarded_label_values(&[&partial_graph_name]);
376        let barrier_sync_latency = streaming_metrics
377            .barrier_sync_latency
378            .with_guarded_label_values(&[&partial_graph_name]);
379        let barrier_manager_progress = streaming_metrics
380            .barrier_manager_progress
381            .with_guarded_label_values(&[&partial_graph_name]);
382        Self {
383            epoch_barrier_state_map: Default::default(),
384            prev_barrier_table_ids: None,
385            create_mview_progress: Default::default(),
386            list_finished_source_ids: Default::default(),
387            load_finished_source_ids: Default::default(),
388            cdc_table_backfill_progress: Default::default(),
389            cdc_source_offset_updated: Default::default(),
390            iceberg_pk_index_sink_metadata: Default::default(),
391            truncate_tables: Default::default(),
392            refresh_finished_tables: Default::default(),
393            state_store,
394            barrier_inflight_latency,
395            barrier_sync_latency,
396            barrier_manager_progress,
397        }
398    }
399
400    #[cfg(test)]
401    pub(crate) fn for_test() -> Self {
402        Self::new_inner(
403            StateStoreImpl::for_test(),
404            Arc::new(StreamingMetrics::unused()),
405            PartialGraphId::new(0),
406        )
407    }
408
409    pub(super) fn is_empty(&self) -> bool {
410        self.epoch_barrier_state_map.is_empty()
411    }
412}
413
414pub(crate) struct SuspendedPartialGraphState {
415    pub(super) suspend_time: Instant,
416    inner: PartialGraphState,
417    failure: Option<(Option<ActorId>, StreamError)>,
418}
419
420impl SuspendedPartialGraphState {
421    fn new(
422        state: PartialGraphState,
423        failure: Option<(Option<ActorId>, StreamError)>,
424        _completing_futures: Option<FuturesOrdered<AwaitEpochCompletedFuture>>, /* discard the completing futures */
425    ) -> Self {
426        Self {
427            suspend_time: Instant::now(),
428            inner: state,
429            failure,
430        }
431    }
432
433    async fn reset(mut self) -> ResetPartialGraphOutput {
434        let root_err = self.inner.try_find_root_actor_failure(self.failure).await;
435        self.inner.abort_and_wait_actors().await;
436        ResetPartialGraphOutput { root_err }
437    }
438}
439
440pub(crate) struct ResetPartialGraphOutput {
441    pub(crate) root_err: Option<ScoredStreamError>,
442}
443
444pub(in crate::task) enum PartialGraphStatus {
445    ReceivedExchangeRequest(Vec<(String, UpDownActorIds, TakeReceiverRequest)>),
446    Running(PartialGraphState),
447    Suspended(SuspendedPartialGraphState),
448    Resetting,
449    /// temporary place holder
450    Unspecified,
451}
452
453impl PartialGraphStatus {
454    pub(crate) async fn abort(&mut self) {
455        match self {
456            PartialGraphStatus::ReceivedExchangeRequest(pending_requests) => {
457                for (_, _, request) in pending_requests.drain(..) {
458                    if let TakeReceiverRequest::Remote { result_sender, .. } = request {
459                        let _ = result_sender.send(Err(anyhow!("partial graph aborted").into()));
460                    }
461                }
462            }
463            PartialGraphStatus::Running(state) => {
464                state.abort_and_wait_actors().await;
465            }
466            PartialGraphStatus::Suspended(SuspendedPartialGraphState { inner: state, .. }) => {
467                state.abort_and_wait_actors().await;
468            }
469            PartialGraphStatus::Resetting => {}
470            PartialGraphStatus::Unspecified => {
471                unreachable!()
472            }
473        }
474    }
475
476    pub(crate) fn state_for_request(&mut self) -> Option<&mut PartialGraphState> {
477        match self {
478            PartialGraphStatus::ReceivedExchangeRequest(_) => {
479                unreachable!("should not handle request")
480            }
481            PartialGraphStatus::Running(state) => Some(state),
482            PartialGraphStatus::Suspended(_) => None,
483            PartialGraphStatus::Resetting => {
484                unreachable!("should not receive further request during cleaning")
485            }
486            PartialGraphStatus::Unspecified => {
487                unreachable!()
488            }
489        }
490    }
491
492    pub(super) fn poll_next_event(
493        &mut self,
494        cx: &mut Context<'_>,
495    ) -> Poll<ManagedBarrierStateEvent> {
496        match self {
497            PartialGraphStatus::ReceivedExchangeRequest(_) => Poll::Pending,
498            PartialGraphStatus::Running(state) => state.poll_next_event(cx),
499            PartialGraphStatus::Suspended(_) | PartialGraphStatus::Resetting => Poll::Pending,
500            PartialGraphStatus::Unspecified => {
501                unreachable!()
502            }
503        }
504    }
505
506    pub(super) fn suspend(
507        &mut self,
508        failed_actor: Option<ActorId>,
509        err: StreamError,
510        completing_futures: Option<FuturesOrdered<AwaitEpochCompletedFuture>>,
511    ) {
512        let state = must_match!(replace(self, PartialGraphStatus::Unspecified), PartialGraphStatus::Running(state) => state);
513        *self = PartialGraphStatus::Suspended(SuspendedPartialGraphState::new(
514            state,
515            Some((failed_actor, err)),
516            completing_futures,
517        ));
518    }
519
520    pub(super) fn start_reset(
521        &mut self,
522        partial_graph_id: PartialGraphId,
523        completing_futures: Option<FuturesOrdered<AwaitEpochCompletedFuture>>,
524        table_ids_to_clear: &mut HashSet<TableId>,
525    ) -> BoxFuture<'static, ResetPartialGraphOutput> {
526        match replace(self, PartialGraphStatus::Resetting) {
527            PartialGraphStatus::ReceivedExchangeRequest(pending_requests) => {
528                for (_, _, request) in pending_requests {
529                    if let TakeReceiverRequest::Remote { result_sender, .. } = request {
530                        let _ = result_sender.send(Err(anyhow!("partial graph reset").into()));
531                    }
532                }
533                async move { ResetPartialGraphOutput { root_err: None } }.boxed()
534            }
535            PartialGraphStatus::Running(state) => {
536                assert_eq!(partial_graph_id, state.partial_graph_id);
537                info!(
538                    %partial_graph_id,
539                    "start partial graph reset from Running"
540                );
541                table_ids_to_clear.extend(state.table_ids.iter().copied());
542                SuspendedPartialGraphState::new(state, None, completing_futures)
543                    .reset()
544                    .boxed()
545            }
546            PartialGraphStatus::Suspended(state) => {
547                assert!(
548                    completing_futures.is_none(),
549                    "should have been clear when suspended"
550                );
551                assert_eq!(partial_graph_id, state.inner.partial_graph_id);
552                info!(
553                    %partial_graph_id,
554                    suspend_elapsed = ?state.suspend_time.elapsed(),
555                    "start partial graph reset after suspended"
556                );
557                table_ids_to_clear.extend(state.inner.table_ids.iter().copied());
558                state.reset().boxed()
559            }
560            PartialGraphStatus::Resetting => {
561                unreachable!("should not reset for twice");
562            }
563            PartialGraphStatus::Unspecified => {
564                unreachable!()
565            }
566        }
567    }
568}
569
570#[derive(Default)]
571pub(in crate::task) struct ManagedBarrierState {
572    pub(super) partial_graphs: HashMap<PartialGraphId, PartialGraphStatus>,
573    pub(super) resetting_graphs:
574        FuturesUnordered<JoinHandle<Vec<(PartialGraphId, ResetPartialGraphOutput)>>>,
575}
576
577pub(super) enum ManagedBarrierStateEvent {
578    BarrierCollected {
579        partial_graph_id: PartialGraphId,
580        barrier: Barrier,
581    },
582    ActorError {
583        partial_graph_id: PartialGraphId,
584        actor_id: ActorId,
585        err: StreamError,
586    },
587    PartialGraphsReset(Vec<(PartialGraphId, ResetPartialGraphOutput)>),
588    RegisterLocalUpstreamOutput {
589        actor_id: ActorId,
590        upstream_actor_id: ActorId,
591        upstream_partial_graph_id: PartialGraphId,
592        term_id: String,
593        tx: permit::Sender,
594    },
595}
596
597impl ManagedBarrierState {
598    pub(super) fn next_event(&mut self) -> impl Future<Output = ManagedBarrierStateEvent> + '_ {
599        poll_fn(|cx| {
600            for graph in self.partial_graphs.values_mut() {
601                if let Poll::Ready(event) = graph.poll_next_event(cx) {
602                    return Poll::Ready(event);
603                }
604            }
605            if let Poll::Ready(Some(result)) = self.resetting_graphs.poll_next_unpin(cx) {
606                let outputs = result.expect("failed to join resetting future");
607                for (partial_graph_id, _) in &outputs {
608                    let PartialGraphStatus::Resetting = self
609                        .partial_graphs
610                        .remove(partial_graph_id)
611                        .expect("should exist")
612                    else {
613                        panic!("should be resetting")
614                    };
615                }
616                return Poll::Ready(ManagedBarrierStateEvent::PartialGraphsReset(outputs));
617            }
618            Poll::Pending
619        })
620    }
621}
622
623/// Per-partial-graph barrier state manager. Handles barriers for one specific partial graph.
624/// Part of [`ManagedBarrierState`] in [`super::LocalBarrierWorker`].
625///
626/// See [`crate::task`] for architecture overview.
627pub(crate) struct PartialGraphState {
628    partial_graph_id: PartialGraphId,
629    pub(crate) actor_states: HashMap<ActorId, InflightActorState>,
630    pub(super) actor_pending_new_output_requests:
631        HashMap<ActorId, Vec<(ActorId, NewOutputRequest)>>,
632
633    pub(crate) graph_state: PartialGraphManagedBarrierState,
634
635    table_ids: HashSet<TableId>,
636
637    actor_manager: Arc<StreamActorManager>,
638
639    pub(super) local_barrier_manager: LocalBarrierManager,
640
641    barrier_event_rx: UnboundedReceiver<LocalBarrierEvent>,
642    pub(super) actor_failure_rx: UnboundedReceiver<(ActorId, StreamError)>,
643}
644
645impl PartialGraphState {
646    /// Create a barrier manager state. This will be called only once.
647    pub(super) fn new(
648        partial_graph_id: PartialGraphId,
649        term_id: String,
650        actor_manager: Arc<StreamActorManager>,
651    ) -> Self {
652        let (local_barrier_manager, barrier_event_rx, actor_failure_rx) =
653            LocalBarrierManager::new(term_id, actor_manager.env.clone());
654        Self {
655            partial_graph_id,
656            actor_states: Default::default(),
657            actor_pending_new_output_requests: Default::default(),
658            graph_state: PartialGraphManagedBarrierState::new(&actor_manager, partial_graph_id),
659            table_ids: Default::default(),
660            actor_manager,
661            local_barrier_manager,
662            barrier_event_rx,
663            actor_failure_rx,
664        }
665    }
666
667    pub(super) fn to_debug_info(&self) -> ManagedBarrierStateDebugInfo<'_> {
668        ManagedBarrierStateDebugInfo {
669            running_actors: self.actor_states.keys().cloned().collect(),
670            graph_state: &self.graph_state,
671        }
672    }
673
674    async fn abort_and_wait_actors(&mut self) {
675        for (actor_id, state) in &self.actor_states {
676            tracing::debug!("force stopping actor {}", actor_id);
677            state.join_handle.abort();
678            if let Some(monitor_task_handle) = &state.monitor_task_handle {
679                monitor_task_handle.abort();
680            }
681        }
682
683        for (actor_id, state) in self.actor_states.drain() {
684            tracing::debug!("join actor {}", actor_id);
685            let result = state.join_handle.await;
686            assert!(result.is_ok() || result.unwrap_err().is_cancelled());
687        }
688    }
689}
690
691impl InflightActorState {
692    pub(super) fn register_barrier_sender(
693        &mut self,
694        tx: mpsc::UnboundedSender<Barrier>,
695    ) -> StreamResult<()> {
696        match &self.status {
697            InflightActorStatus::IssuedFirst(pending_barriers) => {
698                for barrier in pending_barriers {
699                    tx.send(barrier.clone()).map_err(|_| {
700                        StreamError::barrier_send(
701                            barrier.clone(),
702                            self.actor_id,
703                            "failed to send pending barriers to newly registered sender",
704                        )
705                    })?;
706                }
707                self.barrier_senders.push(tx);
708            }
709            InflightActorStatus::Running(_) => {
710                unreachable!("should not register barrier sender when entering Running status")
711            }
712        }
713        Ok(())
714    }
715}
716
717impl PartialGraphState {
718    pub(super) fn register_barrier_sender(
719        &mut self,
720        actor_id: ActorId,
721        tx: mpsc::UnboundedSender<Barrier>,
722    ) -> StreamResult<()> {
723        self.actor_states
724            .get_mut(&actor_id)
725            .expect("should exist")
726            .register_barrier_sender(tx)
727    }
728}
729
730impl PartialGraphState {
731    pub(super) fn transform_to_issued(
732        &mut self,
733        barrier: &Barrier,
734        request: InjectBarrierRequest,
735    ) -> StreamResult<()> {
736        assert_eq!(self.partial_graph_id, request.partial_graph_id);
737        let actor_to_stop = barrier.all_stop_actors();
738        let is_stop_actor = |actor_id| {
739            actor_to_stop
740                .map(|actors| actors.contains(&actor_id))
741                .unwrap_or(false)
742        };
743
744        let table_ids = HashSet::from_iter(request.table_ids_to_sync);
745        self.table_ids.extend(table_ids.iter().cloned());
746
747        self.graph_state.transform_to_issued(
748            barrier,
749            request.actor_ids_to_collect.iter().copied(),
750            table_ids,
751        );
752
753        let mut new_actors = HashSet::new();
754        for (node, fragment_id, actor) in
755            request
756                .actors_to_build
757                .into_iter()
758                .flat_map(|fragment_actors| {
759                    let node = Arc::new(fragment_actors.node.unwrap());
760                    fragment_actors
761                        .actors
762                        .into_iter()
763                        .map(move |actor| (node.clone(), fragment_actors.fragment_id, actor))
764                })
765        {
766            let actor_id = actor.actor_id;
767            assert!(!is_stop_actor(actor_id));
768            assert!(new_actors.insert(actor_id));
769            assert!(request.actor_ids_to_collect.contains(&actor_id));
770            let (new_output_request_tx, new_output_request_rx) = unbounded_channel();
771            if let Some(pending_requests) = self.actor_pending_new_output_requests.remove(&actor_id)
772            {
773                for request in pending_requests {
774                    let _ = new_output_request_tx.send(request);
775                }
776            }
777            let (join_handle, monitor_join_handle) = self.actor_manager.spawn_actor(
778                actor,
779                fragment_id,
780                node,
781                self.local_barrier_manager.clone(),
782                new_output_request_rx,
783            );
784            assert!(
785                self.actor_states
786                    .try_insert(
787                        actor_id,
788                        InflightActorState::start(
789                            actor_id,
790                            barrier,
791                            new_output_request_tx,
792                            join_handle,
793                            monitor_join_handle
794                        )
795                    )
796                    .is_ok()
797            );
798        }
799
800        // Spawn a trivial join handle to be compatible with the unit test. In the unit tests that involve local barrier manager,
801        // actors are spawned in the local test logic, but we assume that there is an entry for each spawned actor in ·actor_states`,
802        // so under cfg!(test) we add a dummy entry for each new actor.
803        if cfg!(test) {
804            for &actor_id in &request.actor_ids_to_collect {
805                if !self.actor_states.contains_key(&actor_id) {
806                    let (tx, rx) = unbounded_channel();
807                    let join_handle = self.actor_manager.runtime.spawn(async move {
808                        // The rx is spawned so that tx.send() will not fail.
809                        let _ = rx;
810                        pending().await
811                    });
812                    assert!(
813                        self.actor_states
814                            .try_insert(
815                                actor_id,
816                                InflightActorState::start(actor_id, barrier, tx, join_handle, None,)
817                            )
818                            .is_ok()
819                    );
820                    new_actors.insert(actor_id);
821                }
822            }
823        }
824
825        // Note: it's important to issue barrier to actor after issuing to graph to ensure that
826        // we call `start_epoch` on the graph before the actors receive the barrier
827        for &actor_id in &request.actor_ids_to_collect {
828            if new_actors.contains(&actor_id) {
829                continue;
830            }
831            self.actor_states
832                .get_mut(&actor_id)
833                .unwrap_or_else(|| {
834                    panic!(
835                        "should exist: {} {:?}",
836                        actor_id, request.actor_ids_to_collect
837                    );
838                })
839                .issue_barrier(barrier, is_stop_actor(actor_id))?;
840        }
841
842        Ok(())
843    }
844
845    pub(super) fn new_actor_output_request(
846        &mut self,
847        actor_id: ActorId,
848        upstream_actor_id: ActorId,
849        request: TakeReceiverRequest,
850    ) {
851        let request = match request {
852            TakeReceiverRequest::Remote {
853                result_sender,
854                upstream_fragment_id,
855            } => {
856                let upstream_fragment_id_str = upstream_fragment_id.to_string();
857                let fragment_channel_buffered_bytes = self
858                    .actor_manager
859                    .streaming_metrics
860                    .fragment_channel_buffered_bytes
861                    .with_guarded_label_values(&[&upstream_fragment_id_str]);
862                let (tx, rx) = permit::channel_from_config_with_metrics(
863                    self.local_barrier_manager.env.global_config(),
864                    permit::ChannelMetrics {
865                        sender_actor_channel_buffered_bytes: fragment_channel_buffered_bytes
866                            .clone(),
867                        receiver_actor_channel_buffered_bytes: fragment_channel_buffered_bytes,
868                    },
869                );
870                let _ = result_sender.send(Ok(rx));
871                NewOutputRequest::Remote(tx)
872            }
873            TakeReceiverRequest::Local(tx) => NewOutputRequest::Local(tx),
874        };
875        if let Some(actor) = self.actor_states.get_mut(&upstream_actor_id) {
876            let _ = actor.new_output_request_tx.send((actor_id, request));
877        } else {
878            self.actor_pending_new_output_requests
879                .entry(upstream_actor_id)
880                .or_default()
881                .push((actor_id, request));
882        }
883    }
884
885    /// Handles [`LocalBarrierEvent`] from [`crate::task::barrier_manager::LocalBarrierManager`].
886    pub(super) fn poll_next_event(
887        &mut self,
888        cx: &mut Context<'_>,
889    ) -> Poll<ManagedBarrierStateEvent> {
890        if let Poll::Ready(option) = self.actor_failure_rx.poll_recv(cx) {
891            let (actor_id, err) = option.expect("non-empty when tx in local_barrier_manager");
892            return Poll::Ready(ManagedBarrierStateEvent::ActorError {
893                actor_id,
894                err,
895                partial_graph_id: self.partial_graph_id,
896            });
897        }
898        // yield some pending collected epochs
899        {
900            if let Some(barrier) = self.graph_state.may_have_collected_all() {
901                return Poll::Ready(ManagedBarrierStateEvent::BarrierCollected {
902                    barrier,
903                    partial_graph_id: self.partial_graph_id,
904                });
905            }
906        }
907        while let Poll::Ready(event) = self.barrier_event_rx.poll_recv(cx) {
908            match event.expect("non-empty when tx in local_barrier_manager") {
909                LocalBarrierEvent::ReportActorCollected { actor_id, epoch } => {
910                    if let Some(barrier) = self.collect(actor_id, epoch) {
911                        return Poll::Ready(ManagedBarrierStateEvent::BarrierCollected {
912                            barrier,
913                            partial_graph_id: self.partial_graph_id,
914                        });
915                    }
916                }
917                LocalBarrierEvent::ReportCreateProgress {
918                    epoch,
919                    fragment_id,
920                    actor,
921                    state,
922                } => {
923                    self.update_create_mview_progress(epoch, fragment_id, actor, state);
924                }
925                LocalBarrierEvent::ReportSourceListFinished {
926                    epoch,
927                    actor_id,
928                    table_id,
929                    associated_source_id,
930                } => {
931                    self.report_source_list_finished(
932                        epoch,
933                        actor_id,
934                        table_id,
935                        associated_source_id,
936                    );
937                }
938                LocalBarrierEvent::ReportSourceLoadFinished {
939                    epoch,
940                    actor_id,
941                    table_id,
942                    associated_source_id,
943                } => {
944                    self.report_source_load_finished(
945                        epoch,
946                        actor_id,
947                        table_id,
948                        associated_source_id,
949                    );
950                }
951                LocalBarrierEvent::RefreshFinished {
952                    epoch,
953                    actor_id,
954                    table_id,
955                    staging_table_id,
956                } => {
957                    self.report_refresh_finished(epoch, actor_id, table_id, staging_table_id);
958                }
959                LocalBarrierEvent::RegisterBarrierSender {
960                    actor_id,
961                    barrier_sender,
962                } => {
963                    if let Err(err) = self.register_barrier_sender(actor_id, barrier_sender) {
964                        return Poll::Ready(ManagedBarrierStateEvent::ActorError {
965                            actor_id,
966                            err,
967                            partial_graph_id: self.partial_graph_id,
968                        });
969                    }
970                }
971                LocalBarrierEvent::RegisterLocalUpstreamOutput {
972                    actor_id,
973                    upstream_actor_id,
974                    upstream_partial_graph_id,
975                    term_id,
976                    tx,
977                } => {
978                    return Poll::Ready(ManagedBarrierStateEvent::RegisterLocalUpstreamOutput {
979                        actor_id,
980                        upstream_actor_id,
981                        upstream_partial_graph_id,
982                        term_id,
983                        tx,
984                    });
985                }
986                LocalBarrierEvent::ReportCdcTableBackfillProgress {
987                    actor_id,
988                    epoch,
989                    state,
990                } => {
991                    self.update_cdc_table_backfill_progress(epoch, actor_id, state);
992                }
993                LocalBarrierEvent::ReportCdcSourceOffsetUpdated {
994                    epoch,
995                    actor_id,
996                    source_id,
997                } => {
998                    self.report_cdc_source_offset_updated(epoch, actor_id, source_id);
999                }
1000                LocalBarrierEvent::ReportIcebergPkIndexSinkMetadata {
1001                    epoch,
1002                    sink_id,
1003                    actor_id,
1004                    role,
1005                    metadata,
1006                } => {
1007                    self.report_iceberg_pk_index_sink_metadata(
1008                        epoch, sink_id, actor_id, role, metadata,
1009                    );
1010                }
1011            }
1012        }
1013
1014        debug_assert!(self.graph_state.may_have_collected_all().is_none());
1015        Poll::Pending
1016    }
1017}
1018
1019impl PartialGraphState {
1020    #[must_use]
1021    pub(super) fn collect(&mut self, actor_id: ActorId, epoch: EpochPair) -> Option<Barrier> {
1022        let is_finished = self
1023            .actor_states
1024            .get_mut(&actor_id)
1025            .expect("should exist")
1026            .collect(epoch);
1027        if is_finished {
1028            let state = self.actor_states.remove(&actor_id).expect("should exist");
1029            if let Some(monitor_task_handle) = state.monitor_task_handle {
1030                monitor_task_handle.abort();
1031            }
1032        }
1033        self.graph_state.collect(actor_id, epoch);
1034        self.graph_state.may_have_collected_all()
1035    }
1036
1037    pub(super) fn pop_barrier_to_complete(&mut self, prev_epoch: u64) -> BarrierToComplete {
1038        self.graph_state.pop_barrier_to_complete(prev_epoch)
1039    }
1040
1041    /// Collect actor errors for a while and find the one that might be the root cause.
1042    ///
1043    /// Returns `None` if there's no actor error received.
1044    async fn try_find_root_actor_failure(
1045        &mut self,
1046        first_failure: Option<(Option<ActorId>, StreamError)>,
1047    ) -> Option<ScoredStreamError> {
1048        let mut later_errs = vec![];
1049        // fetch more actor errors within a timeout
1050        let _ = tokio::time::timeout(Duration::from_secs(3), async {
1051            let mut uncollected_actors: HashSet<_> = self.actor_states.keys().cloned().collect();
1052            if let Some((Some(failed_actor), _)) = &first_failure {
1053                uncollected_actors.remove(failed_actor);
1054            }
1055            while !uncollected_actors.is_empty()
1056                && let Some((actor_id, error)) = self.actor_failure_rx.recv().await
1057            {
1058                uncollected_actors.remove(&actor_id);
1059                later_errs.push(error);
1060            }
1061        })
1062        .await;
1063
1064        first_failure
1065            .into_iter()
1066            .map(|(_, err)| err)
1067            .chain(later_errs)
1068            .map(|e| e.with_score())
1069            .max_by_key(|e| e.score)
1070    }
1071
1072    /// Report that a source has finished listing for a specific epoch
1073    pub(super) fn report_source_list_finished(
1074        &mut self,
1075        epoch: EpochPair,
1076        actor_id: ActorId,
1077        table_id: TableId,
1078        associated_source_id: SourceId,
1079    ) {
1080        // Find the correct partial graph state by matching the actor's partial graph id
1081        if let Some(actor_state) = self.actor_states.get(&actor_id)
1082            && actor_state.inflight_barriers.contains(&epoch.prev)
1083        {
1084            self.graph_state
1085                .list_finished_source_ids
1086                .entry(epoch.curr)
1087                .or_default()
1088                .push(PbListFinishedSource {
1089                    reporter_actor_id: actor_id,
1090                    table_id,
1091                    associated_source_id,
1092                });
1093        } else {
1094            warn!(
1095                ?epoch,
1096                %actor_id, %table_id, %associated_source_id, "ignore source list finished"
1097            );
1098        }
1099    }
1100
1101    /// Report that a source has finished loading for a specific epoch
1102    pub(super) fn report_source_load_finished(
1103        &mut self,
1104        epoch: EpochPair,
1105        actor_id: ActorId,
1106        table_id: TableId,
1107        associated_source_id: SourceId,
1108    ) {
1109        // Find the correct partial graph state by matching the actor's partial graph id
1110        if let Some(actor_state) = self.actor_states.get(&actor_id)
1111            && actor_state.inflight_barriers.contains(&epoch.prev)
1112        {
1113            self.graph_state
1114                .load_finished_source_ids
1115                .entry(epoch.curr)
1116                .or_default()
1117                .push(PbLoadFinishedSource {
1118                    reporter_actor_id: actor_id,
1119                    table_id,
1120                    associated_source_id,
1121                });
1122        } else {
1123            warn!(
1124                ?epoch,
1125                %actor_id, %table_id, %associated_source_id, "ignore source load finished"
1126            );
1127        }
1128    }
1129
1130    /// Report that a CDC source has updated its offset at least once
1131    pub(super) fn report_cdc_source_offset_updated(
1132        &mut self,
1133        epoch: EpochPair,
1134        actor_id: ActorId,
1135        source_id: SourceId,
1136    ) {
1137        if let Some(actor_state) = self.actor_states.get(&actor_id)
1138            && actor_state.inflight_barriers.contains(&epoch.prev)
1139        {
1140            self.graph_state
1141                .cdc_source_offset_updated
1142                .entry(epoch.curr)
1143                .or_default()
1144                .push(PbCdcSourceOffsetUpdated {
1145                    reporter_actor_id: actor_id,
1146                    source_id,
1147                });
1148        } else {
1149            warn!(
1150                ?epoch,
1151                %actor_id, %source_id, "ignore cdc source offset updated"
1152            );
1153        }
1154    }
1155
1156    /// Record a Iceberg pk-index sink metadata report.
1157    pub(super) fn report_iceberg_pk_index_sink_metadata(
1158        &mut self,
1159        epoch: EpochPair,
1160        sink_id: SinkId,
1161        actor_id: ActorId,
1162        role: PbIcebergPkIndexSinkRole,
1163        metadata: Option<SinkMetadata>,
1164    ) {
1165        if let Some(actor_state) = self.actor_states.get(&actor_id)
1166            && actor_state.inflight_barriers.contains(&epoch.prev)
1167        {
1168            self.graph_state
1169                .iceberg_pk_index_sink_metadata
1170                .entry(epoch.curr)
1171                .or_default()
1172                .push(PbIcebergPkIndexSinkMetadata {
1173                    sink_id,
1174                    reporter_actor_id: actor_id,
1175                    prev_epoch: epoch.prev,
1176                    role: role as i32,
1177                    metadata,
1178                });
1179        } else {
1180            tracing::warn!(
1181                ?epoch,
1182                %actor_id,
1183                %sink_id,
1184                ?role,
1185                "ignore iceberg v3 sink metadata report from non-inflight actor"
1186            );
1187        }
1188    }
1189
1190    /// Report that a table has finished refreshing for a specific epoch
1191    pub(super) fn report_refresh_finished(
1192        &mut self,
1193        epoch: EpochPair,
1194        actor_id: ActorId,
1195        table_id: TableId,
1196        staging_table_id: TableId,
1197    ) {
1198        // Find the correct partial graph state by matching the actor's partial graph id
1199        let Some(actor_state) = self.actor_states.get(&actor_id) else {
1200            warn!(
1201                ?epoch,
1202                %actor_id, %table_id, "ignore refresh finished table: actor_state not found"
1203            );
1204            return;
1205        };
1206        if !actor_state.inflight_barriers.contains(&epoch.prev) {
1207            warn!(
1208                ?epoch,
1209                %actor_id,
1210                %table_id,
1211                inflight_barriers = ?actor_state.inflight_barriers,
1212                "ignore refresh finished table: partial_graph_id not found in inflight_barriers"
1213            );
1214            return;
1215        };
1216        self.graph_state
1217            .refresh_finished_tables
1218            .entry(epoch.curr)
1219            .or_default()
1220            .insert(table_id);
1221        self.graph_state
1222            .truncate_tables
1223            .entry(epoch.curr)
1224            .or_default()
1225            .insert(staging_table_id);
1226    }
1227}
1228
1229impl PartialGraphManagedBarrierState {
1230    /// Check whether any `Issued` barrier has been collected by all actors and, if so,
1231    /// transition it to `AllCollected`.
1232    fn may_have_collected_all(&mut self) -> Option<Barrier> {
1233        for barrier_state in self.epoch_barrier_state_map.values_mut() {
1234            match &barrier_state.inner {
1235                ManagedBarrierStateInner::Issued(IssuedState {
1236                    remaining_actors, ..
1237                }) if remaining_actors.is_empty() => {}
1238                ManagedBarrierStateInner::AllCollected { .. } => {
1239                    continue;
1240                }
1241                ManagedBarrierStateInner::Issued(_) => {
1242                    break;
1243                }
1244            }
1245
1246            self.barrier_manager_progress.inc();
1247
1248            let create_mview_progress = self
1249                .create_mview_progress
1250                .remove(&barrier_state.barrier.epoch.curr)
1251                .unwrap_or_default()
1252                .into_iter()
1253                .map(|(actor, (fragment_id, state))| state.to_pb(fragment_id, actor))
1254                .collect();
1255
1256            let list_finished_source_ids = self
1257                .list_finished_source_ids
1258                .remove(&barrier_state.barrier.epoch.curr)
1259                .unwrap_or_default();
1260
1261            let load_finished_source_ids = self
1262                .load_finished_source_ids
1263                .remove(&barrier_state.barrier.epoch.curr)
1264                .unwrap_or_default();
1265
1266            let cdc_table_backfill_progress = self
1267                .cdc_table_backfill_progress
1268                .remove(&barrier_state.barrier.epoch.curr)
1269                .unwrap_or_default()
1270                .into_iter()
1271                .map(|(actor, state)| state.to_pb(actor, barrier_state.barrier.epoch.curr))
1272                .collect();
1273
1274            let cdc_source_offset_updated = self
1275                .cdc_source_offset_updated
1276                .remove(&barrier_state.barrier.epoch.curr)
1277                .unwrap_or_default();
1278
1279            let iceberg_pk_index_sink_metadata = self
1280                .iceberg_pk_index_sink_metadata
1281                .remove(&barrier_state.barrier.epoch.curr)
1282                .unwrap_or_default();
1283
1284            let truncate_tables = self
1285                .truncate_tables
1286                .remove(&barrier_state.barrier.epoch.curr)
1287                .unwrap_or_default()
1288                .into_iter()
1289                .collect();
1290
1291            let refresh_finished_tables = self
1292                .refresh_finished_tables
1293                .remove(&barrier_state.barrier.epoch.curr)
1294                .unwrap_or_default()
1295                .into_iter()
1296                .collect();
1297            let prev_state = replace(
1298                &mut barrier_state.inner,
1299                ManagedBarrierStateInner::AllCollected {
1300                    create_mview_progress,
1301                    list_finished_source_ids,
1302                    load_finished_source_ids,
1303                    truncate_tables,
1304                    refresh_finished_tables,
1305                    cdc_table_backfill_progress,
1306                    cdc_source_offset_updated,
1307                    iceberg_pk_index_sink_metadata,
1308                },
1309            );
1310
1311            must_match!(prev_state, ManagedBarrierStateInner::Issued(IssuedState {
1312                barrier_inflight_latency: timer,
1313                ..
1314            }) => {
1315                timer.observe_duration();
1316            });
1317
1318            return Some(barrier_state.barrier.clone());
1319        }
1320        None
1321    }
1322
1323    fn pop_barrier_to_complete(&mut self, prev_epoch: u64) -> BarrierToComplete {
1324        let (popped_prev_epoch, barrier_state) = self
1325            .epoch_barrier_state_map
1326            .pop_first()
1327            .expect("should exist");
1328
1329        assert_eq!(prev_epoch, popped_prev_epoch);
1330
1331        let (
1332            create_mview_progress,
1333            list_finished_source_ids,
1334            load_finished_source_ids,
1335            cdc_table_backfill_progress,
1336            cdc_source_offset_updated,
1337            iceberg_pk_index_sink_metadata,
1338            truncate_tables,
1339            refresh_finished_tables,
1340        ) = must_match!(barrier_state.inner, ManagedBarrierStateInner::AllCollected {
1341            create_mview_progress,
1342            list_finished_source_ids,
1343            load_finished_source_ids,
1344            truncate_tables,
1345            refresh_finished_tables,
1346            cdc_table_backfill_progress,
1347            cdc_source_offset_updated,
1348            iceberg_pk_index_sink_metadata,
1349        } => {
1350            (create_mview_progress, list_finished_source_ids, load_finished_source_ids, cdc_table_backfill_progress, cdc_source_offset_updated, iceberg_pk_index_sink_metadata, truncate_tables, refresh_finished_tables)
1351        });
1352        BarrierToComplete {
1353            barrier: barrier_state.barrier,
1354            table_ids: barrier_state.table_ids,
1355            create_mview_progress,
1356            list_finished_source_ids,
1357            load_finished_source_ids,
1358            truncate_tables,
1359            refresh_finished_tables,
1360            cdc_table_backfill_progress,
1361            cdc_source_offset_updated,
1362            iceberg_pk_index_sink_metadata,
1363        }
1364    }
1365
1366    pub(super) fn barrier_sync_latency(&self) -> LabelGuardedHistogram {
1367        self.barrier_sync_latency.clone()
1368    }
1369}
1370
1371pub(crate) struct BarrierToComplete {
1372    pub barrier: Barrier,
1373    pub table_ids: Option<HashSet<TableId>>,
1374    pub create_mview_progress: Vec<PbCreateMviewProgress>,
1375    pub list_finished_source_ids: Vec<PbListFinishedSource>,
1376    pub load_finished_source_ids: Vec<PbLoadFinishedSource>,
1377    pub truncate_tables: Vec<TableId>,
1378    pub refresh_finished_tables: Vec<TableId>,
1379    pub cdc_table_backfill_progress: Vec<PbCdcTableBackfillProgress>,
1380    pub cdc_source_offset_updated: Vec<PbCdcSourceOffsetUpdated>,
1381    pub iceberg_pk_index_sink_metadata: Vec<PbIcebergPkIndexSinkMetadata>,
1382}
1383
1384impl PartialGraphManagedBarrierState {
1385    /// Collect a `barrier` from the actor with `actor_id`.
1386    pub(super) fn collect(&mut self, actor_id: impl Into<ActorId>, epoch: EpochPair) {
1387        let actor_id = actor_id.into();
1388        tracing::debug!(
1389            target: "events::stream::barrier::manager::collect",
1390            ?epoch, %actor_id, state = ?self.epoch_barrier_state_map,
1391            "collect_barrier",
1392        );
1393
1394        match self.epoch_barrier_state_map.get_mut(&epoch.prev) {
1395            None => {
1396                // If the barrier's state is stashed, this occurs exclusively in scenarios where the barrier has not been
1397                // injected by the barrier manager, or the barrier message is blocked at the `RemoteInput` side waiting for injection.
1398                // Given these conditions, it's inconceivable for an actor to attempt collect at this point.
1399                panic!(
1400                    "cannot collect new actor barrier {:?} at current state: None",
1401                    epoch,
1402                )
1403            }
1404            Some(&mut BarrierState {
1405                ref barrier,
1406                inner:
1407                    ManagedBarrierStateInner::Issued(IssuedState {
1408                        ref mut remaining_actors,
1409                        ..
1410                    }),
1411                ..
1412            }) => {
1413                let exist = remaining_actors.remove(&actor_id);
1414                assert!(
1415                    exist,
1416                    "the actor doesn't exist. actor_id: {:?}, curr_epoch: {:?}",
1417                    actor_id, epoch.curr
1418                );
1419                assert_eq!(barrier.epoch.curr, epoch.curr);
1420            }
1421            Some(BarrierState { inner, .. }) => {
1422                panic!(
1423                    "cannot collect new actor barrier {:?} at current state: {:?}",
1424                    epoch, inner
1425                )
1426            }
1427        }
1428    }
1429
1430    /// When the meta service issues a `send_barrier` request, call this function to transform to
1431    /// `Issued` and start to collect or to notify.
1432    pub(super) fn transform_to_issued(
1433        &mut self,
1434        barrier: &Barrier,
1435        actor_ids_to_collect: impl IntoIterator<Item = ActorId>,
1436        table_ids: HashSet<TableId>,
1437    ) {
1438        let timer = self.barrier_inflight_latency.start_timer();
1439
1440        if let Some(hummock) = self.state_store.as_hummock() {
1441            hummock.start_epoch(barrier.epoch.curr, table_ids.clone());
1442        }
1443
1444        let table_ids = match barrier.kind {
1445            BarrierKind::Unspecified => {
1446                unreachable!()
1447            }
1448            BarrierKind::Initial => {
1449                assert!(
1450                    self.prev_barrier_table_ids.is_none(),
1451                    "non empty table_ids at initial barrier: {:?}",
1452                    self.prev_barrier_table_ids
1453                );
1454                info!(epoch = ?barrier.epoch, "initialize at Initial barrier");
1455                self.prev_barrier_table_ids = Some((barrier.epoch, table_ids));
1456                None
1457            }
1458            BarrierKind::Barrier => {
1459                if let Some((prev_epoch, prev_table_ids)) = self.prev_barrier_table_ids.as_mut() {
1460                    assert_eq!(prev_epoch.curr, barrier.epoch.prev);
1461                    assert_eq!(prev_table_ids, &table_ids);
1462                    *prev_epoch = barrier.epoch;
1463                } else {
1464                    info!(epoch = ?barrier.epoch, "initialize at non-checkpoint barrier");
1465                    self.prev_barrier_table_ids = Some((barrier.epoch, table_ids));
1466                }
1467                None
1468            }
1469            BarrierKind::Checkpoint => Some(
1470                if let Some((prev_epoch, prev_table_ids)) = self
1471                    .prev_barrier_table_ids
1472                    .replace((barrier.epoch, table_ids))
1473                    && prev_epoch.curr == barrier.epoch.prev
1474                {
1475                    prev_table_ids
1476                } else {
1477                    debug!(epoch = ?barrier.epoch, "reinitialize at Checkpoint barrier");
1478                    HashSet::new()
1479                },
1480            ),
1481        };
1482
1483        if let Some(&mut BarrierState { ref inner, .. }) =
1484            self.epoch_barrier_state_map.get_mut(&barrier.epoch.prev)
1485        {
1486            {
1487                panic!(
1488                    "barrier epochs{:?} state has already been `Issued`. Current state: {:?}",
1489                    barrier.epoch, inner
1490                );
1491            }
1492        };
1493
1494        self.epoch_barrier_state_map.insert(
1495            barrier.epoch.prev,
1496            BarrierState {
1497                barrier: barrier.clone(),
1498                inner: ManagedBarrierStateInner::Issued(IssuedState {
1499                    remaining_actors: BTreeSet::from_iter(actor_ids_to_collect),
1500                    barrier_inflight_latency: timer,
1501                }),
1502                table_ids,
1503            },
1504        );
1505    }
1506
1507    #[cfg(test)]
1508    async fn pop_next_completed_epoch(&mut self) -> u64 {
1509        if let Some(barrier) = self.may_have_collected_all() {
1510            self.pop_barrier_to_complete(barrier.epoch.prev);
1511            return barrier.epoch.prev;
1512        }
1513        pending().await
1514    }
1515}
1516
1517#[cfg(test)]
1518mod tests {
1519    use std::collections::HashSet;
1520
1521    use risingwave_common::util::epoch::test_epoch;
1522
1523    use crate::executor::Barrier;
1524    use crate::task::barrier_worker::managed_state::PartialGraphManagedBarrierState;
1525
1526    #[tokio::test]
1527    async fn test_managed_state_add_actor() {
1528        let mut managed_barrier_state = PartialGraphManagedBarrierState::for_test();
1529        let barrier1 = Barrier::new_test_barrier(test_epoch(1));
1530        let barrier2 = Barrier::new_test_barrier(test_epoch(2));
1531        let barrier3 = Barrier::new_test_barrier(test_epoch(3));
1532        let actor_ids_to_collect1 = HashSet::from([1.into(), 2.into()]);
1533        let actor_ids_to_collect2 = HashSet::from([1.into(), 2.into()]);
1534        let actor_ids_to_collect3 = HashSet::from([1.into(), 2.into(), 3.into()]);
1535        managed_barrier_state.transform_to_issued(&barrier1, actor_ids_to_collect1, HashSet::new());
1536        managed_barrier_state.transform_to_issued(&barrier2, actor_ids_to_collect2, HashSet::new());
1537        managed_barrier_state.transform_to_issued(&barrier3, actor_ids_to_collect3, HashSet::new());
1538        managed_barrier_state.collect(1, barrier1.epoch);
1539        managed_barrier_state.collect(2, barrier1.epoch);
1540        assert_eq!(
1541            managed_barrier_state.pop_next_completed_epoch().await,
1542            test_epoch(0)
1543        );
1544        assert_eq!(
1545            managed_barrier_state
1546                .epoch_barrier_state_map
1547                .first_key_value()
1548                .unwrap()
1549                .0,
1550            &test_epoch(1)
1551        );
1552        managed_barrier_state.collect(1, barrier2.epoch);
1553        managed_barrier_state.collect(1, barrier3.epoch);
1554        managed_barrier_state.collect(2, barrier2.epoch);
1555        assert_eq!(
1556            managed_barrier_state.pop_next_completed_epoch().await,
1557            test_epoch(1)
1558        );
1559        assert_eq!(
1560            managed_barrier_state
1561                .epoch_barrier_state_map
1562                .first_key_value()
1563                .unwrap()
1564                .0,
1565            &test_epoch(2)
1566        );
1567        managed_barrier_state.collect(2, barrier3.epoch);
1568        managed_barrier_state.collect(3, barrier3.epoch);
1569        assert_eq!(
1570            managed_barrier_state.pop_next_completed_epoch().await,
1571            test_epoch(2)
1572        );
1573        assert!(managed_barrier_state.epoch_barrier_state_map.is_empty());
1574    }
1575
1576    #[tokio::test]
1577    async fn test_managed_state_stop_actor() {
1578        let mut managed_barrier_state = PartialGraphManagedBarrierState::for_test();
1579        let barrier1 = Barrier::new_test_barrier(test_epoch(1));
1580        let barrier2 = Barrier::new_test_barrier(test_epoch(2));
1581        let barrier3 = Barrier::new_test_barrier(test_epoch(3));
1582        let actor_ids_to_collect1 = HashSet::from([1.into(), 2.into(), 3.into(), 4.into()]);
1583        let actor_ids_to_collect2 = HashSet::from([1.into(), 2.into(), 3.into()]);
1584        let actor_ids_to_collect3 = HashSet::from([1.into(), 2.into()]);
1585        managed_barrier_state.transform_to_issued(&barrier1, actor_ids_to_collect1, HashSet::new());
1586        managed_barrier_state.transform_to_issued(&barrier2, actor_ids_to_collect2, HashSet::new());
1587        managed_barrier_state.transform_to_issued(&barrier3, actor_ids_to_collect3, HashSet::new());
1588
1589        managed_barrier_state.collect(1, barrier1.epoch);
1590        managed_barrier_state.collect(1, barrier2.epoch);
1591        managed_barrier_state.collect(1, barrier3.epoch);
1592        managed_barrier_state.collect(2, barrier1.epoch);
1593        managed_barrier_state.collect(2, barrier2.epoch);
1594        managed_barrier_state.collect(2, barrier3.epoch);
1595        assert_eq!(
1596            managed_barrier_state
1597                .epoch_barrier_state_map
1598                .first_key_value()
1599                .unwrap()
1600                .0,
1601            &0
1602        );
1603        managed_barrier_state.collect(3, barrier1.epoch);
1604        managed_barrier_state.collect(3, barrier2.epoch);
1605        assert_eq!(
1606            managed_barrier_state
1607                .epoch_barrier_state_map
1608                .first_key_value()
1609                .unwrap()
1610                .0,
1611            &0
1612        );
1613        managed_barrier_state.collect(4, barrier1.epoch);
1614        assert_eq!(
1615            managed_barrier_state.pop_next_completed_epoch().await,
1616            test_epoch(0)
1617        );
1618        assert_eq!(
1619            managed_barrier_state.pop_next_completed_epoch().await,
1620            test_epoch(1)
1621        );
1622        assert_eq!(
1623            managed_barrier_state.pop_next_completed_epoch().await,
1624            test_epoch(2)
1625        );
1626        assert!(managed_barrier_state.epoch_barrier_state_map.is_empty());
1627    }
1628}