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