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