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