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