1use 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
78pub const ENABLE_BARRIER_AGGREGATION: bool = false;
81
82#[derive(Debug)]
84pub struct BarrierCompleteResult {
85 pub sync_result: Option<SyncResult>,
87
88 pub create_mview_progress: Vec<PbCreateMviewProgress>,
90
91 pub list_finished_source_ids: Vec<PbListFinishedSource>,
93
94 pub load_finished_source_ids: Vec<PbLoadFinishedSource>,
96
97 pub cdc_table_backfill_progress: Vec<PbCdcTableBackfillProgress>,
98
99 pub cdc_source_offset_updated: Vec<PbCdcSourceOffsetUpdated>,
101
102 pub iceberg_pk_index_sink_metadata: Vec<PbIcebergPkIndexSinkMetadata>,
104
105 pub truncate_tables: Vec<TableId>,
107 pub refresh_finished_tables: Vec<TableId>,
109}
110
111pub(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 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 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 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)) .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#[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
297pub(super) struct LocalBarrierWorker {
305 pub(super) state: ManagedBarrierState,
307
308 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 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 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 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 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 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 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}