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!("fail to send response. control stream 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    pub(super) term_id: String,
317}
318
319impl LocalBarrierWorker {
320    pub(super) fn new(actor_manager: Arc<StreamActorManager>, term_id: String) -> Self {
321        Self {
322            state: Default::default(),
323            await_epoch_completed_futures: Default::default(),
324            control_stream_handle: ControlStreamHandle::empty(),
325            actor_manager,
326            term_id,
327        }
328    }
329
330    fn to_debug_info(&self) -> LocalBarrierWorkerDebugInfo<'_> {
331        LocalBarrierWorkerDebugInfo {
332            managed_barrier_state: self
333                .state
334                .partial_graphs
335                .iter()
336                .map(|(partial_graph_id, status)| {
337                    (*partial_graph_id, {
338                        match status {
339                            PartialGraphStatus::ReceivedExchangeRequest(_) => {
340                                ("ReceivedExchangeRequest".to_owned(), None)
341                            }
342                            PartialGraphStatus::Running(state) => {
343                                ("running".to_owned(), Some(state.to_debug_info()))
344                            }
345                            PartialGraphStatus::Suspended(state) => {
346                                (format!("suspended: {:?}", state.suspend_time), None)
347                            }
348                            PartialGraphStatus::Resetting => ("resetting".to_owned(), None),
349                            PartialGraphStatus::Unspecified => {
350                                unreachable!()
351                            }
352                        }
353                    })
354                })
355                .collect(),
356            has_control_stream_connected: self.control_stream_handle.connected(),
357        }
358    }
359
360    async fn next_completed_epoch(
361        futures: &mut HashMap<PartialGraphId, FuturesOrdered<AwaitEpochCompletedFuture>>,
362    ) -> (PartialGraphId, Barrier, StreamResult<BarrierCompleteResult>) {
363        poll_fn(|cx| {
364            for (partial_graph_id, futures) in &mut *futures {
365                if let Poll::Ready(Some((barrier, result))) = futures.poll_next_unpin(cx) {
366                    return Poll::Ready((*partial_graph_id, barrier, result));
367                }
368            }
369            Poll::Pending
370        })
371        .await
372    }
373
374    async fn run(mut self, mut actor_op_rx: UnboundedReceiver<LocalActorOperation>) {
375        loop {
376            select! {
377                biased;
378                event = self.state.next_event() => {
379                    match event {
380                        ManagedBarrierStateEvent::BarrierCollected{
381                            partial_graph_id,
382                            barrier,
383                        } => {
384                            // update await_epoch_completed_futures
385                            // handled below in next_completed_epoch
386                            self.complete_barrier(partial_graph_id, barrier.epoch.prev);
387                        }
388                        ManagedBarrierStateEvent::ActorError{
389                            partial_graph_id,
390                            actor_id,
391                            err,
392                        } => {
393                            self.on_partial_graph_failure(partial_graph_id, Some(actor_id), err, "recv actor failure");
394                        }
395                        ManagedBarrierStateEvent::PartialGraphsReset(output) => {
396                            for (partial_graph_id, output) in output {
397                                self.ack_partial_graph_reset(partial_graph_id, Some(output));
398                            }
399                        }
400                        ManagedBarrierStateEvent::RegisterLocalUpstreamOutput{
401                            actor_id,
402                            upstream_actor_id,
403                            upstream_partial_graph_id,
404                            tx
405                        } => {
406                            self.handle_actor_op(LocalActorOperation::TakeReceiver {
407                                partial_graph_id: upstream_partial_graph_id,
408                                term_id: self.term_id.clone(),
409                                ids: (upstream_actor_id, actor_id),
410                                request: TakeReceiverRequest::Local(tx),
411                            });
412                        }
413                    }
414                }
415                (partial_graph_id, barrier, result) = Self::next_completed_epoch(&mut self.await_epoch_completed_futures) => {
416                    match result {
417                        Ok(result) => {
418                            self.on_epoch_completed(partial_graph_id, barrier.epoch.prev, result);
419                        }
420                        Err(err) => {
421                            // TODO: may only report as partial graph failure instead of reset the stream
422                            // when the HummockUploader support partial recovery. Currently the HummockUploader
423                            // enter `Err` state and stop working until a global recovery to clear the uploader.
424                            self.control_stream_handle.reset_stream_with_err(Status::internal(format!("failed to complete epoch: {} {:?} {:?}", partial_graph_id, barrier.epoch, err.as_report())));
425                        }
426                    }
427                },
428                actor_op = actor_op_rx.recv() => {
429                    if let Some(actor_op) = actor_op {
430                        match actor_op {
431                            LocalActorOperation::NewControlStream { handle, init_request  } => {
432                                self.control_stream_handle.reset_stream_with_err(Status::internal("control stream has been reset to a new one"));
433                                self.reset(init_request).await;
434                                self.control_stream_handle = handle;
435                                self.control_stream_handle.send_response(streaming_control_stream_response::Response::Init(InitResponse {}));
436                            }
437                            LocalActorOperation::Shutdown { result_sender } => {
438                                if self.state.partial_graphs.values().any(|graph| {
439                                    match graph {
440                                        PartialGraphStatus::Running(graph) => {
441                                            !graph.actor_states.is_empty()
442                                        }
443                                        PartialGraphStatus::Suspended(_) | PartialGraphStatus::Resetting |
444                                            PartialGraphStatus::ReceivedExchangeRequest(_) => {
445                                            false
446                                        }
447                                        PartialGraphStatus::Unspecified => {
448                                            unreachable!()
449                                        }
450                                    }
451                                }) {
452                                    tracing::warn!(
453                                        "shutdown with running actors, scaling or migration will be triggered"
454                                    );
455                                }
456                                self.control_stream_handle.shutdown_stream().await;
457                                let _ = result_sender.send(());
458                            }
459                            actor_op => {
460                                self.handle_actor_op(actor_op);
461                            }
462                        }
463                    }
464                    else {
465                        break;
466                    }
467                },
468                request = self.control_stream_handle.next_request() => {
469                    let result = self.handle_streaming_control_request(request.request.expect("non empty"));
470                    if let Err((partial_graph_id, err)) = result {
471                        self.on_partial_graph_failure(partial_graph_id, None, err, "failed to inject barrier");
472                    }
473                },
474            }
475        }
476    }
477
478    fn handle_streaming_control_request(
479        &mut self,
480        request: Request,
481    ) -> Result<(), (PartialGraphId, StreamError)> {
482        match request {
483            Request::InjectBarrier(req) => {
484                let partial_graph_id = req.partial_graph_id;
485                let result: StreamResult<()> = try {
486                    let barrier = Barrier::from_protobuf(req.get_barrier().unwrap())
487                        .map_err(StreamError::from)?;
488                    self.send_barrier(&barrier, req)?;
489                };
490                result.map_err(|e| (partial_graph_id, e))?;
491                Ok(())
492            }
493            Request::RemovePartialGraph(req) => {
494                self.remove_partial_graphs(req.partial_graph_ids);
495                Ok(())
496            }
497            Request::CreatePartialGraph(req) => {
498                self.add_partial_graph(req.partial_graph_id);
499                Ok(())
500            }
501            Request::ResetPartialGraphs(req) => {
502                self.reset_partial_graphs(req);
503                Ok(())
504            }
505            Request::Init(_) => {
506                unreachable!()
507            }
508        }
509    }
510
511    fn handle_actor_op(&mut self, actor_op: LocalActorOperation) {
512        match actor_op {
513            LocalActorOperation::NewControlStream { .. } | LocalActorOperation::Shutdown { .. } => {
514                unreachable!("event {actor_op} should be handled separately in async context")
515            }
516            LocalActorOperation::TakeReceiver {
517                partial_graph_id,
518                term_id,
519                ids,
520                request,
521            } => {
522                let err = if self.term_id != term_id {
523                    {
524                        warn!(
525                            ?ids,
526                            term_id,
527                            current_term_id = self.term_id,
528                            "take receiver on unmatched term_id"
529                        );
530                        anyhow!(
531                            "take receiver {:?} on unmatched term_id {} to current term_id {}",
532                            ids,
533                            term_id,
534                            self.term_id
535                        )
536                    }
537                } else {
538                    match self.state.partial_graphs.entry(partial_graph_id) {
539                        Entry::Occupied(mut entry) => match entry.get_mut() {
540                            PartialGraphStatus::ReceivedExchangeRequest(pending_requests) => {
541                                pending_requests.push((ids, request));
542                                return;
543                            }
544                            PartialGraphStatus::Running(graph) => {
545                                let (upstream_actor_id, actor_id) = ids;
546                                graph.new_actor_output_request(
547                                    actor_id,
548                                    upstream_actor_id,
549                                    request,
550                                );
551                                return;
552                            }
553                            PartialGraphStatus::Suspended(_) => {
554                                anyhow!("partial graph suspended")
555                            }
556                            PartialGraphStatus::Resetting => {
557                                anyhow!("partial graph resetting")
558                            }
559                            PartialGraphStatus::Unspecified => {
560                                unreachable!()
561                            }
562                        },
563                        Entry::Vacant(entry) => {
564                            entry.insert(PartialGraphStatus::ReceivedExchangeRequest(vec![(
565                                ids, request,
566                            )]));
567                            return;
568                        }
569                    }
570                };
571                if let TakeReceiverRequest::Remote { result_sender, .. } = request {
572                    let _ = result_sender.send(Err(err.into()));
573                }
574            }
575            #[cfg(test)]
576            LocalActorOperation::GetCurrentLocalBarrierManager(sender) => {
577                let partial_graph_status = self
578                    .state
579                    .partial_graphs
580                    .get(&crate::task::TEST_PARTIAL_GRAPH_ID)
581                    .unwrap();
582                let partial_graph_state = risingwave_common::must_match!(partial_graph_status, PartialGraphStatus::Running(database_state) => database_state);
583                let _ = sender.send(partial_graph_state.local_barrier_manager.clone());
584            }
585            #[cfg(test)]
586            LocalActorOperation::TakePendingNewOutputRequest(actor_id, sender) => {
587                let partial_graph_status = self
588                    .state
589                    .partial_graphs
590                    .get_mut(&crate::task::TEST_PARTIAL_GRAPH_ID)
591                    .unwrap();
592
593                let partial_graph_state = risingwave_common::must_match!(partial_graph_status, PartialGraphStatus::Running(database_state) => database_state);
594                assert!(!partial_graph_state.actor_states.contains_key(&actor_id));
595                let requests = partial_graph_state
596                    .actor_pending_new_output_requests
597                    .remove(&actor_id)
598                    .unwrap();
599                let _ = sender.send(requests);
600            }
601            #[cfg(test)]
602            LocalActorOperation::Flush(sender) => {
603                use futures::FutureExt;
604                while let Some(request) = self.control_stream_handle.next_request().now_or_never() {
605                    self.handle_streaming_control_request(
606                        request.request.expect("should not be empty"),
607                    )
608                    .unwrap();
609                }
610                while let Some(event) = self.state.next_event().now_or_never() {
611                    match event {
612                        ManagedBarrierStateEvent::BarrierCollected {
613                            barrier,
614                            partial_graph_id,
615                        } => {
616                            self.complete_barrier(partial_graph_id, barrier.epoch.prev);
617                        }
618                        ManagedBarrierStateEvent::ActorError { .. }
619                        | ManagedBarrierStateEvent::PartialGraphsReset { .. }
620                        | ManagedBarrierStateEvent::RegisterLocalUpstreamOutput { .. } => {
621                            unreachable!()
622                        }
623                    }
624                }
625                sender.send(()).unwrap()
626            }
627            LocalActorOperation::InspectState { result_sender } => {
628                let debug_info = self.to_debug_info();
629                let _ = result_sender.send(debug_info.to_string());
630            }
631        }
632    }
633}
634
635mod await_epoch_completed_future {
636    use std::future::Future;
637
638    use futures::FutureExt;
639    use futures::future::BoxFuture;
640    use risingwave_common::id::TableId;
641    use risingwave_hummock_sdk::SyncResult;
642    use risingwave_pb::stream_service::barrier_complete_response::{
643        PbCdcSourceOffsetUpdated, PbCdcTableBackfillProgress, PbCreateMviewProgress,
644        PbIcebergPkIndexSinkMetadata, PbListFinishedSource, PbLoadFinishedSource,
645    };
646
647    use crate::error::StreamResult;
648    use crate::executor::Barrier;
649    use crate::task::{BarrierCompleteResult, await_tree_key};
650
651    pub(super) type AwaitEpochCompletedFuture =
652        impl Future<Output = (Barrier, StreamResult<BarrierCompleteResult>)> + 'static;
653
654    #[define_opaque(AwaitEpochCompletedFuture)]
655    #[expect(clippy::too_many_arguments)]
656    pub(super) fn instrument_complete_barrier_future(
657        complete_barrier_future: Option<BoxFuture<'static, StreamResult<SyncResult>>>,
658        barrier: Barrier,
659        barrier_await_tree_reg: Option<&await_tree::Registry>,
660        create_mview_progress: Vec<PbCreateMviewProgress>,
661        list_finished_source_ids: Vec<PbListFinishedSource>,
662        load_finished_source_ids: Vec<PbLoadFinishedSource>,
663        cdc_table_backfill_progress: Vec<PbCdcTableBackfillProgress>,
664        cdc_source_offset_updated: Vec<PbCdcSourceOffsetUpdated>,
665        iceberg_pk_index_sink_metadata: Vec<PbIcebergPkIndexSinkMetadata>,
666        truncate_tables: Vec<TableId>,
667        refresh_finished_tables: Vec<TableId>,
668    ) -> AwaitEpochCompletedFuture {
669        let prev_epoch = barrier.epoch.prev;
670        let future = async move {
671            if let Some(future) = complete_barrier_future {
672                let result = future.await;
673                result.map(Some)
674            } else {
675                Ok(None)
676            }
677        }
678        .map(move |result| {
679            (
680                barrier,
681                result.map(|sync_result| BarrierCompleteResult {
682                    sync_result,
683                    create_mview_progress,
684                    list_finished_source_ids,
685                    load_finished_source_ids,
686                    cdc_table_backfill_progress,
687                    cdc_source_offset_updated,
688                    iceberg_pk_index_sink_metadata,
689                    truncate_tables,
690                    refresh_finished_tables,
691                }),
692            )
693        });
694        if let Some(reg) = barrier_await_tree_reg {
695            reg.register(
696                await_tree_key::BarrierAwait { prev_epoch },
697                format!("SyncEpoch({})", prev_epoch),
698            )
699            .instrument(future)
700            .left_future()
701        } else {
702            future.right_future()
703        }
704    }
705}
706
707use await_epoch_completed_future::*;
708use risingwave_common::catalog::TableId;
709use risingwave_pb::hummock::vector_index_delta::PbVectorIndexAdds;
710use risingwave_storage::{StateStoreImpl, dispatch_state_store};
711
712use crate::executor::exchange::permit;
713
714fn sync_epoch(
715    state_store: &StateStoreImpl,
716    barrier_sync_latency: LabelGuardedHistogram,
717    prev_epoch: u64,
718    table_ids: HashSet<TableId>,
719) -> BoxFuture<'static, StreamResult<SyncResult>> {
720    let timer = barrier_sync_latency.start_timer();
721
722    let state_store = state_store.clone();
723    let future = async move {
724        dispatch_state_store!(state_store, hummock, {
725            hummock.sync(vec![(prev_epoch, table_ids)]).await
726        })
727    };
728
729    future
730        .instrument_await(await_tree::span!("sync_epoch (epoch {})", prev_epoch))
731        .inspect_ok(move |_| {
732            let _guard = &barrier_sync_latency;
733            timer.observe_duration();
734        })
735        .map_err(move |e| {
736            tracing::error!(
737                prev_epoch,
738                error = %e.as_report(),
739                "Failed to sync state store",
740            );
741            e.into()
742        })
743        .boxed()
744}
745
746impl LocalBarrierWorker {
747    fn complete_barrier(&mut self, partial_graph_id: PartialGraphId, prev_epoch: u64) {
748        {
749            let Some(graph_state) = self
750                .state
751                .partial_graphs
752                .get_mut(&partial_graph_id)
753                .expect("should exist")
754                .state_for_request()
755            else {
756                return;
757            };
758            let BarrierToComplete {
759                barrier,
760                table_ids,
761                create_mview_progress,
762                list_finished_source_ids,
763                load_finished_source_ids,
764                cdc_table_backfill_progress,
765                cdc_source_offset_updated,
766                iceberg_pk_index_sink_metadata,
767                truncate_tables,
768                refresh_finished_tables,
769            } = graph_state.pop_barrier_to_complete(prev_epoch);
770
771            let complete_barrier_future = match &barrier.kind {
772                BarrierKind::Unspecified => unreachable!(),
773                BarrierKind::Initial => {
774                    tracing::info!(
775                        epoch = prev_epoch,
776                        "ignore sealing data for the first barrier"
777                    );
778                    tracing::info!(?prev_epoch, "ignored syncing data for the first barrier");
779                    None
780                }
781                BarrierKind::Barrier => None,
782                BarrierKind::Checkpoint => Some(sync_epoch(
783                    &self.actor_manager.env.state_store(),
784                    graph_state.graph_state.barrier_sync_latency(),
785                    prev_epoch,
786                    table_ids.expect("should be Some on BarrierKind::Checkpoint"),
787                )),
788            };
789
790            self.await_epoch_completed_futures
791                .entry(partial_graph_id)
792                .or_default()
793                .push_back({
794                    instrument_complete_barrier_future(
795                        complete_barrier_future,
796                        barrier,
797                        self.actor_manager.await_tree_reg.as_ref(),
798                        create_mview_progress,
799                        list_finished_source_ids,
800                        load_finished_source_ids,
801                        cdc_table_backfill_progress,
802                        cdc_source_offset_updated,
803                        iceberg_pk_index_sink_metadata,
804                        truncate_tables,
805                        refresh_finished_tables,
806                    )
807                });
808        }
809    }
810
811    fn on_epoch_completed(
812        &mut self,
813        partial_graph_id: PartialGraphId,
814        epoch: u64,
815        result: BarrierCompleteResult,
816    ) {
817        let BarrierCompleteResult {
818            create_mview_progress,
819            sync_result,
820            list_finished_source_ids,
821            load_finished_source_ids,
822            cdc_table_backfill_progress,
823            cdc_source_offset_updated,
824            iceberg_pk_index_sink_metadata,
825            truncate_tables,
826            refresh_finished_tables,
827        } = result;
828
829        let (synced_sstables, table_watermarks, old_value_ssts, vector_index_adds) = sync_result
830            .map(|sync_result| {
831                (
832                    sync_result.uncommitted_ssts,
833                    sync_result.table_watermarks,
834                    sync_result.old_value_ssts,
835                    sync_result.vector_index_adds,
836                )
837            })
838            .unwrap_or_default();
839
840        let result = {
841            {
842                streaming_control_stream_response::Response::CompleteBarrier(
843                    BarrierCompleteResponse {
844                        request_id: "todo".to_owned(),
845                        partial_graph_id,
846                        epoch,
847                        status: None,
848                        create_mview_progress,
849                        synced_sstables: synced_sstables
850                            .into_iter()
851                            .map(
852                                |LocalSstableInfo {
853                                     sst_info,
854                                     table_stats,
855                                     created_at,
856                                 }| PbLocalSstableInfo {
857                                    sst: Some(sst_info.into()),
858                                    table_stats_map: to_prost_table_stats_map(table_stats),
859                                    created_at,
860                                },
861                            )
862                            .collect_vec(),
863                        worker_id: self.actor_manager.env.worker_id(),
864                        table_watermarks: table_watermarks
865                            .into_iter()
866                            .map(|(key, value)| (key, value.into()))
867                            .collect(),
868                        old_value_sstables: old_value_ssts
869                            .into_iter()
870                            .map(|sst| sst.sst_info.into())
871                            .collect(),
872                        list_finished_sources: list_finished_source_ids,
873                        load_finished_sources: load_finished_source_ids,
874                        cdc_source_offset_updated,
875                        vector_index_adds: vector_index_adds
876                            .into_iter()
877                            .map(|(table_id, adds)| {
878                                (
879                                    table_id,
880                                    PbVectorIndexAdds {
881                                        adds: adds.into_iter().map(|add| add.into()).collect(),
882                                    },
883                                )
884                            })
885                            .collect(),
886                        cdc_table_backfill_progress,
887                        truncate_tables,
888                        refresh_finished_tables,
889                        iceberg_pk_index_sink_metadata,
890                    },
891                )
892            }
893        };
894
895        self.control_stream_handle.send_response(result);
896    }
897
898    /// Broadcast a barrier to all senders. Save a receiver which will get notified when this
899    /// barrier is finished, in managed mode.
900    ///
901    /// Note that the error returned here is typically a [`StreamError::barrier_send`], which is not
902    /// the root cause of the failure. The caller should then call `try_find_root_failure`
903    /// to find the root cause.
904    fn send_barrier(
905        &mut self,
906        barrier: &Barrier,
907        request: InjectBarrierRequest,
908    ) -> StreamResult<()> {
909        debug!(
910            target: "events::stream::barrier::manager::send",
911            "send barrier {:?}, actor_ids_to_collect = {:?}",
912            barrier,
913            request.actor_ids_to_collect
914        );
915
916        let status = self
917            .state
918            .partial_graphs
919            .get_mut(&request.partial_graph_id)
920            .expect("should exist");
921        if let Some(state) = status.state_for_request() {
922            state.transform_to_issued(barrier, request)?;
923        }
924        Ok(())
925    }
926
927    fn remove_partial_graphs(
928        &mut self,
929        partial_graph_ids: impl IntoIterator<Item = PartialGraphId>,
930    ) {
931        for partial_graph_id in partial_graph_ids {
932            if let Some(mut graph) = self.state.partial_graphs.remove(&partial_graph_id) {
933                if let Some(graph) = graph.state_for_request() {
934                    assert!(
935                        graph.graph_state.is_empty(),
936                        "non empty graph to be removed: {}",
937                        &graph.graph_state
938                    );
939                }
940            } else {
941                warn!(
942                    partial_graph_id = %partial_graph_id,
943                    "no partial graph to remove"
944                );
945            }
946        }
947    }
948
949    fn add_partial_graph(&mut self, partial_graph_id: PartialGraphId) {
950        match self.state.partial_graphs.entry(partial_graph_id) {
951            Entry::Occupied(entry) => {
952                let status = entry.into_mut();
953                if let PartialGraphStatus::ReceivedExchangeRequest(pending_requests) = status {
954                    let mut graph = PartialGraphState::new(
955                        partial_graph_id,
956                        self.term_id.clone(),
957                        self.actor_manager.clone(),
958                    );
959                    for ((upstream_actor_id, actor_id), request) in pending_requests.drain(..) {
960                        graph.new_actor_output_request(actor_id, upstream_actor_id, request);
961                    }
962                    *status = PartialGraphStatus::Running(graph);
963                } else {
964                    panic!("duplicated partial graph: {}", partial_graph_id);
965                }
966
967                status
968            }
969            Entry::Vacant(entry) => {
970                entry.insert(PartialGraphStatus::Running(PartialGraphState::new(
971                    partial_graph_id,
972                    self.term_id.clone(),
973                    self.actor_manager.clone(),
974                )))
975            }
976        };
977    }
978
979    fn reset_partial_graphs(&mut self, req: ResetPartialGraphsRequest) {
980        let mut table_ids_to_clear = HashSet::new();
981        let mut reset_futures = HashMap::new();
982        for partial_graph_id in req.partial_graph_ids {
983            if let Some(status) = self.state.partial_graphs.get_mut(&partial_graph_id) {
984                let reset_future = status.start_reset(
985                    partial_graph_id,
986                    self.await_epoch_completed_futures.remove(&partial_graph_id),
987                    &mut table_ids_to_clear,
988                );
989                reset_futures.insert(partial_graph_id, reset_future);
990            } else {
991                self.ack_partial_graph_reset(partial_graph_id, None);
992            }
993        }
994        if reset_futures.is_empty() {
995            assert!(table_ids_to_clear.is_empty());
996            return;
997        }
998        let state_store = self.actor_manager.env.state_store();
999        self.state.resetting_graphs.push(spawn(async move {
1000            let outputs =
1001                join_all(
1002                    reset_futures
1003                        .into_iter()
1004                        .map(|(partial_graph_id, future)| async move {
1005                            (partial_graph_id, future.await)
1006                        }),
1007                )
1008                .await;
1009            if !table_ids_to_clear.is_empty()
1010                && let Some(hummock) = state_store.as_hummock()
1011            {
1012                hummock.clear_tables(table_ids_to_clear).await;
1013            }
1014            outputs
1015        }));
1016    }
1017
1018    fn ack_partial_graph_reset(
1019        &mut self,
1020        partial_graph_id: PartialGraphId,
1021        reset_output: Option<ResetPartialGraphOutput>,
1022    ) {
1023        info!(
1024            %partial_graph_id,
1025            "partial graph reset successfully"
1026        );
1027        assert!(!self.state.partial_graphs.contains_key(&partial_graph_id));
1028        self.await_epoch_completed_futures.remove(&partial_graph_id);
1029        self.control_stream_handle.ack_reset_partial_graph(
1030            partial_graph_id,
1031            reset_output.and_then(|output| output.root_err),
1032        );
1033    }
1034
1035    /// When some other failure happens (like failed to send barrier), the error is reported using
1036    /// this function. The control stream will be responded with a message to notify about the error,
1037    /// and the global barrier worker will later reset and rerun the partial graph.
1038    fn on_partial_graph_failure(
1039        &mut self,
1040        partial_graph_id: PartialGraphId,
1041        failed_actor: Option<ActorId>,
1042        err: StreamError,
1043        message: impl Into<String>,
1044    ) {
1045        let message = message.into();
1046        error!(%partial_graph_id, ?failed_actor, message, err = ?err.as_report(), "suspend partial graph on error");
1047        let completing_futures = self.await_epoch_completed_futures.remove(&partial_graph_id);
1048        self.state
1049            .partial_graphs
1050            .get_mut(&partial_graph_id)
1051            .expect("should exist")
1052            .suspend(failed_actor, err, completing_futures);
1053        self.control_stream_handle
1054            .send_response(Response::ReportPartialGraphFailure(
1055                ReportPartialGraphFailureResponse { partial_graph_id },
1056            ));
1057    }
1058
1059    /// Force stop all actors on this worker, and then drop their resources.
1060    async fn reset(&mut self, init_request: InitRequest) {
1061        join(
1062            join_all(
1063                self.state
1064                    .partial_graphs
1065                    .values_mut()
1066                    .map(|graph| graph.abort()),
1067            ),
1068            async {
1069                while let Some(join_result) = self.state.resetting_graphs.next().await {
1070                    join_result.expect("failed to join reset partial graphs handle");
1071                }
1072            },
1073        )
1074        .await;
1075        if let Some(m) = self.actor_manager.await_tree_reg.as_ref() {
1076            m.clear();
1077        }
1078
1079        if let Some(hummock) = self.actor_manager.env.state_store().as_hummock() {
1080            hummock
1081                .clear_shared_buffer()
1082                .instrument_await("store_clear_shared_buffer".verbose())
1083                .await
1084        }
1085        self.actor_manager.env.dml_manager_ref().clear();
1086        *self = Self::new(self.actor_manager.clone(), init_request.term_id);
1087        self.actor_manager.env.client_pool().invalidate_all();
1088    }
1089
1090    /// Create a [`LocalBarrierWorker`] with managed mode.
1091    pub fn spawn(
1092        env: StreamEnvironment,
1093        streaming_metrics: Arc<StreamingMetrics>,
1094        await_tree_reg: Option<await_tree::Registry>,
1095        watermark_epoch: AtomicU64Ref,
1096        actor_op_rx: UnboundedReceiver<LocalActorOperation>,
1097    ) -> JoinHandle<()> {
1098        let runtime = {
1099            let mut builder = tokio::runtime::Builder::new_multi_thread();
1100            if let Some(worker_threads_num) = env.global_config().actor_runtime_worker_threads_num {
1101                builder.worker_threads(worker_threads_num);
1102            }
1103            builder
1104                .thread_name("rw-streaming")
1105                .enable_all()
1106                .build()
1107                .unwrap()
1108        };
1109
1110        let actor_manager = Arc::new(StreamActorManager {
1111            env,
1112            streaming_metrics,
1113            watermark_epoch,
1114            await_tree_reg,
1115            runtime: runtime.into(),
1116            config_override_cache: ConfigOverrideCache::new(CONFIG_OVERRIDE_CACHE_DEFAULT_CAPACITY),
1117        });
1118        let worker = LocalBarrierWorker::new(actor_manager, "uninitialized".into());
1119        tokio::spawn(worker.run(actor_op_rx))
1120    }
1121}
1122
1123pub(super) struct EventSender<T>(pub(super) UnboundedSender<T>);
1124
1125impl<T> Clone for EventSender<T> {
1126    fn clone(&self) -> Self {
1127        Self(self.0.clone())
1128    }
1129}
1130
1131impl<T> EventSender<T> {
1132    pub(super) fn send_event(&self, event: T) {
1133        self.0.send(event).expect("should be able to send event")
1134    }
1135
1136    pub(super) async fn send_and_await<RSP>(
1137        &self,
1138        make_event: impl FnOnce(oneshot::Sender<RSP>) -> T,
1139    ) -> StreamResult<RSP> {
1140        let (tx, rx) = oneshot::channel();
1141        let event = make_event(tx);
1142        self.send_event(event);
1143        rx.await
1144            .map_err(|_| anyhow!("barrier manager maybe reset").into())
1145    }
1146}
1147
1148pub(crate) enum NewOutputRequest {
1149    Local(permit::Sender),
1150    Remote(permit::Sender),
1151}
1152
1153#[cfg(test)]
1154pub(crate) mod barrier_test_utils {
1155    use assert_matches::assert_matches;
1156    use futures::StreamExt;
1157    use risingwave_pb::stream_service::streaming_control_stream_request::{
1158        InitRequest, PbCreatePartialGraphRequest,
1159    };
1160    use risingwave_pb::stream_service::{
1161        InjectBarrierRequest, PbStreamingControlStreamRequest, StreamingControlStreamRequest,
1162        StreamingControlStreamResponse, streaming_control_stream_request,
1163        streaming_control_stream_response,
1164    };
1165    use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender, unbounded_channel};
1166    use tokio::sync::oneshot;
1167    use tokio_stream::wrappers::UnboundedReceiverStream;
1168    use tonic::Status;
1169
1170    use crate::executor::Barrier;
1171    use crate::task::barrier_worker::{ControlStreamHandle, EventSender, LocalActorOperation};
1172    use crate::task::{ActorId, LocalBarrierManager, NewOutputRequest, TEST_PARTIAL_GRAPH_ID};
1173
1174    pub(crate) struct LocalBarrierTestEnv {
1175        pub local_barrier_manager: LocalBarrierManager,
1176        pub(super) actor_op_tx: EventSender<LocalActorOperation>,
1177        pub request_tx: UnboundedSender<Result<StreamingControlStreamRequest, Status>>,
1178        pub response_rx: UnboundedReceiver<Result<StreamingControlStreamResponse, Status>>,
1179    }
1180
1181    impl LocalBarrierTestEnv {
1182        pub(crate) async fn for_test() -> Self {
1183            let actor_op_tx = LocalBarrierManager::spawn_for_test();
1184
1185            let (request_tx, request_rx) = unbounded_channel();
1186            let (response_tx, mut response_rx) = unbounded_channel();
1187
1188            request_tx
1189                .send(Ok(PbStreamingControlStreamRequest {
1190                    request: Some(
1191                        streaming_control_stream_request::Request::CreatePartialGraph(
1192                            PbCreatePartialGraphRequest {
1193                                partial_graph_id: TEST_PARTIAL_GRAPH_ID,
1194                            },
1195                        ),
1196                    ),
1197                }))
1198                .unwrap();
1199
1200            actor_op_tx.send_event(LocalActorOperation::NewControlStream {
1201                handle: ControlStreamHandle::new(
1202                    response_tx,
1203                    UnboundedReceiverStream::new(request_rx).boxed(),
1204                ),
1205                init_request: InitRequest {
1206                    term_id: "for_test".into(),
1207                },
1208            });
1209
1210            assert_matches!(
1211                response_rx.recv().await.unwrap().unwrap().response.unwrap(),
1212                streaming_control_stream_response::Response::Init(_)
1213            );
1214
1215            let local_barrier_manager = actor_op_tx
1216                .send_and_await(LocalActorOperation::GetCurrentLocalBarrierManager)
1217                .await
1218                .unwrap();
1219
1220            Self {
1221                local_barrier_manager,
1222                actor_op_tx,
1223                request_tx,
1224                response_rx,
1225            }
1226        }
1227
1228        pub(crate) fn inject_barrier(
1229            &self,
1230            barrier: &Barrier,
1231            actor_to_collect: impl IntoIterator<Item = ActorId>,
1232        ) {
1233            self.request_tx
1234                .send(Ok(StreamingControlStreamRequest {
1235                    request: Some(streaming_control_stream_request::Request::InjectBarrier(
1236                        InjectBarrierRequest {
1237                            request_id: "".to_owned(),
1238                            barrier: Some(barrier.to_protobuf()),
1239                            actor_ids_to_collect: actor_to_collect.into_iter().collect(),
1240                            table_ids_to_sync: vec![],
1241                            partial_graph_id: TEST_PARTIAL_GRAPH_ID,
1242                            actors_to_build: vec![],
1243                        },
1244                    )),
1245                }))
1246                .unwrap();
1247        }
1248
1249        pub(crate) async fn flush_all_events(&self) {
1250            Self::flush_all_events_impl(&self.actor_op_tx).await
1251        }
1252
1253        pub(super) async fn flush_all_events_impl(actor_op_tx: &EventSender<LocalActorOperation>) {
1254            let (tx, rx) = oneshot::channel();
1255            actor_op_tx.send_event(LocalActorOperation::Flush(tx));
1256            rx.await.unwrap()
1257        }
1258
1259        pub(crate) async fn take_pending_new_output_requests(
1260            &self,
1261            actor_id: ActorId,
1262        ) -> Vec<(ActorId, NewOutputRequest)> {
1263            self.actor_op_tx
1264                .send_and_await(|tx| LocalActorOperation::TakePendingNewOutputRequest(actor_id, tx))
1265                .await
1266                .unwrap()
1267        }
1268    }
1269}