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