Skip to main content

risingwave_stream/task/barrier_worker/
mod.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::hash_map::Entry;
16use std::collections::{HashMap, HashSet};
17use std::fmt::Display;
18use std::future::{pending, poll_fn};
19use std::sync::Arc;
20use std::task::Poll;
21
22use anyhow::anyhow;
23use await_tree::{InstrumentAwait, SpanExt};
24use futures::future::{BoxFuture, join, join_all};
25use futures::stream::{BoxStream, FuturesOrdered};
26use futures::{FutureExt, StreamExt, TryFutureExt};
27use itertools::Itertools;
28use risingwave_common::bitmap::Bitmap;
29use risingwave_common::catalog::TableId;
30use risingwave_pb::stream_plan::barrier::BarrierKind;
31use risingwave_pb::stream_service::barrier_complete_response::{
32    PbLocalSstableInfo, PbTableChangeLogProgress,
33};
34use risingwave_rpc_client::error::{ToTonicStatus, TonicStatusWrapper};
35use risingwave_storage::store_impl::AsHummock;
36use thiserror_ext::AsReport;
37use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
38use tokio::sync::oneshot;
39use tokio::task::JoinHandle;
40use tokio::{select, spawn};
41use tonic::{Code, Status};
42use tracing::warn;
43
44use self::managed_state::ManagedBarrierState;
45use crate::error::{ScoredStreamError, StreamError, StreamResult};
46#[cfg(test)]
47use crate::task::LocalBarrierManager;
48use crate::task::managed_state::{BarrierToComplete, ResetPartialGraphOutput};
49use crate::task::{
50    ActorId, AtomicU64Ref, CONFIG_OVERRIDE_CACHE_DEFAULT_CAPACITY, ConfigOverrideCache, FragmentId,
51    PartialGraphId, StreamActorManager, StreamEnvironment, UpDownActorIds,
52};
53pub mod managed_state;
54#[cfg(test)]
55mod tests;
56
57use risingwave_common::metrics::LabelGuardedHistogram;
58use risingwave_hummock_sdk::table_stats::to_prost_table_stats_map;
59use risingwave_hummock_sdk::{LocalSstableInfo, SyncResult};
60use risingwave_pb::stream_service::streaming_control_stream_request::{
61    InitRequest, Request, ResetPartialGraphsRequest,
62};
63use risingwave_pb::stream_service::streaming_control_stream_response::{
64    InitResponse, ReportPartialGraphFailureResponse, ResetPartialGraphResponse, Response,
65    ShutdownResponse,
66};
67use risingwave_pb::stream_service::{
68    BarrierCompleteResponse, InjectBarrierRequest, PbScoredError, StreamingControlStreamRequest,
69    StreamingControlStreamResponse, streaming_control_stream_response,
70};
71
72use crate::executor::Barrier;
73use crate::executor::exchange::permit::Receiver;
74use crate::executor::monitor::StreamingMetrics;
75use crate::task::barrier_worker::managed_state::{
76    EpochBarrierEvent, ManagedBarrierStateDebugInfo, ManagedBarrierStateEvent, PartialGraphState,
77    PartialGraphStatus,
78};
79use crate::task::progress::TableChangeLogProgress;
80
81/// If enabled, all actors will be grouped in the same tracing span within one epoch.
82/// Note that this option will significantly increase the overhead of tracing.
83pub const ENABLE_BARRIER_AGGREGATION: bool = false;
84
85/// Collect result of some barrier on current compute node. Will be reported to the meta service in [`LocalBarrierWorker::on_epoch_completed`].
86#[derive(Debug)]
87pub struct BarrierCompleteResult {
88    /// The result returned from `sync` of `StateStore`.
89    pub sync_result: Option<SyncResult>,
90    pub(crate) event: EpochBarrierEvent,
91}
92
93type TableChangeLogSubscriberKey = (FragmentId, TableId, TableId);
94
95struct LocalTableChangeLogProgress {
96    vnode_bitmap: Bitmap,
97    min_truncate_epoch: u64,
98}
99
100fn aggregate_table_change_log_progress(
101    reports: Vec<TableChangeLogProgress>,
102) -> Vec<PbTableChangeLogProgress> {
103    let mut aggregated = HashMap::<TableChangeLogSubscriberKey, LocalTableChangeLogProgress>::new();
104    let mut invalid = HashSet::new();
105
106    for report in reports {
107        let key = (
108            report.fragment_id,
109            report.upstream_table_id,
110            report.progress_table_id,
111        );
112        if !report.vnode_bitmap.any() {
113            warn!(
114                actor_id = %report.actor_id,
115                fragment_id = %report.fragment_id,
116                upstream_table_id = %report.upstream_table_id,
117                progress_table_id = %report.progress_table_id,
118                "ignore empty table change log progress vnode bitmap"
119            );
120            invalid.insert(key);
121            continue;
122        }
123
124        match aggregated.entry(key) {
125            Entry::Vacant(entry) => {
126                entry.insert(LocalTableChangeLogProgress {
127                    vnode_bitmap: (*report.vnode_bitmap).clone(),
128                    min_truncate_epoch: report.truncate_epoch,
129                });
130            }
131            Entry::Occupied(mut entry) => {
132                let progress = entry.get_mut();
133                if progress.vnode_bitmap.len() != report.vnode_bitmap.len()
134                    || (&progress.vnode_bitmap & &*report.vnode_bitmap).any()
135                {
136                    warn!(
137                        actor_id = %report.actor_id,
138                        fragment_id = %report.fragment_id,
139                        upstream_table_id = %report.upstream_table_id,
140                        progress_table_id = %report.progress_table_id,
141                        "ignore inconsistent table change log progress vnode ownership"
142                    );
143                    invalid.insert(key);
144                    continue;
145                }
146                progress.vnode_bitmap |= &*report.vnode_bitmap;
147                progress.min_truncate_epoch =
148                    progress.min_truncate_epoch.min(report.truncate_epoch);
149            }
150        }
151    }
152
153    aggregated
154        .into_iter()
155        .filter_map(
156            |(key @ (fragment_id, upstream_table_id, progress_table_id), progress)| {
157                (!invalid.contains(&key)).then(|| PbTableChangeLogProgress {
158                    fragment_id,
159                    upstream_table_id,
160                    progress_table_id,
161                    min_truncate_epoch: progress.min_truncate_epoch,
162                    vnode_bitmap: Some(progress.vnode_bitmap.to_protobuf()),
163                })
164            },
165        )
166        .collect()
167}
168
169/// Lives in [`crate::task::barrier_worker::LocalBarrierWorker`],
170/// Communicates with `ControlStreamManager` in meta.
171/// Handles [`risingwave_pb::stream_service::streaming_control_stream_request::Request`].
172pub(super) struct ControlStreamHandle {
173    #[expect(clippy::type_complexity)]
174    pair: Option<(
175        UnboundedSender<Result<StreamingControlStreamResponse, Status>>,
176        BoxStream<'static, Result<StreamingControlStreamRequest, Status>>,
177    )>,
178}
179
180impl ControlStreamHandle {
181    fn empty() -> Self {
182        Self { pair: None }
183    }
184
185    pub(super) fn new(
186        sender: UnboundedSender<Result<StreamingControlStreamResponse, Status>>,
187        request_stream: BoxStream<'static, Result<StreamingControlStreamRequest, Status>>,
188    ) -> Self {
189        Self {
190            pair: Some((sender, request_stream)),
191        }
192    }
193
194    pub(super) fn connected(&self) -> bool {
195        self.pair.is_some()
196    }
197
198    fn reset_stream_with_err(&mut self, err: Status) {
199        if let Some((sender, _)) = self.pair.take() {
200            // Note: `TonicStatusWrapper` provides a better error report.
201            let err = TonicStatusWrapper::new(err);
202            warn!(error = %err.as_report(), "control stream reset with error");
203
204            let err = err.into_inner();
205            if sender.send(Err(err)).is_err() {
206                warn!("failed to notify reset of control stream");
207            }
208        }
209    }
210
211    /// Send `Shutdown` message to the control stream and wait for the stream to be closed
212    /// by the meta service.
213    async fn shutdown_stream(&mut self) {
214        if let Some((sender, _)) = self.pair.take() {
215            if sender
216                .send(Ok(StreamingControlStreamResponse {
217                    response: Some(streaming_control_stream_response::Response::Shutdown(
218                        ShutdownResponse::default(),
219                    )),
220                }))
221                .is_err()
222            {
223                warn!("failed to notify shutdown of control stream");
224            } else {
225                tracing::info!("waiting for meta service to close control stream...");
226
227                // Wait for the stream to be closed, to ensure that the `Shutdown` message has
228                // been acknowledged by the meta service for more precise error report.
229                //
230                // This is because the meta service will reset the control stream manager and
231                // drop the connection to us upon recovery. As a result, the receiver part of
232                // this sender will also be dropped, causing the stream to close.
233                sender.closed().await;
234            }
235        } else {
236            debug!("control stream has been reset, ignore shutdown");
237        }
238    }
239
240    pub(super) fn ack_reset_partial_graph(
241        &mut self,
242        partial_graph_id: PartialGraphId,
243        root_err: Option<ScoredStreamError>,
244    ) {
245        self.send_response(Response::ResetPartialGraph(ResetPartialGraphResponse {
246            partial_graph_id,
247            root_err: root_err.map(|err| PbScoredError {
248                err_msg: err.error.to_report_string(),
249                score: err.score.0,
250            }),
251        }));
252    }
253
254    fn send_response(&mut self, response: streaming_control_stream_response::Response) {
255        if let Some((sender, _)) = self.pair.as_ref() {
256            if sender
257                .send(Ok(StreamingControlStreamResponse {
258                    response: Some(response),
259                }))
260                .is_err()
261            {
262                self.pair = None;
263                warn!("failed to send the response; the control stream was reset");
264            }
265        } else {
266            debug!(?response, "control stream has been reset. ignore response");
267        }
268    }
269
270    async fn next_request(&mut self) -> StreamingControlStreamRequest {
271        if let Some((_, stream)) = &mut self.pair {
272            match stream.next().await {
273                Some(Ok(request)) => {
274                    return request;
275                }
276                Some(Err(e)) => self.reset_stream_with_err(
277                    anyhow!(TonicStatusWrapper::new(e)) // wrap the status to provide better error report
278                        .context("failed to get request")
279                        .to_status_unnamed(Code::Internal),
280                ),
281                None => self.reset_stream_with_err(Status::internal("end of stream")),
282            }
283        }
284        pending().await
285    }
286}
287
288pub(super) enum TakeReceiverRequest {
289    Remote {
290        result_sender: oneshot::Sender<StreamResult<Receiver>>,
291        upstream_fragment_id: FragmentId,
292    },
293    Local(permit::Sender),
294}
295
296/// Sent from [`crate::task::stream_manager::LocalStreamManager`] to [`crate::task::barrier_worker::LocalBarrierWorker::run`].
297///
298/// See [`crate::task`] for architecture overview.
299#[derive(strum_macros::Display)]
300pub(super) enum LocalActorOperation {
301    NewControlStream {
302        handle: ControlStreamHandle,
303        init_request: InitRequest,
304    },
305    TakeReceiver {
306        partial_graph_id: PartialGraphId,
307        term_id: String,
308        ids: UpDownActorIds,
309        request: TakeReceiverRequest,
310    },
311    #[cfg(test)]
312    GetCurrentLocalBarrierManager(oneshot::Sender<LocalBarrierManager>),
313    #[cfg(test)]
314    TakePendingNewOutputRequest(ActorId, oneshot::Sender<Vec<(ActorId, NewOutputRequest)>>),
315    #[cfg(test)]
316    Flush(oneshot::Sender<()>),
317    InspectState {
318        result_sender: oneshot::Sender<String>,
319    },
320    Shutdown {
321        result_sender: oneshot::Sender<()>,
322    },
323}
324
325pub(super) struct LocalBarrierWorkerDebugInfo<'a> {
326    managed_barrier_state:
327        HashMap<PartialGraphId, (String, Option<ManagedBarrierStateDebugInfo<'a>>)>,
328    has_control_stream_connected: bool,
329}
330
331impl Display for LocalBarrierWorkerDebugInfo<'_> {
332    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
333        writeln!(
334            f,
335            "\nhas_control_stream_connected: {}",
336            self.has_control_stream_connected
337        )?;
338
339        for (partial_graph_id, (status, managed_barrier_state)) in &self.managed_barrier_state {
340            writeln!(
341                f,
342                "partial graph {} status: {} managed_barrier_state:\n{}",
343                partial_graph_id,
344                status,
345                managed_barrier_state
346                    .as_ref()
347                    .map(ToString::to_string)
348                    .unwrap_or_default()
349            )?;
350        }
351        Ok(())
352    }
353}
354
355/// [`LocalBarrierWorker`] manages barrier control flow.
356/// Specifically, [`LocalBarrierWorker`] serves barrier injection from meta server, sends the
357/// barriers to and collects them from all actors, and finally reports the progress.
358///
359/// Runs event loop in [`Self::run`]. Handles events sent by [`crate::task::LocalStreamManager`].
360///
361/// See [`crate::task`] for architecture overview.
362pub(super) struct LocalBarrierWorker {
363    /// Current barrier collection state.
364    pub(super) state: ManagedBarrierState,
365
366    /// Futures will be finished in the order of epoch in ascending order.
367    await_epoch_completed_futures:
368        HashMap<PartialGraphId, FuturesOrdered<AwaitEpochCompletedFuture>>,
369
370    control_stream_handle: ControlStreamHandle,
371
372    pub(super) actor_manager: Arc<StreamActorManager>,
373}
374
375impl LocalBarrierWorker {
376    pub(super) fn new(actor_manager: Arc<StreamActorManager>) -> Self {
377        Self {
378            state: Default::default(),
379            await_epoch_completed_futures: Default::default(),
380            control_stream_handle: ControlStreamHandle::empty(),
381            actor_manager,
382        }
383    }
384
385    fn to_debug_info(&self) -> LocalBarrierWorkerDebugInfo<'_> {
386        LocalBarrierWorkerDebugInfo {
387            managed_barrier_state: self
388                .state
389                .partial_graphs
390                .iter()
391                .map(|(partial_graph_id, status)| {
392                    (*partial_graph_id, {
393                        match status {
394                            PartialGraphStatus::ReceivedExchangeRequest(_) => {
395                                ("ReceivedExchangeRequest".to_owned(), None)
396                            }
397                            PartialGraphStatus::Running(state) => {
398                                ("running".to_owned(), Some(state.to_debug_info()))
399                            }
400                            PartialGraphStatus::Suspended(state) => {
401                                (format!("suspended: {:?}", state.suspend_time), None)
402                            }
403                            PartialGraphStatus::Resetting => ("resetting".to_owned(), None),
404                            PartialGraphStatus::Unspecified => {
405                                unreachable!()
406                            }
407                        }
408                    })
409                })
410                .collect(),
411            has_control_stream_connected: self.control_stream_handle.connected(),
412        }
413    }
414
415    async fn next_completed_epoch(
416        futures: &mut HashMap<PartialGraphId, FuturesOrdered<AwaitEpochCompletedFuture>>,
417    ) -> (PartialGraphId, Barrier, StreamResult<BarrierCompleteResult>) {
418        poll_fn(|cx| {
419            for (partial_graph_id, futures) in &mut *futures {
420                if let Poll::Ready(Some((barrier, result))) = futures.poll_next_unpin(cx) {
421                    return Poll::Ready((*partial_graph_id, barrier, result));
422                }
423            }
424            Poll::Pending
425        })
426        .await
427    }
428
429    async fn run(mut self, mut actor_op_rx: UnboundedReceiver<LocalActorOperation>) {
430        loop {
431            select! {
432                biased;
433                event = self.state.next_event() => {
434                    match event {
435                        ManagedBarrierStateEvent::BarrierCollected{
436                            partial_graph_id,
437                            barrier,
438                        } => {
439                            // update await_epoch_completed_futures
440                            // handled below in next_completed_epoch
441                            self.complete_barrier(partial_graph_id, barrier.epoch.prev);
442                        }
443                        ManagedBarrierStateEvent::ActorError{
444                            partial_graph_id,
445                            actor_id,
446                            err,
447                        } => {
448                            self.on_partial_graph_failure(partial_graph_id, Some(actor_id), err, "recv actor failure");
449                        }
450                        ManagedBarrierStateEvent::PartialGraphsReset(output) => {
451                            for (partial_graph_id, output) in output {
452                                self.ack_partial_graph_reset(partial_graph_id, Some(output));
453                            }
454                        }
455                        ManagedBarrierStateEvent::RegisterLocalUpstreamOutput{
456                            actor_id,
457                            upstream_actor_id,
458                            upstream_partial_graph_id,
459                            term_id,
460                            tx
461                        } => {
462                            self.handle_actor_op(LocalActorOperation::TakeReceiver {
463                                partial_graph_id: upstream_partial_graph_id,
464                                term_id,
465                                ids: (upstream_actor_id, actor_id),
466                                request: TakeReceiverRequest::Local(tx),
467                            });
468                        }
469                    }
470                }
471                (partial_graph_id, barrier, result) = Self::next_completed_epoch(&mut self.await_epoch_completed_futures) => {
472                    match result {
473                        Ok(result) => {
474                            self.on_epoch_completed(partial_graph_id, result);
475                        }
476                        Err(err) => {
477                            // TODO: may only report as partial graph failure instead of reset the stream
478                            // when the HummockUploader support partial recovery. Currently the HummockUploader
479                            // enter `Err` state and stop working until a global recovery to clear the uploader.
480                            self.control_stream_handle.reset_stream_with_err(Status::internal(format!("failed to complete epoch: {} {:?} {:?}", partial_graph_id, barrier.epoch, err.as_report())));
481                        }
482                    }
483                },
484                actor_op = actor_op_rx.recv() => {
485                    if let Some(actor_op) = actor_op {
486                        match actor_op {
487                            LocalActorOperation::NewControlStream { handle, init_request  } => {
488                                self.control_stream_handle.reset_stream_with_err(Status::internal("control stream has been reset to a new one"));
489                                self.reset(init_request).await;
490                                self.control_stream_handle = handle;
491                                self.control_stream_handle.send_response(streaming_control_stream_response::Response::Init(InitResponse {}));
492                            }
493                            LocalActorOperation::Shutdown { result_sender } => {
494                                if self.state.partial_graphs.values().any(|graph| {
495                                    match graph {
496                                        PartialGraphStatus::Running(graph) => {
497                                            !graph.actor_states.is_empty()
498                                        }
499                                        PartialGraphStatus::Suspended(_) | PartialGraphStatus::Resetting |
500                                            PartialGraphStatus::ReceivedExchangeRequest(_) => {
501                                            false
502                                        }
503                                        PartialGraphStatus::Unspecified => {
504                                            unreachable!()
505                                        }
506                                    }
507                                }) {
508                                    tracing::warn!(
509                                        "shutdown with running actors, scaling or migration will be triggered"
510                                    );
511                                }
512                                self.control_stream_handle.shutdown_stream().await;
513                                let _ = result_sender.send(());
514                            }
515                            actor_op => {
516                                self.handle_actor_op(actor_op);
517                            }
518                        }
519                    }
520                    else {
521                        break;
522                    }
523                },
524                request = self.control_stream_handle.next_request() => {
525                    let result = self.handle_streaming_control_request(request.request.expect("non empty"));
526                    if let Err((partial_graph_id, err)) = result {
527                        self.on_partial_graph_failure(partial_graph_id, None, err, "failed to inject barrier");
528                    }
529                },
530            }
531        }
532    }
533
534    fn handle_streaming_control_request(
535        &mut self,
536        request: Request,
537    ) -> Result<(), (PartialGraphId, StreamError)> {
538        match request {
539            Request::InjectBarrier(req) => {
540                let partial_graph_id = req.partial_graph_id;
541                let result: StreamResult<()> = try {
542                    let barrier = Barrier::from_protobuf(req.get_barrier().unwrap())
543                        .map_err(StreamError::from)?;
544                    self.send_barrier(&barrier, req)?;
545                };
546                result.map_err(|e| (partial_graph_id, e))?;
547                Ok(())
548            }
549            Request::RemovePartialGraph(req) => {
550                self.remove_partial_graphs(req.partial_graph_ids);
551                Ok(())
552            }
553            Request::CreatePartialGraph(req) => {
554                self.add_partial_graph(req.partial_graph_id, req.term_id);
555                Ok(())
556            }
557            Request::ResetPartialGraphs(req) => {
558                self.reset_partial_graphs(req);
559                Ok(())
560            }
561            Request::Init(_) => {
562                unreachable!()
563            }
564        }
565    }
566
567    fn handle_actor_op(&mut self, actor_op: LocalActorOperation) {
568        match actor_op {
569            LocalActorOperation::NewControlStream { .. } | LocalActorOperation::Shutdown { .. } => {
570                unreachable!("event {actor_op} should be handled separately in async context")
571            }
572            LocalActorOperation::TakeReceiver {
573                partial_graph_id,
574                term_id,
575                ids,
576                request,
577            } => {
578                let err = match self.state.partial_graphs.entry(partial_graph_id) {
579                    Entry::Occupied(mut entry) => match entry.get_mut() {
580                        PartialGraphStatus::ReceivedExchangeRequest(pending_requests) => {
581                            pending_requests.push((term_id, ids, request));
582                            return;
583                        }
584                        PartialGraphStatus::Running(graph) => {
585                            if graph.local_barrier_manager.term_id != term_id {
586                                warn!(
587                                    %partial_graph_id,
588                                    ?ids,
589                                    request_term_id = term_id,
590                                    current_term_id = graph.local_barrier_manager.term_id,
591                                    "take receiver on unmatched partial graph term"
592                                );
593                                anyhow!(
594                                    "take receiver {:?} on unmatched partial graph term {} to current term {}",
595                                    ids,
596                                    term_id,
597                                    graph.local_barrier_manager.term_id
598                                )
599                            } else {
600                                let (upstream_actor_id, actor_id) = ids;
601                                graph.new_actor_output_request(
602                                    actor_id,
603                                    upstream_actor_id,
604                                    request,
605                                );
606                                return;
607                            }
608                        }
609                        PartialGraphStatus::Suspended(_) => anyhow!("partial graph suspended"),
610                        PartialGraphStatus::Resetting => anyhow!("partial graph resetting"),
611                        PartialGraphStatus::Unspecified => unreachable!(),
612                    },
613                    Entry::Vacant(entry) => {
614                        entry.insert(PartialGraphStatus::ReceivedExchangeRequest(vec![(
615                            term_id, ids, request,
616                        )]));
617                        return;
618                    }
619                };
620                if let TakeReceiverRequest::Remote { result_sender, .. } = request {
621                    let _ = result_sender.send(Err(err.into()));
622                }
623            }
624            #[cfg(test)]
625            LocalActorOperation::GetCurrentLocalBarrierManager(sender) => {
626                let partial_graph_status = self
627                    .state
628                    .partial_graphs
629                    .get(&crate::task::TEST_PARTIAL_GRAPH_ID)
630                    .unwrap();
631                let partial_graph_state = risingwave_common::must_match!(partial_graph_status, PartialGraphStatus::Running(database_state) => database_state);
632                let _ = sender.send(partial_graph_state.local_barrier_manager.clone());
633            }
634            #[cfg(test)]
635            LocalActorOperation::TakePendingNewOutputRequest(actor_id, sender) => {
636                let partial_graph_status = self
637                    .state
638                    .partial_graphs
639                    .get_mut(&crate::task::TEST_PARTIAL_GRAPH_ID)
640                    .unwrap();
641
642                let partial_graph_state = risingwave_common::must_match!(partial_graph_status, PartialGraphStatus::Running(database_state) => database_state);
643                assert!(!partial_graph_state.actor_states.contains_key(&actor_id));
644                let requests = partial_graph_state
645                    .actor_pending_new_output_requests
646                    .remove(&actor_id)
647                    .unwrap();
648                let _ = sender.send(requests);
649            }
650            #[cfg(test)]
651            LocalActorOperation::Flush(sender) => {
652                use futures::FutureExt;
653                while let Some(request) = self.control_stream_handle.next_request().now_or_never() {
654                    self.handle_streaming_control_request(
655                        request.request.expect("should not be empty"),
656                    )
657                    .unwrap();
658                }
659                while let Some(event) = self.state.next_event().now_or_never() {
660                    match event {
661                        ManagedBarrierStateEvent::BarrierCollected {
662                            barrier,
663                            partial_graph_id,
664                        } => {
665                            self.complete_barrier(partial_graph_id, barrier.epoch.prev);
666                        }
667                        ManagedBarrierStateEvent::ActorError { .. }
668                        | ManagedBarrierStateEvent::PartialGraphsReset { .. }
669                        | ManagedBarrierStateEvent::RegisterLocalUpstreamOutput { .. } => {
670                            unreachable!()
671                        }
672                    }
673                }
674                sender.send(()).unwrap()
675            }
676            LocalActorOperation::InspectState { result_sender } => {
677                let debug_info = self.to_debug_info();
678                let _ = result_sender.send(debug_info.to_string());
679            }
680        }
681    }
682}
683
684mod await_epoch_completed_future {
685    use std::future::Future;
686
687    use futures::FutureExt;
688    use futures::future::BoxFuture;
689    use risingwave_hummock_sdk::SyncResult;
690
691    use crate::error::StreamResult;
692    use crate::executor::Barrier;
693    use crate::task::barrier_worker::managed_state::EpochBarrierEvent;
694    use crate::task::{BarrierCompleteResult, await_tree_key};
695
696    pub(super) type AwaitEpochCompletedFuture =
697        impl Future<Output = (Barrier, StreamResult<BarrierCompleteResult>)> + 'static;
698
699    #[define_opaque(AwaitEpochCompletedFuture)]
700    pub(super) fn instrument_complete_barrier_future(
701        complete_barrier_future: Option<BoxFuture<'static, StreamResult<SyncResult>>>,
702        barrier: Barrier,
703        barrier_await_tree_reg: Option<&await_tree::Registry>,
704        event: EpochBarrierEvent,
705    ) -> AwaitEpochCompletedFuture {
706        let prev_epoch = barrier.epoch.prev;
707        let future = async move {
708            if let Some(future) = complete_barrier_future {
709                let result = future.await;
710                result.map(Some)
711            } else {
712                Ok(None)
713            }
714        }
715        .map(move |result| {
716            (
717                barrier,
718                result.map(|sync_result| BarrierCompleteResult { sync_result, event }),
719            )
720        });
721        if let Some(reg) = barrier_await_tree_reg {
722            reg.register(
723                await_tree_key::BarrierAwait { prev_epoch },
724                format!("SyncEpoch({})", prev_epoch),
725            )
726            .instrument(future)
727            .left_future()
728        } else {
729            future.right_future()
730        }
731    }
732}
733
734use await_epoch_completed_future::*;
735use risingwave_pb::hummock::vector_index_delta::PbVectorIndexAdds;
736use risingwave_storage::{StateStoreImpl, dispatch_state_store};
737
738use crate::executor::exchange::permit;
739
740fn sync_epoch(
741    state_store: &StateStoreImpl,
742    barrier_sync_latency: LabelGuardedHistogram,
743    prev_epoch: u64,
744    table_ids: HashSet<TableId>,
745) -> BoxFuture<'static, StreamResult<SyncResult>> {
746    let timer = barrier_sync_latency.start_timer();
747
748    let state_store = state_store.clone();
749    let future = async move {
750        dispatch_state_store!(state_store, hummock, {
751            hummock.sync(vec![(prev_epoch, table_ids)]).await
752        })
753    };
754
755    future
756        .instrument_await(await_tree::span!("sync_epoch (epoch {})", prev_epoch))
757        .inspect_ok(move |_| {
758            let _guard = &barrier_sync_latency;
759            timer.observe_duration();
760        })
761        .map_err(move |e| {
762            tracing::error!(
763                prev_epoch,
764                error = %e.as_report(),
765                "Failed to sync state store",
766            );
767            e.into()
768        })
769        .boxed()
770}
771
772impl LocalBarrierWorker {
773    fn complete_barrier(&mut self, partial_graph_id: PartialGraphId, prev_epoch: u64) {
774        {
775            let Some(graph_state) = self
776                .state
777                .partial_graphs
778                .get_mut(&partial_graph_id)
779                .expect("should exist")
780                .state_for_request()
781            else {
782                return;
783            };
784            let BarrierToComplete {
785                barrier,
786                table_ids,
787                event,
788            } = graph_state.pop_barrier_to_complete(prev_epoch);
789
790            let complete_barrier_future = match &barrier.kind {
791                BarrierKind::Unspecified => unreachable!(),
792                BarrierKind::Initial => {
793                    tracing::info!(
794                        epoch = prev_epoch,
795                        "ignore sealing data for the first barrier"
796                    );
797                    tracing::info!(?prev_epoch, "ignored syncing data for the first barrier");
798                    None
799                }
800                BarrierKind::Barrier => None,
801                BarrierKind::Checkpoint => Some(sync_epoch(
802                    &self.actor_manager.env.state_store(),
803                    graph_state.graph_state.barrier_sync_latency(),
804                    prev_epoch,
805                    table_ids.expect("should be Some on BarrierKind::Checkpoint"),
806                )),
807            };
808
809            self.await_epoch_completed_futures
810                .entry(partial_graph_id)
811                .or_default()
812                .push_back({
813                    instrument_complete_barrier_future(
814                        complete_barrier_future,
815                        barrier,
816                        self.actor_manager.await_tree_reg.as_ref(),
817                        event,
818                    )
819                });
820        }
821    }
822
823    fn on_epoch_completed(
824        &mut self,
825        partial_graph_id: PartialGraphId,
826        result: BarrierCompleteResult,
827    ) {
828        let BarrierCompleteResult { sync_result, event } = result;
829        let EpochBarrierEvent {
830            epoch: event_epoch,
831            create_mview_progress,
832            table_change_log_progress,
833            list_finished_source_ids,
834            load_finished_source_ids,
835            cdc_table_backfill_progress,
836            cdc_source_offset_updated,
837            iceberg_pk_index_sink_metadata,
838            refresh_finished_actors,
839        } = event;
840        let create_mview_progress = create_mview_progress
841            .into_iter()
842            .map(|(actor, (fragment_id, state))| state.to_pb(fragment_id, actor))
843            .collect();
844        let table_change_log_progress =
845            aggregate_table_change_log_progress(table_change_log_progress);
846        let cdc_table_backfill_progress = cdc_table_backfill_progress
847            .into_iter()
848            .map(|(actor, state)| state.to_pb(actor, event_epoch.curr))
849            .collect();
850
851        let (synced_sstables, table_watermarks, old_value_ssts, vector_index_adds) = sync_result
852            .map(|sync_result| {
853                (
854                    sync_result.uncommitted_ssts,
855                    sync_result.table_watermarks,
856                    sync_result.old_value_ssts,
857                    sync_result.vector_index_adds,
858                )
859            })
860            .unwrap_or_default();
861
862        let result = {
863            {
864                streaming_control_stream_response::Response::CompleteBarrier(
865                    BarrierCompleteResponse {
866                        request_id: "todo".to_owned(),
867                        partial_graph_id,
868                        epoch: event_epoch.prev,
869                        status: None,
870                        create_mview_progress,
871                        table_change_log_progress,
872                        synced_sstables: synced_sstables
873                            .into_iter()
874                            .map(
875                                |LocalSstableInfo {
876                                     sst_info,
877                                     table_stats,
878                                     created_at,
879                                 }| PbLocalSstableInfo {
880                                    sst: Some(sst_info.into()),
881                                    table_stats_map: to_prost_table_stats_map(table_stats),
882                                    created_at,
883                                },
884                            )
885                            .collect_vec(),
886                        worker_id: self.actor_manager.env.worker_id(),
887                        table_watermarks: table_watermarks
888                            .into_iter()
889                            .map(|(key, value)| (key, value.into()))
890                            .collect(),
891                        old_value_sstables: old_value_ssts
892                            .into_iter()
893                            .map(|sst| sst.sst_info.into())
894                            .collect(),
895                        list_finished_sources: list_finished_source_ids,
896                        load_finished_sources: load_finished_source_ids,
897                        cdc_source_offset_updated,
898                        vector_index_adds: vector_index_adds
899                            .into_iter()
900                            .map(|(table_id, adds)| {
901                                (
902                                    table_id,
903                                    PbVectorIndexAdds {
904                                        adds: adds.into_iter().map(|add| add.into()).collect(),
905                                    },
906                                )
907                            })
908                            .collect(),
909                        cdc_table_backfill_progress,
910                        refresh_finished_actors,
911                        iceberg_pk_index_sink_metadata,
912                    },
913                )
914            }
915        };
916
917        self.control_stream_handle.send_response(result);
918    }
919
920    /// Broadcast a barrier to all senders. Save a receiver which will get notified when this
921    /// barrier is finished, in managed mode.
922    ///
923    /// Note that the error returned here is typically a [`StreamError::barrier_send`], which is not
924    /// the root cause of the failure. The caller should then call `try_find_root_failure`
925    /// to find the root cause.
926    fn send_barrier(
927        &mut self,
928        barrier: &Barrier,
929        request: InjectBarrierRequest,
930    ) -> StreamResult<()> {
931        debug!(
932            target: "events::stream::barrier::manager::send",
933            "send barrier {:?}, actor_ids_to_collect = {:?}",
934            barrier,
935            request.actor_ids_to_collect
936        );
937
938        let status = self
939            .state
940            .partial_graphs
941            .get_mut(&request.partial_graph_id)
942            .expect("should exist");
943        if let Some(state) = status.state_for_request() {
944            state.transform_to_issued(barrier, request)?;
945        }
946        Ok(())
947    }
948
949    fn remove_partial_graphs(
950        &mut self,
951        partial_graph_ids: impl IntoIterator<Item = PartialGraphId>,
952    ) {
953        for partial_graph_id in partial_graph_ids {
954            if let Some(mut graph) = self.state.partial_graphs.remove(&partial_graph_id) {
955                if let Some(graph) = graph.state_for_request() {
956                    assert!(
957                        graph.graph_state.is_empty(),
958                        "non empty graph to be removed: {}",
959                        &graph.graph_state
960                    );
961                }
962            } else {
963                warn!(
964                    partial_graph_id = %partial_graph_id,
965                    "no partial graph to remove"
966                );
967            }
968        }
969    }
970
971    fn add_partial_graph(&mut self, partial_graph_id: PartialGraphId, term_id: String) {
972        match self.state.partial_graphs.entry(partial_graph_id) {
973            Entry::Occupied(entry) => {
974                let status = entry.into_mut();
975                if let PartialGraphStatus::ReceivedExchangeRequest(pending_requests) = status {
976                    let mut graph = PartialGraphState::new(
977                        partial_graph_id,
978                        term_id.clone(),
979                        self.actor_manager.clone(),
980                    );
981                    for (request_term_id, (upstream_actor_id, actor_id), request) in
982                        pending_requests.drain(..)
983                    {
984                        if request_term_id == term_id {
985                            graph.new_actor_output_request(actor_id, upstream_actor_id, request);
986                        } else {
987                            warn!(
988                                %partial_graph_id,
989                                %upstream_actor_id,
990                                %actor_id,
991                                request_term_id,
992                                current_term_id = term_id,
993                                "reject buffered exchange request with stale partial graph term"
994                            );
995                            if let TakeReceiverRequest::Remote { result_sender, .. } = request {
996                                let _ = result_sender.send(Err(anyhow!(
997                                    "take receiver ({upstream_actor_id}, {actor_id}) on stale partial graph term {request_term_id}; current term is {term_id}"
998                                )
999                                .into()));
1000                            }
1001                        }
1002                    }
1003                    *status = PartialGraphStatus::Running(graph);
1004                } else {
1005                    panic!("duplicated partial graph: {}", partial_graph_id);
1006                }
1007
1008                status
1009            }
1010            Entry::Vacant(entry) => entry.insert(PartialGraphStatus::Running(
1011                PartialGraphState::new(partial_graph_id, term_id, self.actor_manager.clone()),
1012            )),
1013        };
1014    }
1015
1016    fn reset_partial_graphs(&mut self, req: ResetPartialGraphsRequest) {
1017        let mut table_ids_to_clear = HashSet::new();
1018        let mut reset_futures = HashMap::new();
1019        for partial_graph_id in req.partial_graph_ids {
1020            if let Some(status) = self.state.partial_graphs.get_mut(&partial_graph_id) {
1021                let reset_future = status.start_reset(
1022                    partial_graph_id,
1023                    self.await_epoch_completed_futures.remove(&partial_graph_id),
1024                    &mut table_ids_to_clear,
1025                );
1026                reset_futures.insert(partial_graph_id, reset_future);
1027            } else {
1028                self.ack_partial_graph_reset(partial_graph_id, None);
1029            }
1030        }
1031        if reset_futures.is_empty() {
1032            assert!(table_ids_to_clear.is_empty());
1033            return;
1034        }
1035        let state_store = self.actor_manager.env.state_store();
1036        self.state.resetting_graphs.push(spawn(async move {
1037            let outputs =
1038                join_all(
1039                    reset_futures
1040                        .into_iter()
1041                        .map(|(partial_graph_id, future)| async move {
1042                            (partial_graph_id, future.await)
1043                        }),
1044                )
1045                .await;
1046            if !table_ids_to_clear.is_empty()
1047                && let Some(hummock) = state_store.as_hummock()
1048            {
1049                hummock.clear_tables(table_ids_to_clear).await;
1050            }
1051            outputs
1052        }));
1053    }
1054
1055    fn ack_partial_graph_reset(
1056        &mut self,
1057        partial_graph_id: PartialGraphId,
1058        reset_output: Option<ResetPartialGraphOutput>,
1059    ) {
1060        info!(
1061            %partial_graph_id,
1062            "partial graph reset successfully"
1063        );
1064        assert!(!self.state.partial_graphs.contains_key(&partial_graph_id));
1065        self.await_epoch_completed_futures.remove(&partial_graph_id);
1066        self.control_stream_handle.ack_reset_partial_graph(
1067            partial_graph_id,
1068            reset_output.and_then(|output| output.root_err),
1069        );
1070    }
1071
1072    /// When some other failure happens (like failed to send barrier), the error is reported using
1073    /// this function. The control stream will be responded with a message to notify about the error,
1074    /// and the global barrier worker will later reset and rerun the partial graph.
1075    fn on_partial_graph_failure(
1076        &mut self,
1077        partial_graph_id: PartialGraphId,
1078        failed_actor: Option<ActorId>,
1079        err: StreamError,
1080        message: impl Into<String>,
1081    ) {
1082        let message = message.into();
1083        error!(%partial_graph_id, ?failed_actor, message, err = ?err.as_report(), "suspend partial graph on error");
1084        let completing_futures = self.await_epoch_completed_futures.remove(&partial_graph_id);
1085        self.state
1086            .partial_graphs
1087            .get_mut(&partial_graph_id)
1088            .expect("should exist")
1089            .suspend(failed_actor, err, completing_futures);
1090        self.control_stream_handle
1091            .send_response(Response::ReportPartialGraphFailure(
1092                ReportPartialGraphFailureResponse { partial_graph_id },
1093            ));
1094    }
1095
1096    /// Force stop all actors on this worker, and then drop their resources.
1097    async fn reset(&mut self, _init_request: InitRequest) {
1098        join(
1099            join_all(
1100                self.state
1101                    .partial_graphs
1102                    .values_mut()
1103                    .map(|graph| graph.abort()),
1104            ),
1105            async {
1106                while let Some(join_result) = self.state.resetting_graphs.next().await {
1107                    join_result.expect("failed to join reset partial graphs handle");
1108                }
1109            },
1110        )
1111        .await;
1112        if let Some(m) = self.actor_manager.await_tree_reg.as_ref() {
1113            m.clear();
1114        }
1115
1116        if let Some(hummock) = self.actor_manager.env.state_store().as_hummock() {
1117            hummock
1118                .clear_shared_buffer()
1119                .instrument_await("store_clear_shared_buffer".verbose())
1120                .await
1121        }
1122        self.actor_manager.env.dml_manager_ref().clear();
1123        *self = Self::new(self.actor_manager.clone());
1124        self.actor_manager.env.client_pool().invalidate_all();
1125    }
1126
1127    /// Create a [`LocalBarrierWorker`] with managed mode.
1128    pub fn spawn(
1129        env: StreamEnvironment,
1130        streaming_metrics: Arc<StreamingMetrics>,
1131        await_tree_reg: Option<await_tree::Registry>,
1132        watermark_epoch: AtomicU64Ref,
1133        actor_op_rx: UnboundedReceiver<LocalActorOperation>,
1134    ) -> JoinHandle<()> {
1135        let runtime = {
1136            let mut builder = tokio::runtime::Builder::new_multi_thread();
1137            if let Some(worker_threads_num) = env.global_config().actor_runtime_worker_threads_num {
1138                builder.worker_threads(worker_threads_num);
1139            }
1140            builder
1141                .thread_name("rw-streaming")
1142                .enable_all()
1143                .build()
1144                .unwrap()
1145        };
1146
1147        let actor_manager = Arc::new(StreamActorManager {
1148            env,
1149            streaming_metrics,
1150            watermark_epoch,
1151            await_tree_reg,
1152            runtime: runtime.into(),
1153            config_override_cache: ConfigOverrideCache::new(CONFIG_OVERRIDE_CACHE_DEFAULT_CAPACITY),
1154        });
1155        let worker = LocalBarrierWorker::new(actor_manager);
1156        tokio::spawn(worker.run(actor_op_rx))
1157    }
1158}
1159
1160pub(super) struct EventSender<T>(pub(super) UnboundedSender<T>);
1161
1162impl<T> Clone for EventSender<T> {
1163    fn clone(&self) -> Self {
1164        Self(self.0.clone())
1165    }
1166}
1167
1168impl<T> EventSender<T> {
1169    pub(super) fn send_event(&self, event: T) {
1170        self.0.send(event).expect("should be able to send event")
1171    }
1172
1173    pub(super) async fn send_and_await<RSP>(
1174        &self,
1175        make_event: impl FnOnce(oneshot::Sender<RSP>) -> T,
1176    ) -> StreamResult<RSP> {
1177        let (tx, rx) = oneshot::channel();
1178        let event = make_event(tx);
1179        self.send_event(event);
1180        rx.await
1181            .map_err(|_| anyhow!("barrier manager maybe reset").into())
1182    }
1183}
1184
1185pub(crate) enum NewOutputRequest {
1186    Local(permit::Sender),
1187    Remote(permit::Sender),
1188}
1189
1190#[cfg(test)]
1191pub(crate) mod barrier_test_utils {
1192    use assert_matches::assert_matches;
1193    use futures::StreamExt;
1194    use risingwave_pb::stream_service::streaming_control_stream_request::{
1195        InitRequest, PbCreatePartialGraphRequest,
1196    };
1197    use risingwave_pb::stream_service::{
1198        InjectBarrierRequest, PbStreamingControlStreamRequest, StreamingControlStreamRequest,
1199        StreamingControlStreamResponse, streaming_control_stream_request,
1200        streaming_control_stream_response,
1201    };
1202    use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender, unbounded_channel};
1203    use tokio::sync::oneshot;
1204    use tokio_stream::wrappers::UnboundedReceiverStream;
1205    use tonic::Status;
1206
1207    use crate::executor::Barrier;
1208    use crate::task::barrier_worker::{ControlStreamHandle, EventSender, LocalActorOperation};
1209    use crate::task::{ActorId, LocalBarrierManager, NewOutputRequest, TEST_PARTIAL_GRAPH_ID};
1210
1211    pub(crate) struct LocalBarrierTestEnv {
1212        pub local_barrier_manager: LocalBarrierManager,
1213        pub(super) actor_op_tx: EventSender<LocalActorOperation>,
1214        pub request_tx: UnboundedSender<Result<StreamingControlStreamRequest, Status>>,
1215        pub response_rx: UnboundedReceiver<Result<StreamingControlStreamResponse, Status>>,
1216    }
1217
1218    impl LocalBarrierTestEnv {
1219        pub(crate) async fn for_test() -> Self {
1220            let actor_op_tx = LocalBarrierManager::spawn_for_test();
1221
1222            let (request_tx, request_rx) = unbounded_channel();
1223            let (response_tx, mut response_rx) = unbounded_channel();
1224
1225            request_tx
1226                .send(Ok(PbStreamingControlStreamRequest {
1227                    request: Some(
1228                        streaming_control_stream_request::Request::CreatePartialGraph(
1229                            PbCreatePartialGraphRequest {
1230                                partial_graph_id: TEST_PARTIAL_GRAPH_ID,
1231                                term_id: "for_test".into(),
1232                            },
1233                        ),
1234                    ),
1235                }))
1236                .unwrap();
1237
1238            actor_op_tx.send_event(LocalActorOperation::NewControlStream {
1239                handle: ControlStreamHandle::new(
1240                    response_tx,
1241                    UnboundedReceiverStream::new(request_rx).boxed(),
1242                ),
1243                init_request: InitRequest {},
1244            });
1245
1246            assert_matches!(
1247                response_rx.recv().await.unwrap().unwrap().response.unwrap(),
1248                streaming_control_stream_response::Response::Init(_)
1249            );
1250
1251            let local_barrier_manager = actor_op_tx
1252                .send_and_await(LocalActorOperation::GetCurrentLocalBarrierManager)
1253                .await
1254                .unwrap();
1255
1256            Self {
1257                local_barrier_manager,
1258                actor_op_tx,
1259                request_tx,
1260                response_rx,
1261            }
1262        }
1263
1264        pub(crate) fn inject_barrier(
1265            &self,
1266            barrier: &Barrier,
1267            actor_to_collect: impl IntoIterator<Item = ActorId>,
1268        ) {
1269            self.request_tx
1270                .send(Ok(StreamingControlStreamRequest {
1271                    request: Some(streaming_control_stream_request::Request::InjectBarrier(
1272                        InjectBarrierRequest {
1273                            request_id: "".to_owned(),
1274                            barrier: Some(barrier.to_protobuf()),
1275                            actor_ids_to_collect: actor_to_collect.into_iter().collect(),
1276                            table_ids_to_sync: vec![],
1277                            partial_graph_id: TEST_PARTIAL_GRAPH_ID,
1278                            actors_to_build: vec![],
1279                        },
1280                    )),
1281                }))
1282                .unwrap();
1283        }
1284
1285        pub(crate) async fn flush_all_events(&self) {
1286            Self::flush_all_events_impl(&self.actor_op_tx).await
1287        }
1288
1289        pub(super) async fn flush_all_events_impl(actor_op_tx: &EventSender<LocalActorOperation>) {
1290            let (tx, rx) = oneshot::channel();
1291            actor_op_tx.send_event(LocalActorOperation::Flush(tx));
1292            rx.await.unwrap()
1293        }
1294
1295        pub(crate) async fn take_pending_new_output_requests(
1296            &self,
1297            actor_id: ActorId,
1298        ) -> Vec<(ActorId, NewOutputRequest)> {
1299            self.actor_op_tx
1300                .send_and_await(|tx| LocalActorOperation::TakePendingNewOutputRequest(actor_id, tx))
1301                .await
1302                .unwrap()
1303        }
1304    }
1305}