1use std::collections::{HashMap, HashSet};
16use std::pin::pin;
17use std::sync::Arc;
18
19use anyhow::anyhow;
20use futures::future::{BoxFuture, Either, select};
21use futures::stream::FuturesUnordered;
22use futures::{FutureExt, Stream, StreamExt, TryStreamExt};
23use risingwave_common::bitmap::Bitmap;
24use risingwave_common::id::{DatabaseId, JobId};
25use risingwave_connector::connector_common::IcebergSinkCompactionUpdate;
26use risingwave_connector::sink::catalog::SinkId;
27use risingwave_connector::sink::{SinkCommittedEpochSubscriber, SinkError, SinkParam};
28use risingwave_pb::connector_service::coordinate_request::Msg;
29use risingwave_pb::connector_service::{CoordinateRequest, CoordinateResponse, coordinate_request};
30use rw_futures_util::pending_on_none;
31use sea_orm::DatabaseConnection;
32use thiserror_ext::AsReport;
33use tokio::sync::mpsc;
34use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender, unbounded_channel};
35use tokio::sync::oneshot::{Receiver, Sender, channel};
36use tokio::task::{JoinError, JoinHandle};
37use tokio_stream::wrappers::UnboundedReceiverStream;
38use tonic::Status;
39use tracing::{error, info, warn};
40
41use crate::hummock::HummockManagerRef;
42use crate::manager::MetadataManager;
43use crate::manager::sink_coordination::SinkWriterRequestStream;
44use crate::manager::sink_coordination::coordinator_worker::CoordinatorWorker;
45use crate::manager::sink_coordination::handle::SinkWriterCoordinationHandle;
46use crate::notification::{
47 CollectionNotifier, Notifier, NotifierStarter, StartReceiver, wait_collection,
48};
49
50macro_rules! send_with_err_check {
51 ($tx:expr, $msg:expr) => {
52 if $tx.send($msg).is_err() {
53 error!("unable to send msg");
54 }
55 };
56}
57
58const BOUNDED_CHANNEL_SIZE: usize = 16;
59
60enum ManagerRequest {
61 NewSinkWriter(SinkWriterCoordinationHandle),
62 StopCoordinator {
63 notifier: Notifier,
64 job_ids: Vec<JobId>,
66 },
67 RecoveryStart {
68 recovery: RecoveryStart,
69 notifier: Notifier,
70 },
71 RecoverySucceeded {
72 recovery: RecoverySucceeded,
73 notifier: Notifier,
74 },
75}
76
77#[derive(Debug)]
78pub enum RecoveryStart {
79 Global,
80 Database {
81 database_id: DatabaseId,
82 job_ids: HashSet<JobId>,
83 },
84}
85
86#[derive(Debug)]
87pub enum RecoverySucceeded {
88 Global {
89 failed_databases: HashMap<DatabaseId, HashSet<JobId>>,
90 },
91 Database(DatabaseId),
92}
93
94#[derive(Debug)]
95enum RecoveryFence {
96 Global,
97 Databases(HashMap<DatabaseId, HashSet<JobId>>),
98}
99
100#[derive(Clone)]
101pub struct SinkCoordinatorManager {
102 request_tx: mpsc::Sender<ManagerRequest>,
103}
104fn new_committed_epoch_subscriber(
105 hummock_manager: HummockManagerRef,
106 metadata_manager: MetadataManager,
107) -> SinkCommittedEpochSubscriber {
108 Arc::new(move |sink_id| {
109 let hummock_manager = hummock_manager.clone();
110 let metadata_manager = metadata_manager.clone();
111 async move {
112 let state_table_ids = metadata_manager
113 .get_sink_state_table_ids(sink_id)
114 .await
115 .map_err(SinkError::from)?;
116 let Some(table_id) = state_table_ids.first() else {
117 return Err(anyhow!("no state table id in sink: {}", sink_id).into());
118 };
119 hummock_manager
120 .subscribe_table_committed_epoch(*table_id)
121 .await
122 .map_err(SinkError::from)
123 }
124 .boxed()
125 })
126}
127
128impl SinkCoordinatorManager {
129 pub fn start_worker(
130 db: DatabaseConnection,
131 hummock_manager: HummockManagerRef,
132 metadata_manager: MetadataManager,
133 iceberg_compact_stat_sender: UnboundedSender<IcebergSinkCompactionUpdate>,
134 await_tree_reg: await_tree::Registry,
135 ) -> (Self, (JoinHandle<()>, Sender<()>)) {
136 let subscriber = new_committed_epoch_subscriber(hummock_manager, metadata_manager);
137 Self::start_worker_with_spawn_worker({
138 move |param, manager_request_stream| {
139 let sink_id = param.sink_id;
140 let fut = CoordinatorWorker::run(
141 param,
142 manager_request_stream,
143 db.clone(),
144 subscriber.clone(),
145 iceberg_compact_stat_sender.clone(),
146 );
147 tokio::spawn(
148 await_tree_reg
149 .register_derived_root(format!("Sink Coordinator {sink_id}"))
150 .instrument(fut),
151 )
152 }
153 })
154 }
155
156 fn start_worker_with_spawn_worker(
157 spawn_coordinator_worker: impl SpawnCoordinatorFn,
158 ) -> (Self, (JoinHandle<()>, Sender<()>)) {
159 let (request_tx, request_rx) = mpsc::channel(BOUNDED_CHANNEL_SIZE);
160 let (shutdown_tx, shutdown_rx) = channel();
161 let worker = ManagerWorker::new(request_rx, shutdown_rx);
162 let join_handle = tokio::spawn(worker.execute(spawn_coordinator_worker));
163 (
164 SinkCoordinatorManager { request_tx },
165 (join_handle, shutdown_tx),
166 )
167 }
168
169 pub async fn handle_new_request(
170 &self,
171 mut request_stream: SinkWriterRequestStream,
172 ) -> Result<impl Stream<Item = Result<CoordinateResponse, Status>> + use<>, Status> {
173 let (param, vnode_bitmap) = match request_stream.try_next().await? {
174 Some(CoordinateRequest {
175 msg:
176 Some(Msg::StartRequest(coordinate_request::StartCoordinationRequest {
177 param: Some(param),
178 vnode_bitmap: Some(vnode_bitmap),
179 })),
180 }) => (SinkParam::from_proto(param), Bitmap::from(&vnode_bitmap)),
181 msg => {
182 return Err(Status::invalid_argument(format!(
183 "expected CoordinateRequest::StartRequest in the first request, get {:?}",
184 msg
185 )));
186 }
187 };
188 let (response_tx, response_rx) = mpsc::unbounded_channel();
189 self.request_tx
190 .send(ManagerRequest::NewSinkWriter(
191 SinkWriterCoordinationHandle::new(request_stream, response_tx, param, vnode_bitmap),
192 ))
193 .await
194 .map_err(|_| {
195 Status::unavailable(
196 "unable to send to sink manager worker. The worker may have stopped",
197 )
198 })?;
199
200 Ok(UnboundedReceiverStream::new(response_rx))
201 }
202
203 pub async fn stop_sink_coordinators_for_jobs(&self, job_ids: Vec<JobId>) {
204 let (notifier, started_rx) = Notifier::new();
205 if self
206 .request_tx
207 .send(ManagerRequest::StopCoordinator { notifier, job_ids })
208 .await
209 .is_err()
210 {
211 error!("unable to send sink coordinator stop request");
212 return;
213 }
214 if let Err(err) = Self::wait_for_completion(started_rx).await {
215 error!(error = %err.as_report(), "failed to wait for sink coordinators to stop");
216 }
217 }
218
219 pub async fn start_recovery(&self, recovery: RecoveryStart) -> anyhow::Result<()> {
220 let (notifier, started_rx) = Notifier::new();
221 self.request_tx
222 .send(ManagerRequest::RecoveryStart { recovery, notifier })
223 .await
224 .map_err(|_| anyhow!("sink coordinator manager worker has stopped"))?;
225 Self::wait_for_completion(started_rx).await
226 }
227
228 pub async fn recovery_succeeded(&self, recovery: RecoverySucceeded) -> anyhow::Result<()> {
229 let (notifier, started_rx) = Notifier::new();
230 self.request_tx
231 .send(ManagerRequest::RecoverySucceeded { recovery, notifier })
232 .await
233 .map_err(|_| anyhow!("sink coordinator manager worker has stopped"))?;
234 Self::wait_for_completion(started_rx).await
235 }
236
237 async fn wait_for_completion(started_rx: StartReceiver) -> anyhow::Result<()> {
238 let receivers = started_rx
239 .await
240 .map_err(|_| anyhow!("sink coordinator manager dropped start notifier"))??;
241 wait_collection(receivers).await?;
242 Ok(())
243 }
244}
245
246struct CoordinatorWorkerHandle {
247 request_sender: Option<UnboundedSender<SinkWriterCoordinationHandle>>,
249 finish_notifiers: Vec<CollectionNotifier>,
251}
252
253struct ManagerWorker {
254 request_rx: mpsc::Receiver<ManagerRequest>,
255 shutdown_rx: Receiver<()>,
257
258 running_coordinator_worker_join_handles:
259 FuturesUnordered<BoxFuture<'static, (SinkId, Result<(), JoinError>)>>,
260 running_coordinator_worker: HashMap<SinkId, CoordinatorWorkerHandle>,
261 recovery_fence: Option<RecoveryFence>,
262}
263
264enum ManagerEvent {
265 NewRequest(ManagerRequest),
266 CoordinatorWorkerFinished {
267 sink_id: SinkId,
268 join_result: Result<(), JoinError>,
269 },
270}
271
272trait SpawnCoordinatorFn = FnMut(SinkParam, UnboundedReceiver<SinkWriterCoordinationHandle>) -> JoinHandle<()>
273 + Send
274 + 'static;
275
276impl ManagerWorker {
277 fn new(request_rx: mpsc::Receiver<ManagerRequest>, shutdown_rx: Receiver<()>) -> Self {
278 ManagerWorker {
279 request_rx,
280 shutdown_rx,
281 running_coordinator_worker_join_handles: Default::default(),
282 running_coordinator_worker: Default::default(),
283 recovery_fence: None,
284 }
285 }
286
287 async fn execute(mut self, mut spawn_coordinator_worker: impl SpawnCoordinatorFn) {
288 while let Some(event) = self.next_event().await {
289 match event {
290 ManagerEvent::NewRequest(request) => match request {
291 ManagerRequest::NewSinkWriter(request) => {
292 self.handle_new_sink_writer(request, &mut spawn_coordinator_worker)
293 }
294 ManagerRequest::StopCoordinator { notifier, job_ids } => {
295 let mut notifier = notifier.start();
296 for job_id in job_ids {
297 if let Some(worker_handle) = self
298 .running_coordinator_worker
299 .get_mut(&job_id.as_sink_id())
300 {
301 Self::drain_coordinator(worker_handle, &mut notifier);
302 }
303 }
304 notifier.started();
305 }
306 ManagerRequest::RecoveryStart { recovery, notifier } => {
307 self.apply_recovery_start(recovery, notifier);
308 }
309 ManagerRequest::RecoverySucceeded { recovery, notifier } => {
310 let notifier = notifier.start();
311 self.apply_recovery_succeeded(recovery);
312 notifier.started();
313 }
314 },
315 ManagerEvent::CoordinatorWorkerFinished {
316 sink_id,
317 join_result,
318 } => self.handle_coordinator_finished(sink_id, join_result),
319 }
320 }
321 self.clean_up().await;
322 info!("sink manager worker exited");
323 }
324
325 fn apply_recovery_start(&mut self, recovery: RecoveryStart, notifier: Notifier) {
326 let job_ids_to_stop = match recovery {
327 RecoveryStart::Global => {
328 self.recovery_fence = Some(RecoveryFence::Global);
329 None
330 }
331 RecoveryStart::Database {
332 database_id,
333 job_ids,
334 } => {
335 let job_ids_to_stop = job_ids.clone();
336 match &mut self.recovery_fence {
337 Some(RecoveryFence::Global) => {}
338 Some(RecoveryFence::Databases(databases)) => {
339 databases.entry(database_id).or_default().extend(job_ids);
340 }
341 None => {
342 self.recovery_fence = Some(RecoveryFence::Databases(HashMap::from([(
343 database_id,
344 job_ids,
345 )])));
346 }
347 }
348 Some(job_ids_to_stop)
349 }
350 };
351
352 let mut notifier = notifier.start();
353 if let Some(job_ids) = job_ids_to_stop {
354 for job_id in job_ids {
355 if let Some(worker_handle) = self
356 .running_coordinator_worker
357 .get_mut(&job_id.as_sink_id())
358 {
359 Self::drain_coordinator(worker_handle, &mut notifier);
360 }
361 }
362 } else {
363 for worker_handle in self.running_coordinator_worker.values_mut() {
364 Self::drain_coordinator(worker_handle, &mut notifier);
365 }
366 }
367 notifier.started();
368 }
369
370 fn drain_coordinator(
371 worker_handle: &mut CoordinatorWorkerHandle,
372 notifier: &mut NotifierStarter,
373 ) {
374 worker_handle.finish_notifiers.push(notifier.add_notify());
375 if let Some(sender) = worker_handle.request_sender.take() {
376 drop(sender);
378 }
379 }
380
381 fn apply_recovery_succeeded(&mut self, recovery: RecoverySucceeded) {
382 match recovery {
383 RecoverySucceeded::Global { failed_databases } => {
384 self.recovery_fence = if failed_databases.is_empty() {
385 None
386 } else {
387 Some(RecoveryFence::Databases(failed_databases))
388 };
389 }
390 RecoverySucceeded::Database(database_id) => {
391 let Some(RecoveryFence::Databases(databases)) = &mut self.recovery_fence else {
392 return;
393 };
394 databases.remove(&database_id);
395 if databases.is_empty() {
396 self.recovery_fence = None;
397 }
398 }
399 }
400 }
401
402 fn is_sink_fenced(&self, sink_id: SinkId) -> bool {
403 match &self.recovery_fence {
404 Some(RecoveryFence::Global) => true,
405 Some(RecoveryFence::Databases(databases)) => databases
406 .values()
407 .any(|job_ids| job_ids.contains(&sink_id.as_job_id())),
408 None => false,
409 }
410 }
411
412 async fn next_event(&mut self) -> Option<ManagerEvent> {
413 match select(
414 select(
415 pin!(self.request_rx.recv()),
416 pin!(pending_on_none(
417 self.running_coordinator_worker_join_handles.next()
418 )),
419 ),
420 &mut self.shutdown_rx,
421 )
422 .await
423 {
424 Either::Left((either, _)) => match either {
425 Either::Left((Some(request), _)) => Some(ManagerEvent::NewRequest(request)),
426 Either::Left((None, _)) => None,
427 Either::Right(((sink_id, join_result), _)) => {
428 Some(ManagerEvent::CoordinatorWorkerFinished {
429 sink_id,
430 join_result,
431 })
432 }
433 },
434 Either::Right(_) => None,
435 }
436 }
437
438 async fn clean_up(&mut self) {
439 info!("sink manager worker start cleaning up");
440 for worker_handle in self.running_coordinator_worker.values_mut() {
441 if let Some(sender) = worker_handle.request_sender.take() {
442 drop(sender);
444 }
445 }
446 while let Some((sink_id, join_result)) =
447 self.running_coordinator_worker_join_handles.next().await
448 {
449 self.handle_coordinator_finished(sink_id, join_result);
450 }
451 info!("sink manager worker finished cleaning up");
452 }
453
454 fn handle_coordinator_finished(&mut self, sink_id: SinkId, join_result: Result<(), JoinError>) {
455 let worker_handle = self
456 .running_coordinator_worker
457 .remove(&sink_id)
458 .expect("finished coordinator should have an associated worker handle");
459 for finish_notifier in worker_handle.finish_notifiers {
460 finish_notifier.notify_collected();
461 }
462 match join_result {
463 Ok(()) => {
464 info!(
465 id = %sink_id,
466 "sink coordinator has gracefully finished",
467 );
468 }
469 Err(err) => {
470 error!(
471 id = %sink_id,
472 error = %err.as_report(),
473 "sink coordinator finished with error",
474 );
475 }
476 }
477 }
478
479 fn handle_new_sink_writer(
480 &mut self,
481 new_writer: SinkWriterCoordinationHandle,
482 spawn_coordinator_worker: &mut impl SpawnCoordinatorFn,
483 ) {
484 let param = new_writer.param();
485 let sink_id = param.sink_id;
486
487 if self.is_sink_fenced(sink_id) {
488 new_writer.abort(Status::unavailable(format!(
489 "sink coordinator for sink {} is unavailable during recovery",
490 sink_id
491 )));
492 return;
493 }
494
495 let handle = self
496 .running_coordinator_worker
497 .entry(param.sink_id)
498 .or_insert_with(|| {
499 let (request_tx, request_rx) = unbounded_channel();
501 let join_handle = spawn_coordinator_worker(param.clone(), request_rx);
502 self.running_coordinator_worker_join_handles.push(
503 join_handle
504 .map(move |join_result| (sink_id, join_result))
505 .boxed(),
506 );
507 CoordinatorWorkerHandle {
508 request_sender: Some(request_tx),
509 finish_notifiers: Vec::new(),
510 }
511 });
512
513 if let Some(sender) = handle.request_sender.as_mut() {
514 send_with_err_check!(sender, new_writer);
515 } else {
516 warn!(
517 "handle a new request while the sink coordinator is being stopped: {:?}",
518 param
519 );
520 new_writer.abort(Status::internal("the sink is being stopped"));
521 }
522 }
523}
524
525#[cfg(test)]
526mod tests {
527 use std::collections::{HashMap, HashSet};
528 use std::future::{Future, poll_fn};
529 use std::pin::pin;
530 use std::sync::Arc;
531 use std::sync::atomic::{AtomicI32, AtomicUsize, Ordering};
532 use std::task::Poll;
533
534 use anyhow::anyhow;
535 use async_trait::async_trait;
536 use futures::future::{join, try_join};
537 use futures::{FutureExt, StreamExt, TryFutureExt};
538 use itertools::Itertools;
539 use rand::seq::SliceRandom;
540 use risingwave_common::bitmap::{Bitmap, BitmapBuilder};
541 use risingwave_common::hash::VirtualNode;
542 use risingwave_common::id::{DatabaseId, JobId};
543 use risingwave_connector::sink::catalog::{SinkId, SinkType};
544 use risingwave_connector::sink::{
545 SinglePhaseCommitCoordinator, SinkCommitCoordinator, SinkError, SinkParam,
546 TwoPhaseCommitCoordinator,
547 };
548 use risingwave_meta_model::SinkSchemachange;
549 use risingwave_pb::connector_service::sink_metadata::{Metadata, SerializedMetadata};
550 use risingwave_pb::connector_service::{CoordinateResponse, SinkMetadata};
551 use risingwave_pb::data::PbDataType;
552 use risingwave_pb::data::data_type::PbTypeName;
553 use risingwave_pb::plan_common::PbField;
554 use risingwave_pb::stream_plan::sink_schema_change::Op as SinkSchemachangeOp;
555 use risingwave_pb::stream_plan::{PbSinkAddColumnsOp, PbSinkSchemaChange};
556 use risingwave_rpc_client::CoordinatorStreamHandle;
557 use sea_orm::{ConnectionTrait, Database, DatabaseConnection};
558 use tokio::sync::Notify;
559 use tokio::sync::mpsc::unbounded_channel;
560 use tokio_stream::wrappers::ReceiverStream;
561 use tonic::Status;
562
563 use super::{
564 ManagerRequest, ManagerWorker, RecoveryFence, RecoveryStart, RecoverySucceeded,
565 SinkWriterCoordinationHandle,
566 };
567 use crate::manager::sink_coordination::SinkCoordinatorManager;
568 use crate::manager::sink_coordination::coordinator_worker::CoordinatorWorker;
569 use crate::manager::sink_coordination::manager::SinkCommittedEpochSubscriber;
570
571 fn test_writer_handle(
572 param: SinkParam,
573 ) -> (
574 SinkWriterCoordinationHandle,
575 tokio::sync::mpsc::UnboundedReceiver<Result<CoordinateResponse, Status>>,
576 ) {
577 let (response_tx, response_rx) = unbounded_channel();
578 (
579 SinkWriterCoordinationHandle::new(
580 futures::stream::pending().boxed(),
581 response_tx,
582 param,
583 Bitmap::ones(1),
584 ),
585 response_rx,
586 )
587 }
588
589 #[test]
590 fn test_recovery_fence_transitions() {
591 let (_request_tx, request_rx) = tokio::sync::mpsc::channel(1);
592 let (_shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel();
593 let mut worker = ManagerWorker::new(request_rx, shutdown_rx);
594 let database_1 = DatabaseId::new(1);
595 let database_2 = DatabaseId::new(2);
596 let sink_1 = SinkId::new(11);
597 let sink_2 = SinkId::new(12);
598 let sink_3 = SinkId::new(21);
599 let job_1 = sink_1.as_job_id();
600 let job_2 = sink_2.as_job_id();
601 let job_3 = sink_3.as_job_id();
602
603 assert!(!worker.is_sink_fenced(sink_1));
604
605 worker.apply_recovery_start(
606 RecoveryStart::Database {
607 database_id: database_1,
608 job_ids: HashSet::from([job_1]),
609 },
610 crate::notification::Notifier::new().0,
611 );
612 assert!(worker.is_sink_fenced(sink_1));
613 assert!(!worker.is_sink_fenced(sink_2));
614
615 worker.apply_recovery_start(
616 RecoveryStart::Database {
617 database_id: database_1,
618 job_ids: HashSet::from([job_2]),
619 },
620 crate::notification::Notifier::new().0,
621 );
622 worker.apply_recovery_start(
623 RecoveryStart::Database {
624 database_id: database_2,
625 job_ids: HashSet::from([job_3]),
626 },
627 crate::notification::Notifier::new().0,
628 );
629 assert!(worker.is_sink_fenced(sink_1));
630 assert!(worker.is_sink_fenced(sink_2));
631 assert!(worker.is_sink_fenced(sink_3));
632
633 worker.apply_recovery_start(
634 RecoveryStart::Global,
635 crate::notification::Notifier::new().0,
636 );
637 worker.apply_recovery_start(
638 RecoveryStart::Global,
639 crate::notification::Notifier::new().0,
640 );
641 assert!(matches!(worker.recovery_fence, Some(RecoveryFence::Global)));
642 assert!(worker.is_sink_fenced(SinkId::new(999)));
643
644 worker.apply_recovery_start(
645 RecoveryStart::Database {
646 database_id: database_1,
647 job_ids: HashSet::from([job_1]),
648 },
649 crate::notification::Notifier::new().0,
650 );
651 assert!(matches!(worker.recovery_fence, Some(RecoveryFence::Global)));
652 worker.apply_recovery_succeeded(RecoverySucceeded::Database(database_1));
653 assert!(matches!(worker.recovery_fence, Some(RecoveryFence::Global)));
654
655 worker.apply_recovery_succeeded(RecoverySucceeded::Global {
656 failed_databases: HashMap::from([
657 (database_1, HashSet::from([job_1, job_2])),
658 (database_2, HashSet::from([job_3])),
659 ]),
660 });
661 assert!(worker.is_sink_fenced(sink_1));
662 assert!(worker.is_sink_fenced(sink_3));
663 assert!(!worker.is_sink_fenced(SinkId::new(999)));
664
665 worker.apply_recovery_succeeded(RecoverySucceeded::Database(database_1));
666 assert!(!worker.is_sink_fenced(sink_1));
667 assert!(worker.is_sink_fenced(sink_3));
668
669 worker.apply_recovery_succeeded(RecoverySucceeded::Database(database_2));
670 assert!(worker.recovery_fence.is_none());
671
672 worker.apply_recovery_start(
673 RecoveryStart::Global,
674 crate::notification::Notifier::new().0,
675 );
676 worker.apply_recovery_succeeded(RecoverySucceeded::Global {
677 failed_databases: HashMap::new(),
678 });
679 assert!(worker.recovery_fence.is_none());
680 }
681
682 #[tokio::test]
683 async fn test_recovery_fence_blocks_spawn_and_waits_for_coordinator_exit() {
684 let database_id = DatabaseId::new(1);
685 let sink_id = SinkId::new(11);
686 let param = SinkParam {
687 sink_id,
688 sink_name: "test".into(),
689 properties: Default::default(),
690 columns: vec![],
691 downstream_pk: None,
692 sink_type: SinkType::AppendOnly,
693 ignore_delete: false,
694 format_desc: None,
695 db_name: "test".into(),
696 sink_from_name: "test".into(),
697 };
698 let spawn_count = Arc::new(AtomicUsize::new(0));
699 let spawned = Arc::new(Notify::new());
700 let stopping = Arc::new(Notify::new());
701 let allow_exit = Arc::new(Notify::new());
702 let (manager, (_join_handle, _shutdown_tx)) =
703 SinkCoordinatorManager::start_worker_with_spawn_worker({
704 let spawn_count = spawn_count.clone();
705 let spawned = spawned.clone();
706 let stopping = stopping.clone();
707 let allow_exit = allow_exit.clone();
708 move |_param, mut request_rx| {
709 spawn_count.fetch_add(1, Ordering::SeqCst);
710 spawned.notify_one();
711 let stopping = stopping.clone();
712 let allow_exit = allow_exit.clone();
713 tokio::spawn(async move {
714 while request_rx.recv().await.is_some() {}
715 stopping.notify_one();
716 allow_exit.notified().await;
717 })
718 }
719 });
720
721 manager
722 .start_recovery(RecoveryStart::Database {
723 database_id,
724 job_ids: HashSet::from([sink_id.as_job_id()]),
725 })
726 .await
727 .unwrap();
728
729 let (writer, mut response_rx) = test_writer_handle(param.clone());
730 manager
731 .request_tx
732 .send(ManagerRequest::NewSinkWriter(writer))
733 .await
734 .unwrap();
735 let status = response_rx.recv().await.unwrap().unwrap_err();
736 assert_eq!(status.code(), tonic::Code::Unavailable);
737 assert_eq!(spawn_count.load(Ordering::SeqCst), 0);
738
739 manager
740 .recovery_succeeded(RecoverySucceeded::Database(database_id))
741 .await
742 .unwrap();
743 let (writer, _response_rx) = test_writer_handle(param.clone());
744 manager
745 .request_tx
746 .send(ManagerRequest::NewSinkWriter(writer))
747 .await
748 .unwrap();
749 spawned.notified().await;
750 assert_eq!(spawn_count.load(Ordering::SeqCst), 1);
751
752 let stop_task = tokio::spawn({
753 let manager = manager.clone();
754 async move {
755 manager
756 .stop_sink_coordinators_for_jobs(vec![sink_id.as_job_id()])
757 .await;
758 }
759 });
760 stopping.notified().await;
761 assert!(!stop_task.is_finished());
762 allow_exit.notify_one();
763 stop_task.await.unwrap();
764
765 let (writer, _response_rx) = test_writer_handle(param.clone());
766 manager
767 .request_tx
768 .send(ManagerRequest::NewSinkWriter(writer))
769 .await
770 .unwrap();
771 spawned.notified().await;
772 assert_eq!(spawn_count.load(Ordering::SeqCst), 2);
773
774 let recovery_task = tokio::spawn({
775 let manager = manager.clone();
776 async move {
777 manager
778 .start_recovery(RecoveryStart::Database {
779 database_id,
780 job_ids: HashSet::from([sink_id.as_job_id()]),
781 })
782 .await
783 .unwrap();
784 }
785 });
786 stopping.notified().await;
787 assert!(!recovery_task.is_finished());
788
789 let (writer, mut response_rx) = test_writer_handle(param);
790 manager
791 .request_tx
792 .send(ManagerRequest::NewSinkWriter(writer))
793 .await
794 .unwrap();
795 let status = response_rx.recv().await.unwrap().unwrap_err();
796 assert_eq!(status.code(), tonic::Code::Unavailable);
797 assert_eq!(spawn_count.load(Ordering::SeqCst), 2);
798
799 allow_exit.notify_one();
800 recovery_task.await.unwrap();
801 }
802
803 struct MockSinglePhaseCoordinator<
804 C,
805 F: FnMut(u64, Vec<SinkMetadata>, &mut C) -> Result<(), SinkError>,
806 > {
807 context: C,
808 f: F,
809 }
810
811 impl<
812 C: Send + 'static,
813 F: FnMut(u64, Vec<SinkMetadata>, &mut C) -> Result<(), SinkError> + Send + 'static,
814 > MockSinglePhaseCoordinator<C, F>
815 {
816 fn new_coordinator(context: C, f: F) -> SinkCommitCoordinator {
817 SinkCommitCoordinator::SinglePhase(Box::new(MockSinglePhaseCoordinator { context, f }))
818 }
819 }
820
821 #[async_trait]
822 impl<C: Send, F: FnMut(u64, Vec<SinkMetadata>, &mut C) -> Result<(), SinkError> + Send>
823 SinglePhaseCommitCoordinator for MockSinglePhaseCoordinator<C, F>
824 {
825 async fn init(&mut self) -> risingwave_connector::sink::Result<()> {
826 Ok(())
827 }
828
829 async fn commit_data(
830 &mut self,
831 epoch: u64,
832 metadata: Vec<SinkMetadata>,
833 ) -> risingwave_connector::sink::Result<()> {
834 (self.f)(epoch, metadata, &mut self.context)
835 }
836
837 async fn commit_schema_change(
838 &mut self,
839 _epoch: u64,
840 _schema_change: PbSinkSchemaChange,
841 ) -> risingwave_connector::sink::Result<()> {
842 unreachable!()
843 }
844 }
845
846 #[tokio::test]
847 async fn test_basic() {
848 let db = prepare_db_backend().await;
849
850 let param = SinkParam {
851 sink_id: SinkId::from(1),
852 sink_name: "test".into(),
853 properties: Default::default(),
854 columns: vec![],
855 downstream_pk: None,
856 sink_type: SinkType::AppendOnly,
857 ignore_delete: false,
858 format_desc: None,
859 db_name: "test".into(),
860 sink_from_name: "test".into(),
861 };
862
863 let epoch0 = 232;
864 let epoch1 = 233;
865 let epoch2 = 234;
866
867 let mut all_vnode = (0..VirtualNode::COUNT_FOR_TEST).collect_vec();
868 all_vnode.shuffle(&mut rand::rng());
869 let (first, second) = all_vnode.split_at(VirtualNode::COUNT_FOR_TEST / 2);
870 let build_bitmap = |indexes: &[usize]| {
871 let mut builder = BitmapBuilder::zeroed(VirtualNode::COUNT_FOR_TEST);
872 for i in indexes {
873 builder.set(*i, true);
874 }
875 builder.finish()
876 };
877 let vnode1 = build_bitmap(first);
878 let vnode2 = build_bitmap(second);
879
880 let metadata = [
881 [vec![1u8, 2u8], vec![3u8, 4u8]],
882 [vec![5u8, 6u8], vec![7u8, 8u8]],
883 ];
884 let sender = Arc::new(tokio::sync::Mutex::new(None));
885 let mock_subscriber: SinkCommittedEpochSubscriber = {
886 let captured_sender = sender.clone();
887 Arc::new(move |_sink_id: SinkId| {
888 let (sender, receiver) = unbounded_channel();
889 let captured_sender = captured_sender.clone();
890 async move {
891 let mut guard = captured_sender.lock().await;
892 *guard = Some(sender);
893 Ok((1, receiver))
894 }
895 .boxed()
896 })
897 };
898
899 let (manager, (_join_handle, _stop_tx)) =
900 SinkCoordinatorManager::start_worker_with_spawn_worker({
901 let expected_param = param.clone();
902 let metadata = metadata.clone();
903 let db = db.clone();
904 move |param, new_writer_rx| {
905 let metadata = metadata.clone();
906 let expected_param = expected_param.clone();
907 let db = db.clone();
908 tokio::spawn({
909 let subscriber = mock_subscriber.clone();
910 async move {
911 assert_eq!(param, expected_param);
913 CoordinatorWorker::execute_coordinator(
914 db,
915 param.clone(),
916 new_writer_rx,
917 MockSinglePhaseCoordinator::new_coordinator(
918 0,
919 move |epoch, metadata_list, count: &mut usize| {
920 *count += 1;
921 let mut metadata_list =
922 metadata_list
923 .into_iter()
924 .map(|metadata| match metadata {
925 SinkMetadata {
926 metadata:
927 Some(Metadata::Serialized(
928 SerializedMetadata { metadata },
929 )),
930 } => metadata,
931 _ => unreachable!(),
932 })
933 .collect_vec();
934 metadata_list.sort();
935 match *count {
936 1 => {
937 assert_eq!(epoch, epoch1);
938 assert_eq!(2, metadata_list.len());
939 assert_eq!(metadata[0][0], metadata_list[0]);
940 assert_eq!(metadata[0][1], metadata_list[1]);
941 }
942 2 => {
943 assert_eq!(epoch, epoch2);
944 assert_eq!(2, metadata_list.len());
945 assert_eq!(metadata[1][0], metadata_list[0]);
946 assert_eq!(metadata[1][1], metadata_list[1]);
947 }
948 _ => unreachable!(),
949 }
950 Ok(())
951 },
952 ),
953 subscriber.clone(),
954 )
955 .await;
956 }
957 })
958 }
959 });
960
961 let build_client = |vnode| async {
962 CoordinatorStreamHandle::new_with_init_stream(param.to_proto(), vnode, |rx| async {
963 Ok(tonic::Response::new(
964 manager
965 .handle_new_request(ReceiverStream::new(rx).map(Ok).boxed())
966 .await
967 .unwrap()
968 .boxed(),
969 ))
970 })
971 .await
972 .unwrap()
973 .0
974 };
975
976 let (mut client1, mut client2) =
977 join(build_client(vnode1), pin!(build_client(vnode2))).await;
978
979 let (aligned_epoch1, aligned_epoch2) = try_join(
980 client1.align_initial_epoch(epoch0),
981 client2.align_initial_epoch(epoch1),
982 )
983 .await
984 .unwrap();
985 assert_eq!(aligned_epoch1, epoch1);
986 assert_eq!(aligned_epoch2, epoch1);
987
988 {
989 let mut commit_future = pin!(
991 client2
992 .commit(
993 epoch1,
994 SinkMetadata {
995 metadata: Some(Metadata::Serialized(SerializedMetadata {
996 metadata: metadata[0][1].clone(),
997 })),
998 },
999 None,
1000 )
1001 .map(|result| result.unwrap())
1002 );
1003 assert!(
1004 poll_fn(|cx| Poll::Ready(commit_future.as_mut().poll(cx)))
1005 .await
1006 .is_pending()
1007 );
1008 join(
1009 commit_future,
1010 client1
1011 .commit(
1012 epoch1,
1013 SinkMetadata {
1014 metadata: Some(Metadata::Serialized(SerializedMetadata {
1015 metadata: metadata[0][0].clone(),
1016 })),
1017 },
1018 None,
1019 )
1020 .map(|result| result.unwrap()),
1021 )
1022 .await;
1023 }
1024
1025 let mut commit_future = pin!(
1027 client1
1028 .commit(
1029 epoch2,
1030 SinkMetadata {
1031 metadata: Some(Metadata::Serialized(SerializedMetadata {
1032 metadata: metadata[1][0].clone(),
1033 })),
1034 },
1035 None,
1036 )
1037 .map(|result| result.unwrap())
1038 );
1039 assert!(
1040 poll_fn(|cx| Poll::Ready(commit_future.as_mut().poll(cx)))
1041 .await
1042 .is_pending()
1043 );
1044 join(
1045 commit_future,
1046 client2
1047 .commit(
1048 epoch2,
1049 SinkMetadata {
1050 metadata: Some(Metadata::Serialized(SerializedMetadata {
1051 metadata: metadata[1][1].clone(),
1052 })),
1053 },
1054 None,
1055 )
1056 .map(|result| result.unwrap()),
1057 )
1058 .await;
1059 }
1060
1061 #[tokio::test]
1062 async fn test_single_writer() {
1063 let db = prepare_db_backend().await;
1064 let param = SinkParam {
1065 sink_id: SinkId::from(1),
1066 sink_name: "test".into(),
1067 properties: Default::default(),
1068 columns: vec![],
1069 downstream_pk: None,
1070 sink_type: SinkType::AppendOnly,
1071 ignore_delete: false,
1072 format_desc: None,
1073 db_name: "test".into(),
1074 sink_from_name: "test".into(),
1075 };
1076
1077 let epoch1 = 233;
1078 let epoch2 = 234;
1079
1080 let all_vnode = (0..VirtualNode::COUNT_FOR_TEST).collect_vec();
1081 let build_bitmap = |indexes: &[usize]| {
1082 let mut builder = BitmapBuilder::zeroed(VirtualNode::COUNT_FOR_TEST);
1083 for i in indexes {
1084 builder.set(*i, true);
1085 }
1086 builder.finish()
1087 };
1088 let vnode = build_bitmap(&all_vnode);
1089
1090 let metadata = [vec![1u8, 2u8], vec![3u8, 4u8]];
1091 let sender = Arc::new(tokio::sync::Mutex::new(None));
1092 let mock_subscriber: SinkCommittedEpochSubscriber = {
1093 let captured_sender = sender.clone();
1094 Arc::new(move |_sink_id: SinkId| {
1095 let (sender, receiver) = unbounded_channel();
1096 let captured_sender = captured_sender.clone();
1097 async move {
1098 let mut guard = captured_sender.lock().await;
1099 *guard = Some(sender);
1100 Ok((1, receiver))
1101 }
1102 .boxed()
1103 })
1104 };
1105 let (manager, (_join_handle, _stop_tx)) =
1106 SinkCoordinatorManager::start_worker_with_spawn_worker({
1107 let expected_param = param.clone();
1108 let metadata = metadata.clone();
1109 let db = db.clone();
1110 move |param, new_writer_rx| {
1111 let metadata = metadata.clone();
1112 let expected_param = expected_param.clone();
1113 let db = db.clone();
1114 tokio::spawn({
1115 let subscriber = mock_subscriber.clone();
1116 async move {
1117 assert_eq!(param, expected_param);
1119 CoordinatorWorker::execute_coordinator(
1120 db,
1121 param.clone(),
1122 new_writer_rx,
1123 MockSinglePhaseCoordinator::new_coordinator(
1124 0,
1125 move |epoch, metadata_list, count: &mut usize| {
1126 *count += 1;
1127 let mut metadata_list =
1128 metadata_list
1129 .into_iter()
1130 .map(|metadata| match metadata {
1131 SinkMetadata {
1132 metadata:
1133 Some(Metadata::Serialized(
1134 SerializedMetadata { metadata },
1135 )),
1136 } => metadata,
1137 _ => unreachable!(),
1138 })
1139 .collect_vec();
1140 metadata_list.sort();
1141 match *count {
1142 1 => {
1143 assert_eq!(epoch, epoch1);
1144 assert_eq!(1, metadata_list.len());
1145 assert_eq!(metadata[0], metadata_list[0]);
1146 }
1147 2 => {
1148 assert_eq!(epoch, epoch2);
1149 assert_eq!(1, metadata_list.len());
1150 assert_eq!(metadata[1], metadata_list[0]);
1151 }
1152 _ => unreachable!(),
1153 }
1154 Ok(())
1155 },
1156 ),
1157 subscriber.clone(),
1158 )
1159 .await;
1160 }
1161 })
1162 }
1163 });
1164
1165 let build_client = |vnode| async {
1166 CoordinatorStreamHandle::new_with_init_stream(param.to_proto(), vnode, |rx| async {
1167 Ok(tonic::Response::new(
1168 manager
1169 .handle_new_request(ReceiverStream::new(rx).map(Ok).boxed())
1170 .await
1171 .unwrap()
1172 .boxed(),
1173 ))
1174 })
1175 .await
1176 .unwrap()
1177 .0
1178 };
1179
1180 let mut client = build_client(vnode).await;
1181
1182 let aligned_epoch = client.align_initial_epoch(epoch1).await.unwrap();
1183 assert_eq!(aligned_epoch, epoch1);
1184
1185 client
1186 .commit(
1187 epoch1,
1188 SinkMetadata {
1189 metadata: Some(Metadata::Serialized(SerializedMetadata {
1190 metadata: metadata[0].clone(),
1191 })),
1192 },
1193 None,
1194 )
1195 .await
1196 .unwrap();
1197
1198 client
1199 .commit(
1200 epoch2,
1201 SinkMetadata {
1202 metadata: Some(Metadata::Serialized(SerializedMetadata {
1203 metadata: metadata[1].clone(),
1204 })),
1205 },
1206 None,
1207 )
1208 .await
1209 .unwrap();
1210 }
1211
1212 #[tokio::test]
1213 async fn test_partial_commit() {
1214 let db = prepare_db_backend().await;
1215 let param = SinkParam {
1216 sink_id: SinkId::from(1),
1217 sink_name: "test".into(),
1218 properties: Default::default(),
1219 columns: vec![],
1220 downstream_pk: None,
1221 sink_type: SinkType::AppendOnly,
1222 ignore_delete: false,
1223 format_desc: None,
1224 db_name: "test".into(),
1225 sink_from_name: "test".into(),
1226 };
1227
1228 let epoch = 233;
1229
1230 let mut all_vnode = (0..VirtualNode::COUNT_FOR_TEST).collect_vec();
1231 all_vnode.shuffle(&mut rand::rng());
1232 let (first, second) = all_vnode.split_at(VirtualNode::COUNT_FOR_TEST / 2);
1233 let build_bitmap = |indexes: &[usize]| {
1234 let mut builder = BitmapBuilder::zeroed(VirtualNode::COUNT_FOR_TEST);
1235 for i in indexes {
1236 builder.set(*i, true);
1237 }
1238 builder.finish()
1239 };
1240 let vnode1 = build_bitmap(first);
1241 let vnode2 = build_bitmap(second);
1242
1243 let sender = Arc::new(tokio::sync::Mutex::new(None));
1244 let mock_subscriber: SinkCommittedEpochSubscriber = {
1245 let captured_sender = sender.clone();
1246 Arc::new(move |_sink_id: SinkId| {
1247 let (sender, receiver) = unbounded_channel();
1248 let captured_sender = captured_sender.clone();
1249 async move {
1250 let mut guard = captured_sender.lock().await;
1251 *guard = Some(sender);
1252 Ok((1, receiver))
1253 }
1254 .boxed()
1255 })
1256 };
1257 let (manager, (_join_handle, _stop_tx)) =
1258 SinkCoordinatorManager::start_worker_with_spawn_worker({
1259 let expected_param = param.clone();
1260 let db = db.clone();
1261 move |param, new_writer_rx| {
1262 let expected_param = expected_param.clone();
1263 let db = db.clone();
1264 tokio::spawn({
1265 let subscriber = mock_subscriber.clone();
1266 async move {
1267 assert_eq!(param, expected_param);
1269 CoordinatorWorker::execute_coordinator(
1270 db,
1271 param,
1272 new_writer_rx,
1273 MockSinglePhaseCoordinator::new_coordinator(
1274 (),
1275 |_, _, _| unreachable!(),
1276 ),
1277 subscriber.clone(),
1278 )
1279 .await;
1280 }
1281 })
1282 }
1283 });
1284
1285 let build_client = |vnode| async {
1286 CoordinatorStreamHandle::new_with_init_stream(param.to_proto(), vnode, |rx| async {
1287 Ok(tonic::Response::new(
1288 manager
1289 .handle_new_request(ReceiverStream::new(rx).map(Ok).boxed())
1290 .await
1291 .unwrap()
1292 .boxed(),
1293 ))
1294 })
1295 .await
1296 .unwrap()
1297 .0
1298 };
1299
1300 let (mut client1, client2) = join(build_client(vnode1), build_client(vnode2)).await;
1301
1302 let mut commit_future = pin!(client1.commit(
1304 epoch,
1305 SinkMetadata {
1306 metadata: Some(Metadata::Serialized(SerializedMetadata {
1307 metadata: vec![],
1308 })),
1309 },
1310 None,
1311 ));
1312 assert!(
1313 poll_fn(|cx| Poll::Ready(commit_future.as_mut().poll(cx)))
1314 .await
1315 .is_pending()
1316 );
1317 drop(client2);
1318 assert!(commit_future.await.is_err());
1319 }
1320
1321 #[tokio::test]
1322 async fn test_fail_commit() {
1323 let db = prepare_db_backend().await;
1324 let param = SinkParam {
1325 sink_id: SinkId::from(1),
1326 sink_name: "test".into(),
1327 properties: Default::default(),
1328 columns: vec![],
1329 downstream_pk: None,
1330 sink_type: SinkType::AppendOnly,
1331 ignore_delete: false,
1332 format_desc: None,
1333 db_name: "test".into(),
1334 sink_from_name: "test".into(),
1335 };
1336
1337 let epoch = 233;
1338
1339 let mut all_vnode = (0..VirtualNode::COUNT_FOR_TEST).collect_vec();
1340 all_vnode.shuffle(&mut rand::rng());
1341 let (first, second) = all_vnode.split_at(VirtualNode::COUNT_FOR_TEST / 2);
1342 let build_bitmap = |indexes: &[usize]| {
1343 let mut builder = BitmapBuilder::zeroed(VirtualNode::COUNT_FOR_TEST);
1344 for i in indexes {
1345 builder.set(*i, true);
1346 }
1347 builder.finish()
1348 };
1349 let vnode1 = build_bitmap(first);
1350 let vnode2 = build_bitmap(second);
1351 let sender = Arc::new(tokio::sync::Mutex::new(None));
1352 let mock_subscriber: SinkCommittedEpochSubscriber = {
1353 let captured_sender = sender.clone();
1354 Arc::new(move |_sink_id: SinkId| {
1355 let (sender, receiver) = unbounded_channel();
1356 let captured_sender = captured_sender.clone();
1357 async move {
1358 let mut guard = captured_sender.lock().await;
1359 *guard = Some(sender);
1360 Ok((1, receiver))
1361 }
1362 .boxed()
1363 })
1364 };
1365 let (manager, (_join_handle, _stop_tx)) =
1366 SinkCoordinatorManager::start_worker_with_spawn_worker({
1367 let expected_param = param.clone();
1368 let db = db.clone();
1369 move |param, new_writer_rx| {
1370 let expected_param = expected_param.clone();
1371 let db = db.clone();
1372 tokio::spawn({
1373 let subscriber = mock_subscriber.clone();
1374 {
1375 async move {
1376 assert_eq!(param, expected_param);
1378 CoordinatorWorker::execute_coordinator(
1379 db,
1380 param,
1381 new_writer_rx,
1382 MockSinglePhaseCoordinator::new_coordinator((), |_, _, _| {
1383 Err(SinkError::Coordinator(anyhow!("failed to commit")))
1384 }),
1385 subscriber.clone(),
1386 )
1387 .await;
1388 }
1389 }
1390 })
1391 }
1392 });
1393
1394 let build_client = |vnode| async {
1395 CoordinatorStreamHandle::new_with_init_stream(param.to_proto(), vnode, |rx| async {
1396 Ok(tonic::Response::new(
1397 manager
1398 .handle_new_request(ReceiverStream::new(rx).map(Ok).boxed())
1399 .await
1400 .unwrap()
1401 .boxed(),
1402 ))
1403 })
1404 .await
1405 .unwrap()
1406 .0
1407 };
1408
1409 let (mut client1, mut client2) = join(build_client(vnode1), build_client(vnode2)).await;
1410
1411 let mut commit_future = pin!(client1.commit(
1413 epoch,
1414 SinkMetadata {
1415 metadata: Some(Metadata::Serialized(SerializedMetadata {
1416 metadata: vec![],
1417 })),
1418 },
1419 None,
1420 ));
1421 assert!(
1422 poll_fn(|cx| Poll::Ready(commit_future.as_mut().poll(cx)))
1423 .await
1424 .is_pending()
1425 );
1426 let (result1, result2) = join(
1427 commit_future,
1428 client2.commit(
1429 epoch,
1430 SinkMetadata {
1431 metadata: Some(Metadata::Serialized(SerializedMetadata {
1432 metadata: vec![],
1433 })),
1434 },
1435 None,
1436 ),
1437 )
1438 .await;
1439 assert!(result1.is_err());
1440 assert!(result2.is_err());
1441 }
1442
1443 #[tokio::test]
1444 async fn test_update_vnode_bitmap() {
1445 let db = prepare_db_backend().await;
1446 let param = SinkParam {
1447 sink_id: SinkId::from(1),
1448 sink_name: "test".into(),
1449 properties: Default::default(),
1450 columns: vec![],
1451 downstream_pk: None,
1452 sink_type: SinkType::AppendOnly,
1453 ignore_delete: false,
1454 format_desc: None,
1455 db_name: "test".into(),
1456 sink_from_name: "test".into(),
1457 };
1458
1459 let epoch1 = 233;
1460 let epoch2 = 234;
1461 let epoch3 = 235;
1462 let epoch4 = 236;
1463
1464 let mut all_vnode = (0..VirtualNode::COUNT_FOR_TEST).collect_vec();
1465 all_vnode.shuffle(&mut rand::rng());
1466 let (first, second) = all_vnode.split_at(VirtualNode::COUNT_FOR_TEST / 2);
1467 let build_bitmap = |indexes: &[usize]| {
1468 let mut builder = BitmapBuilder::zeroed(VirtualNode::COUNT_FOR_TEST);
1469 for i in indexes {
1470 builder.set(*i, true);
1471 }
1472 builder.finish()
1473 };
1474 let vnode1 = build_bitmap(first);
1475 let vnode2 = build_bitmap(second);
1476
1477 let metadata = [
1478 [vec![1u8, 2u8], vec![3u8, 4u8]],
1479 [vec![5u8, 6u8], vec![7u8, 8u8]],
1480 ];
1481
1482 let metadata_scale_out = [vec![9u8, 10u8], vec![11u8, 12u8], vec![13u8, 14u8]];
1483 let metadata_scale_in = [vec![13u8, 14u8], vec![15u8, 16u8]];
1484 let sender = Arc::new(tokio::sync::Mutex::new(None));
1485 let mock_subscriber: SinkCommittedEpochSubscriber = {
1486 let captured_sender = sender.clone();
1487 Arc::new(move |_sink_id: SinkId| {
1488 let (sender, receiver) = unbounded_channel();
1489 let captured_sender = captured_sender.clone();
1490 async move {
1491 let mut guard = captured_sender.lock().await;
1492 *guard = Some(sender);
1493 Ok((1, receiver))
1494 }
1495 .boxed()
1496 })
1497 };
1498 let (manager, (_join_handle, _stop_tx)) =
1499 SinkCoordinatorManager::start_worker_with_spawn_worker({
1500 let expected_param = param.clone();
1501 let metadata = metadata.clone();
1502 let metadata_scale_out = metadata_scale_out.clone();
1503 let metadata_scale_in = metadata_scale_in.clone();
1504 let db = db.clone();
1505 move |param, new_writer_rx| {
1506 let metadata = metadata.clone();
1507 let metadata_scale_out = metadata_scale_out.clone();
1508 let metadata_scale_in = metadata_scale_in.clone();
1509 let expected_param = expected_param.clone();
1510 let db = db.clone();
1511 tokio::spawn({
1512 let subscriber = mock_subscriber.clone();
1513 async move {
1514 assert_eq!(param, expected_param);
1516 CoordinatorWorker::execute_coordinator(
1517 db,
1518 param.clone(),
1519 new_writer_rx,
1520 MockSinglePhaseCoordinator::new_coordinator(
1521 0,
1522 move |epoch, metadata_list, count: &mut usize| {
1523 *count += 1;
1524 let mut metadata_list =
1525 metadata_list
1526 .into_iter()
1527 .map(|metadata| match metadata {
1528 SinkMetadata {
1529 metadata:
1530 Some(Metadata::Serialized(
1531 SerializedMetadata { metadata },
1532 )),
1533 } => metadata,
1534 _ => unreachable!(),
1535 })
1536 .collect_vec();
1537 metadata_list.sort();
1538 let (expected_epoch, expected_metadata_list) = match *count
1539 {
1540 1 => (epoch1, metadata[0].as_slice()),
1541 2 => (epoch2, metadata[1].as_slice()),
1542 3 => (epoch3, metadata_scale_out.as_slice()),
1543 4 => (epoch4, metadata_scale_in.as_slice()),
1544 _ => unreachable!(),
1545 };
1546 assert_eq!(expected_epoch, epoch);
1547 assert_eq!(expected_metadata_list, &metadata_list);
1548 Ok(())
1549 },
1550 ),
1551 subscriber.clone(),
1552 )
1553 .await;
1554 }
1555 })
1556 }
1557 });
1558
1559 let build_client = |vnode| async {
1560 CoordinatorStreamHandle::new_with_init_stream(param.to_proto(), vnode, |rx| async {
1561 Ok(tonic::Response::new(
1562 manager
1563 .handle_new_request(ReceiverStream::new(rx).map(Ok).boxed())
1564 .await
1565 .unwrap()
1566 .boxed(),
1567 ))
1568 })
1569 .await
1570 };
1571
1572 let ((mut client1, _), (mut client2, _)) =
1573 try_join(build_client(vnode1), pin!(build_client(vnode2)))
1574 .await
1575 .unwrap();
1576
1577 let (aligned_epoch1, aligned_epoch2) = try_join(
1578 client1.align_initial_epoch(epoch1),
1579 client2.align_initial_epoch(epoch1),
1580 )
1581 .await
1582 .unwrap();
1583 assert_eq!(aligned_epoch1, epoch1);
1584 assert_eq!(aligned_epoch2, epoch1);
1585
1586 {
1587 let mut commit_future = pin!(
1589 client2
1590 .commit(
1591 epoch1,
1592 SinkMetadata {
1593 metadata: Some(Metadata::Serialized(SerializedMetadata {
1594 metadata: metadata[0][1].clone(),
1595 })),
1596 },
1597 None,
1598 )
1599 .map(|result| result.unwrap())
1600 );
1601 assert!(
1602 poll_fn(|cx| Poll::Ready(commit_future.as_mut().poll(cx)))
1603 .await
1604 .is_pending()
1605 );
1606 join(
1607 commit_future,
1608 client1
1609 .commit(
1610 epoch1,
1611 SinkMetadata {
1612 metadata: Some(Metadata::Serialized(SerializedMetadata {
1613 metadata: metadata[0][0].clone(),
1614 })),
1615 },
1616 None,
1617 )
1618 .map(|result| result.unwrap()),
1619 )
1620 .await;
1621 }
1622
1623 let (vnode1, vnode2, vnode3) = {
1624 let (first, second) = all_vnode.split_at(VirtualNode::COUNT_FOR_TEST / 3);
1625 let (second, third) = second.split_at(VirtualNode::COUNT_FOR_TEST / 3);
1626 (
1627 build_bitmap(first),
1628 build_bitmap(second),
1629 build_bitmap(third),
1630 )
1631 };
1632
1633 let mut build_client3_future = pin!(build_client(vnode3));
1634 assert!(
1635 poll_fn(|cx| Poll::Ready(build_client3_future.as_mut().poll(cx)))
1636 .await
1637 .is_pending()
1638 );
1639 let mut client3;
1640 {
1641 {
1642 let mut commit_future = pin!(
1644 client1
1645 .commit(
1646 epoch2,
1647 SinkMetadata {
1648 metadata: Some(Metadata::Serialized(SerializedMetadata {
1649 metadata: metadata[1][0].clone(),
1650 })),
1651 },
1652 None,
1653 )
1654 .map_err(Into::into)
1655 );
1656 assert!(
1657 poll_fn(|cx| Poll::Ready(commit_future.as_mut().poll(cx)))
1658 .await
1659 .is_pending()
1660 );
1661 try_join(
1662 commit_future,
1663 client2.commit(
1664 epoch2,
1665 SinkMetadata {
1666 metadata: Some(Metadata::Serialized(SerializedMetadata {
1667 metadata: metadata[1][1].clone(),
1668 })),
1669 },
1670 None,
1671 ),
1672 )
1673 .await
1674 .unwrap();
1675 }
1676
1677 client3 = {
1678 let (
1679 (client3, init_epoch),
1680 (update_vnode_bitmap_epoch1, update_vnode_bitmap_epoch2),
1681 ) = try_join(
1682 build_client3_future,
1683 try_join(
1684 client1.update_vnode_bitmap(&vnode1),
1685 client2.update_vnode_bitmap(&vnode2),
1686 )
1687 .map_err(Into::into),
1688 )
1689 .await
1690 .unwrap();
1691 assert_eq!(init_epoch, Some(epoch2));
1692 assert_eq!(update_vnode_bitmap_epoch1, epoch2);
1693 assert_eq!(update_vnode_bitmap_epoch2, epoch2);
1694 client3
1695 };
1696 let mut commit_future3 = pin!(client3.commit(
1697 epoch3,
1698 SinkMetadata {
1699 metadata: Some(Metadata::Serialized(SerializedMetadata {
1700 metadata: metadata_scale_out[2].clone(),
1701 })),
1702 },
1703 None,
1704 ));
1705 assert!(
1706 poll_fn(|cx| Poll::Ready(commit_future3.as_mut().poll(cx)))
1707 .await
1708 .is_pending()
1709 );
1710 let mut commit_future1 = pin!(client1.commit(
1711 epoch3,
1712 SinkMetadata {
1713 metadata: Some(Metadata::Serialized(SerializedMetadata {
1714 metadata: metadata_scale_out[0].clone(),
1715 })),
1716 },
1717 None,
1718 ));
1719 assert!(
1720 poll_fn(|cx| Poll::Ready(commit_future1.as_mut().poll(cx)))
1721 .await
1722 .is_pending()
1723 );
1724 assert!(
1725 poll_fn(|cx| Poll::Ready(commit_future3.as_mut().poll(cx)))
1726 .await
1727 .is_pending()
1728 );
1729 try_join(
1730 client2.commit(
1731 epoch3,
1732 SinkMetadata {
1733 metadata: Some(Metadata::Serialized(SerializedMetadata {
1734 metadata: metadata_scale_out[1].clone(),
1735 })),
1736 },
1737 None,
1738 ),
1739 try_join(commit_future1, commit_future3),
1740 )
1741 .await
1742 .unwrap();
1743 }
1744
1745 let (vnode2, vnode3) = {
1746 let (first, second) = all_vnode.split_at(VirtualNode::COUNT_FOR_TEST / 3);
1747 (build_bitmap(first), build_bitmap(second))
1748 };
1749
1750 {
1751 let (_, (update_vnode_bitmap_epoch2, update_vnode_bitmap_epoch3)) = try_join(
1752 client1.stop(),
1753 try_join(
1754 client2.update_vnode_bitmap(&vnode2),
1755 client3.update_vnode_bitmap(&vnode3),
1756 ),
1757 )
1758 .await
1759 .unwrap();
1760 assert_eq!(update_vnode_bitmap_epoch2, epoch3);
1761 assert_eq!(update_vnode_bitmap_epoch3, epoch3);
1762 }
1763
1764 {
1765 let mut commit_future = pin!(
1766 client2
1767 .commit(
1768 epoch4,
1769 SinkMetadata {
1770 metadata: Some(Metadata::Serialized(SerializedMetadata {
1771 metadata: metadata_scale_in[0].clone(),
1772 })),
1773 },
1774 None,
1775 )
1776 .map(|result| result.unwrap())
1777 );
1778 assert!(
1779 poll_fn(|cx| Poll::Ready(commit_future.as_mut().poll(cx)))
1780 .await
1781 .is_pending()
1782 );
1783 join(
1784 commit_future,
1785 client3
1786 .commit(
1787 epoch4,
1788 SinkMetadata {
1789 metadata: Some(Metadata::Serialized(SerializedMetadata {
1790 metadata: metadata_scale_in[1].clone(),
1791 })),
1792 },
1793 None,
1794 )
1795 .map(|result| result.unwrap()),
1796 )
1797 .await;
1798 }
1799 }
1800
1801 struct MockTwoPhaseCoordinator<
1802 P: FnMut(
1803 u64,
1804 Vec<SinkMetadata>,
1805 Option<PbSinkSchemaChange>,
1806 ) -> Result<Option<Vec<u8>>, SinkError>,
1807 CD: FnMut(u64, Vec<u8>) -> Result<(), SinkError>,
1808 CS: FnMut(u64, PbSinkSchemaChange) -> Result<(), SinkError>,
1809 > {
1810 pre_commit: P,
1811 commit_data: CD,
1812 commit_schema_change: CS,
1813 }
1814
1815 impl<
1816 P: FnMut(
1817 u64,
1818 Vec<SinkMetadata>,
1819 Option<PbSinkSchemaChange>,
1820 ) -> Result<Option<Vec<u8>>, SinkError>
1821 + Send
1822 + 'static,
1823 CD: FnMut(u64, Vec<u8>) -> Result<(), SinkError> + Send + 'static,
1824 CS: FnMut(u64, PbSinkSchemaChange) -> Result<(), SinkError> + Send + 'static,
1825 > MockTwoPhaseCoordinator<P, CD, CS>
1826 {
1827 fn new_coordinator(
1828 pre_commit: P,
1829 commit_data: CD,
1830 commit_schema_change: CS,
1831 ) -> SinkCommitCoordinator {
1832 SinkCommitCoordinator::TwoPhase(Box::new(MockTwoPhaseCoordinator {
1833 pre_commit,
1834 commit_data,
1835 commit_schema_change,
1836 }))
1837 }
1838 }
1839
1840 #[async_trait]
1841 impl<
1842 P: FnMut(
1843 u64,
1844 Vec<SinkMetadata>,
1845 Option<PbSinkSchemaChange>,
1846 ) -> Result<Option<Vec<u8>>, SinkError>
1847 + Send
1848 + 'static,
1849 CD: FnMut(u64, Vec<u8>) -> Result<(), SinkError> + Send + 'static,
1850 CS: FnMut(u64, PbSinkSchemaChange) -> Result<(), SinkError> + Send + 'static,
1851 > TwoPhaseCommitCoordinator for MockTwoPhaseCoordinator<P, CD, CS>
1852 {
1853 async fn init(&mut self) -> risingwave_connector::sink::Result<()> {
1854 Ok(())
1855 }
1856
1857 async fn pre_commit(
1858 &mut self,
1859 epoch: u64,
1860 metadata: Vec<SinkMetadata>,
1861 schema_change: Option<PbSinkSchemaChange>,
1862 ) -> risingwave_connector::sink::Result<Option<Vec<u8>>> {
1863 (self.pre_commit)(epoch, metadata, schema_change)
1864 }
1865
1866 async fn commit_data(
1867 &mut self,
1868 epoch: u64,
1869 commit_metadata: Vec<u8>,
1870 ) -> risingwave_connector::sink::Result<()> {
1871 (self.commit_data)(epoch, commit_metadata)
1872 }
1873
1874 async fn commit_schema_change(
1875 &mut self,
1876 epoch: u64,
1877 schema_change: PbSinkSchemaChange,
1878 ) -> risingwave_connector::sink::Result<()> {
1879 (self.commit_schema_change)(epoch, schema_change)
1880 }
1881
1882 async fn abort(&mut self, _epoch: u64, _commit_metadata: Vec<u8>) {
1883 tracing::debug!("abort called");
1884 }
1885 }
1886
1887 async fn prepare_db_backend() -> DatabaseConnection {
1888 let db: DatabaseConnection = Database::connect("sqlite::memory:").await.unwrap();
1889 let ddl = "
1890 CREATE TABLE IF NOT EXISTS pending_sink_state (
1891 sink_id i32 NOT NULL,
1892 epoch i64 NOT NULL,
1893 sink_state STRING NOT NULL,
1894 metadata BLOB,
1895 schema_change BLOB,
1896 PRIMARY KEY (sink_id, epoch)
1897 )
1898 ";
1899 db.execute(sea_orm::Statement::from_string(
1900 db.get_database_backend(),
1901 ddl.to_owned(),
1902 ))
1903 .await
1904 .unwrap();
1905 db
1906 }
1907
1908 async fn list_rows(
1909 db: &DatabaseConnection,
1910 ) -> Vec<(
1911 i32,
1912 i64,
1913 String,
1914 Option<Vec<u8>>,
1915 Option<PbSinkSchemaChange>,
1916 )> {
1917 let sql =
1918 "SELECT sink_id, epoch, sink_state, metadata, schema_change FROM pending_sink_state";
1919 let rows = db
1920 .query_all(sea_orm::Statement::from_string(
1921 db.get_database_backend(),
1922 sql.to_owned(),
1923 ))
1924 .await
1925 .unwrap();
1926 rows.into_iter()
1927 .map(|row| {
1928 (
1929 row.try_get("", "sink_id").unwrap(),
1930 row.try_get("", "epoch").unwrap(),
1931 row.try_get("", "sink_state").unwrap(),
1932 row.try_get("", "metadata").unwrap(),
1933 row.try_get::<Option<SinkSchemachange>>("", "schema_change")
1934 .unwrap()
1935 .map(|v| v.to_protobuf()),
1936 )
1937 })
1938 .collect()
1939 }
1940
1941 async fn set_epoch_aborted(db: &DatabaseConnection, sink_id: SinkId, epoch: u64) {
1942 let sql = format!(
1943 "UPDATE pending_sink_state SET sink_state = 'ABORTED' WHERE sink_id = {} AND epoch = {}",
1944 sink_id, epoch as i64
1945 );
1946 db.execute(sea_orm::Statement::from_string(
1947 db.get_database_backend(),
1948 sql,
1949 ))
1950 .await
1951 .unwrap();
1952 }
1953
1954 #[tokio::test]
1955 async fn test_init_response_skips_recovered_empty_pending_epoch() {
1956 let db = prepare_db_backend().await;
1957
1958 let param = SinkParam {
1959 sink_id: SinkId::from(1),
1960 sink_name: "test".into(),
1961 properties: Default::default(),
1962 columns: vec![],
1963 downstream_pk: None,
1964 sink_type: SinkType::AppendOnly,
1965 ignore_delete: false,
1966 format_desc: None,
1967 db_name: "test".into(),
1968 sink_from_name: "test".into(),
1969 };
1970
1971 let epoch1 = 233;
1972
1973 let sql = format!(
1974 "INSERT INTO pending_sink_state (sink_id, epoch, sink_state) VALUES ({}, {}, 'PENDING')",
1975 param.sink_id, epoch1
1976 );
1977 db.execute(sea_orm::Statement::from_string(
1978 db.get_database_backend(),
1979 sql,
1980 ))
1981 .await
1982 .unwrap();
1983
1984 let all_vnode = (0..VirtualNode::COUNT_FOR_TEST).collect_vec();
1985 let build_bitmap = |indexes: &[usize]| {
1986 let mut builder = BitmapBuilder::zeroed(VirtualNode::COUNT_FOR_TEST);
1987 for i in indexes {
1988 builder.set(*i, true);
1989 }
1990 builder.finish()
1991 };
1992 let vnode = build_bitmap(&all_vnode);
1993
1994 let sender = Arc::new(tokio::sync::Mutex::new(None));
1995 let mock_subscriber: SinkCommittedEpochSubscriber = {
1996 let captured_sender = sender.clone();
1997 Arc::new(move |_sink_id: SinkId| {
1998 let (epoch_sender, receiver) = unbounded_channel();
1999 let captured_sender = captured_sender.clone();
2000 async move {
2001 let mut guard = captured_sender.lock().await;
2002 *guard = Some(epoch_sender);
2003 Ok((epoch1, receiver))
2004 }
2005 .boxed()
2006 })
2007 };
2008
2009 let pre_commit_attempt = Arc::new(AtomicI32::new(0));
2010 let commit_attempt = Arc::new(AtomicI32::new(0));
2011 let (manager, (_join_handle, _stop_tx)) =
2012 SinkCoordinatorManager::start_worker_with_spawn_worker({
2013 let expected_param = param.clone();
2014 let db = db.clone();
2015 let pre_commit_attempt = pre_commit_attempt.clone();
2016 let commit_attempt = commit_attempt.clone();
2017 move |param, new_writer_rx| {
2018 let expected_param = expected_param.clone();
2019 let db = db.clone();
2020 let pre_commit_attempt = pre_commit_attempt.clone();
2021 let commit_attempt = commit_attempt.clone();
2022 tokio::spawn({
2023 let subscriber = mock_subscriber.clone();
2024 async move {
2025 assert_eq!(param, expected_param);
2026 CoordinatorWorker::execute_coordinator(
2027 db,
2028 param.clone(),
2029 new_writer_rx,
2030 MockTwoPhaseCoordinator::new_coordinator(
2031 move |_epoch, _metadata_list, _schema_change| {
2032 pre_commit_attempt.fetch_add(
2033 1,
2034 std::sync::atomic::Ordering::SeqCst,
2035 );
2036 Err(SinkError::Coordinator(anyhow!(
2037 "pre_commit should not be called for a known pending epoch"
2038 )))
2039 },
2040 move |_epoch, _commit_metadata| {
2041 commit_attempt
2042 .fetch_add(1, std::sync::atomic::Ordering::SeqCst);
2043 Ok(())
2044 },
2045 move |_epoch, _schema_change| unreachable!(),
2046 ),
2047 subscriber.clone(),
2048 )
2049 .await;
2050 }
2051 })
2052 }
2053 });
2054
2055 let (_client, log_store_rewind_start_epoch) =
2056 CoordinatorStreamHandle::new_with_init_stream(param.to_proto(), vnode, |rx| async {
2057 Ok(tonic::Response::new(
2058 manager
2059 .handle_new_request(ReceiverStream::new(rx).map(Ok).boxed())
2060 .await
2061 .unwrap()
2062 .boxed(),
2063 ))
2064 })
2065 .await
2066 .unwrap();
2067 assert_eq!(log_store_rewind_start_epoch, Some(epoch1));
2068
2069 for _ in 0..50 {
2070 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
2071 let rows = list_rows(&db).await;
2072 if rows[0].2 == "COMMITTED" {
2073 break;
2074 }
2075 }
2076
2077 assert_eq!(
2078 pre_commit_attempt.load(std::sync::atomic::Ordering::SeqCst),
2079 0
2080 );
2081 assert_eq!(commit_attempt.load(std::sync::atomic::Ordering::SeqCst), 0);
2082 let rows = list_rows(&db).await;
2083 assert_eq!(rows.len(), 1);
2084 assert_eq!(rows[0].1, epoch1 as i64);
2085 assert_eq!(rows[0].2, "COMMITTED");
2086 assert_eq!(rows[0].3, None);
2087 }
2088
2089 #[tokio::test]
2090 async fn test_pre_commit_failed() {
2091 let db = prepare_db_backend().await;
2092
2093 let param = SinkParam {
2094 sink_id: SinkId::from(1),
2095 sink_name: "test".into(),
2096 properties: Default::default(),
2097 columns: vec![],
2098 downstream_pk: None,
2099 sink_type: SinkType::AppendOnly,
2100 ignore_delete: false,
2101 format_desc: None,
2102 db_name: "test".into(),
2103 sink_from_name: "test".into(),
2104 };
2105
2106 let epoch1 = 233;
2107
2108 let all_vnode = (0..VirtualNode::COUNT_FOR_TEST).collect_vec();
2109 let build_bitmap = |indexes: &[usize]| {
2110 let mut builder = BitmapBuilder::zeroed(VirtualNode::COUNT_FOR_TEST);
2111 for i in indexes {
2112 builder.set(*i, true);
2113 }
2114 builder.finish()
2115 };
2116 let vnode = build_bitmap(&all_vnode);
2117
2118 let metadata = vec![1u8, 2u8];
2119 let sender = Arc::new(tokio::sync::Mutex::new(None));
2120 let mock_subscriber: SinkCommittedEpochSubscriber = {
2121 let captured_sender = sender.clone();
2122 Arc::new(move |_sink_id: SinkId| {
2123 let (sender, receiver) = unbounded_channel();
2124 let captured_sender = captured_sender.clone();
2125 async move {
2126 let mut guard = captured_sender.lock().await;
2127 *guard = Some(sender);
2128 Ok((epoch1, receiver))
2129 }
2130 .boxed()
2131 })
2132 };
2133
2134 let (manager, (_join_handle, _stop_tx)) =
2135 SinkCoordinatorManager::start_worker_with_spawn_worker({
2136 let expected_param = param.clone();
2137 let db = db.clone();
2138 move |param, new_writer_rx| {
2139 let expected_param = expected_param.clone();
2140 let db = db.clone();
2141 tokio::spawn({
2142 let subscriber = mock_subscriber.clone();
2143 async move {
2144 assert_eq!(param, expected_param);
2146 CoordinatorWorker::execute_coordinator(
2147 db,
2148 param.clone(),
2149 new_writer_rx,
2150 MockTwoPhaseCoordinator::new_coordinator(
2151 move |_epoch, _metadata_list, _schema_change| {
2152 Err(SinkError::Coordinator(anyhow!("failed to pre commit")))
2153 },
2154 move |_epoch, _commit_metadata| unreachable!(),
2155 move |_epoch, _schema_change| unreachable!(),
2156 ),
2157 subscriber.clone(),
2158 )
2159 .await;
2160 }
2161 })
2162 }
2163 });
2164
2165 let build_client = |vnode| async {
2166 CoordinatorStreamHandle::new_with_init_stream(param.to_proto(), vnode, |rx| async {
2167 Ok(tonic::Response::new(
2168 manager
2169 .handle_new_request(ReceiverStream::new(rx).map(Ok).boxed())
2170 .await
2171 .unwrap()
2172 .boxed(),
2173 ))
2174 })
2175 .await
2176 .unwrap()
2177 .0
2178 };
2179
2180 let mut client = build_client(vnode).await;
2181
2182 let aligned_epoch = client.align_initial_epoch(1).await.unwrap();
2183 assert_eq!(aligned_epoch, 1);
2184
2185 let commit_result = client
2186 .commit(
2187 epoch1,
2188 SinkMetadata {
2189 metadata: Some(Metadata::Serialized(SerializedMetadata {
2190 metadata: metadata.clone(),
2191 })),
2192 },
2193 None,
2194 )
2195 .await;
2196 assert!(commit_result.is_err());
2197
2198 let rows = list_rows(&db).await;
2199 assert!(rows.is_empty());
2200 }
2201
2202 #[tokio::test]
2203 async fn test_empty_pre_commit_waiting_on_checkpoint() {
2204 let db = prepare_db_backend().await;
2205
2206 let param = SinkParam {
2207 sink_id: SinkId::from(1),
2208 sink_name: "test".into(),
2209 properties: Default::default(),
2210 columns: vec![],
2211 downstream_pk: None,
2212 sink_type: SinkType::AppendOnly,
2213 ignore_delete: false,
2214 format_desc: None,
2215 db_name: "test".into(),
2216 sink_from_name: "test".into(),
2217 };
2218
2219 let epoch0 = 232;
2220 let epoch1 = 233;
2221
2222 let all_vnode = (0..VirtualNode::COUNT_FOR_TEST).collect_vec();
2223 let build_bitmap = |indexes: &[usize]| {
2224 let mut builder = BitmapBuilder::zeroed(VirtualNode::COUNT_FOR_TEST);
2225 for i in indexes {
2226 builder.set(*i, true);
2227 }
2228 builder.finish()
2229 };
2230 let vnode = build_bitmap(&all_vnode);
2231
2232 let metadata = vec![1u8, 2u8];
2233
2234 let sender = Arc::new(tokio::sync::Mutex::new(None));
2235 let mock_subscriber: SinkCommittedEpochSubscriber = {
2236 let captured_sender = sender.clone();
2237 Arc::new(move |_sink_id: SinkId| {
2238 let (sender, receiver) = unbounded_channel();
2239 let captured_sender = captured_sender.clone();
2240 async move {
2241 let mut guard = captured_sender.lock().await;
2242 *guard = Some(sender);
2243 Ok((epoch0, receiver))
2244 }
2245 .boxed()
2246 })
2247 };
2248
2249 let (manager, (_join_handle, _stop_tx)) =
2250 SinkCoordinatorManager::start_worker_with_spawn_worker({
2251 let expected_param = param.clone();
2252 let db = db.clone();
2253 move |param, new_writer_rx| {
2254 let expected_param = expected_param.clone();
2255 let db = db.clone();
2256 tokio::spawn({
2257 let subscriber = mock_subscriber.clone();
2258 async move {
2259 assert_eq!(param, expected_param);
2261 CoordinatorWorker::execute_coordinator(
2262 db,
2263 param.clone(),
2264 new_writer_rx,
2265 MockTwoPhaseCoordinator::new_coordinator(
2266 move |_epoch, metadata_list, _schema_change| {
2267 assert_eq!(metadata_list.len(), 1);
2268 Ok(None)
2269 },
2270 move |_epoch, _commit_metadata| unreachable!(),
2271 move |_epoch, _schema_change| unreachable!(),
2272 ),
2273 subscriber.clone(),
2274 )
2275 .await;
2276 }
2277 })
2278 }
2279 });
2280
2281 let build_client = |vnode| async {
2282 CoordinatorStreamHandle::new_with_init_stream(param.to_proto(), vnode, |rx| async {
2283 Ok(tonic::Response::new(
2284 manager
2285 .handle_new_request(ReceiverStream::new(rx).map(Ok).boxed())
2286 .await
2287 .unwrap()
2288 .boxed(),
2289 ))
2290 })
2291 .await
2292 .unwrap()
2293 .0
2294 };
2295
2296 let mut client = build_client(vnode).await;
2297
2298 let aligned_epoch = client.align_initial_epoch(1).await.unwrap();
2299 assert_eq!(aligned_epoch, 1);
2300
2301 client
2302 .commit(
2303 epoch1,
2304 SinkMetadata {
2305 metadata: Some(Metadata::Serialized(SerializedMetadata {
2306 metadata: metadata.clone(),
2307 })),
2308 },
2309 None,
2310 )
2311 .await
2312 .unwrap();
2313
2314 {
2315 let rows = list_rows(&db).await;
2316 assert_eq!(rows.len(), 1);
2317 assert_eq!(rows[0].1, epoch1 as i64);
2318 assert_eq!(rows[0].2, "PENDING");
2319 assert_eq!(rows[0].3, None);
2320
2321 let guard = sender.lock().await;
2322 let sender = guard.as_ref().unwrap().clone();
2323 sender.send(epoch1).unwrap();
2324 }
2325
2326 for _ in 0..50 {
2328 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
2329 let rows = list_rows(&db).await;
2330 if rows[0].2 == "COMMITTED" {
2331 break;
2332 }
2333 }
2334
2335 {
2336 let rows = list_rows(&db).await;
2337 assert_eq!(rows.len(), 1);
2338 assert_eq!(rows[0].1, epoch1 as i64);
2339 assert_eq!(rows[0].2, "COMMITTED");
2340 assert_eq!(rows[0].3, None);
2341 }
2342 }
2343
2344 #[tokio::test]
2345 async fn test_commit_retry_loop() {
2346 let db = prepare_db_backend().await;
2347
2348 let param = SinkParam {
2349 sink_id: SinkId::from(1),
2350 sink_name: "test".into(),
2351 properties: Default::default(),
2352 columns: vec![],
2353 downstream_pk: None,
2354 sink_type: SinkType::AppendOnly,
2355 ignore_delete: false,
2356 format_desc: None,
2357 db_name: "test".into(),
2358 sink_from_name: "test".into(),
2359 };
2360
2361 let epoch1 = 233;
2362
2363 let all_vnode = (0..VirtualNode::COUNT_FOR_TEST).collect_vec();
2364 let build_bitmap = |indexes: &[usize]| {
2365 let mut builder = BitmapBuilder::zeroed(VirtualNode::COUNT_FOR_TEST);
2366 for i in indexes {
2367 builder.set(*i, true);
2368 }
2369 builder.finish()
2370 };
2371 let vnode = build_bitmap(&all_vnode);
2372
2373 let metadata = vec![1u8, 2u8];
2374 let sender = Arc::new(tokio::sync::Mutex::new(None));
2375 let mock_subscriber: SinkCommittedEpochSubscriber = {
2376 let captured_sender = sender.clone();
2377 Arc::new(move |_sink_id: SinkId| {
2378 let (sender, receiver) = unbounded_channel();
2379 let captured_sender = captured_sender.clone();
2380 async move {
2381 let mut guard = captured_sender.lock().await;
2382 *guard = Some(sender);
2383 Ok((epoch1, receiver))
2384 }
2385 .boxed()
2386 })
2387 };
2388
2389 let commit_attempt = Arc::new(AtomicI32::new(0));
2390
2391 let (manager, (_join_handle, _stop_tx)) =
2392 SinkCoordinatorManager::start_worker_with_spawn_worker({
2393 let expected_param = param.clone();
2394 let metadata = metadata.clone();
2395 let db = db.clone();
2396 let commit_attempt = commit_attempt.clone();
2397 move |param, new_writer_rx| {
2398 let metadata = metadata.clone();
2399 let expected_param = expected_param.clone();
2400 let db = db.clone();
2401 let commit_attempt = commit_attempt.clone();
2402 tokio::spawn({
2403 let subscriber = mock_subscriber.clone();
2404 async move {
2405 assert_eq!(param, expected_param);
2407 CoordinatorWorker::execute_coordinator(
2408 db,
2409 param.clone(),
2410 new_writer_rx,
2411 MockTwoPhaseCoordinator::new_coordinator(
2412 move |_epoch, metadata_list, _schema_change| {
2413 let metadata =
2414 Itertools::exactly_one(metadata_list.into_iter())
2415 .unwrap();
2416 Ok(match metadata.metadata {
2417 Some(Metadata::Serialized(SerializedMetadata {
2418 metadata,
2419 })) => Some(metadata),
2420 _ => unreachable!(),
2421 })
2422 },
2423 move |_epoch, commit_metadata| {
2424 assert_eq!(commit_metadata, metadata);
2425 if commit_attempt
2426 .fetch_add(1, std::sync::atomic::Ordering::SeqCst)
2427 < 2
2428 {
2429 Err(SinkError::Coordinator(anyhow!("failed to commit")))
2430 } else {
2431 Ok(())
2432 }
2433 },
2434 move |_epoch, _schema_change| unreachable!(),
2435 ),
2436 subscriber.clone(),
2437 )
2438 .await;
2439 }
2440 })
2441 }
2442 });
2443
2444 let build_client = |vnode| async {
2445 CoordinatorStreamHandle::new_with_init_stream(param.to_proto(), vnode, |rx| async {
2446 Ok(tonic::Response::new(
2447 manager
2448 .handle_new_request(ReceiverStream::new(rx).map(Ok).boxed())
2449 .await
2450 .unwrap()
2451 .boxed(),
2452 ))
2453 })
2454 .await
2455 .unwrap()
2456 .0
2457 };
2458
2459 let mut client = build_client(vnode).await;
2460
2461 let aligned_epoch = client.align_initial_epoch(1).await.unwrap();
2462 assert_eq!(aligned_epoch, 1);
2463
2464 client
2465 .commit(
2466 epoch1,
2467 SinkMetadata {
2468 metadata: Some(Metadata::Serialized(SerializedMetadata {
2469 metadata: metadata.clone(),
2470 })),
2471 },
2472 None,
2473 )
2474 .await
2475 .unwrap();
2476
2477 for _ in 0..100 {
2479 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
2480 let rows = list_rows(&db).await;
2481 if rows[0].2 == "COMMITTED" {
2482 break;
2483 }
2484 }
2485
2486 assert_eq!(commit_attempt.load(std::sync::atomic::Ordering::SeqCst), 3);
2487
2488 {
2489 let rows = list_rows(&db).await;
2490 assert_eq!(rows.len(), 1);
2491 assert_eq!(rows[0].1, epoch1 as i64);
2492 assert_eq!(rows[0].2, "COMMITTED");
2493 }
2494 }
2495
2496 #[tokio::test]
2497 async fn test_aborted() {
2498 let db = prepare_db_backend().await;
2499
2500 let param = SinkParam {
2501 sink_id: SinkId::from(1),
2502 sink_name: "test".into(),
2503 properties: Default::default(),
2504 columns: vec![],
2505 downstream_pk: None,
2506 sink_type: SinkType::AppendOnly,
2507 ignore_delete: false,
2508 format_desc: None,
2509 db_name: "test".into(),
2510 sink_from_name: "test".into(),
2511 };
2512
2513 let epoch0 = 232;
2514 let epoch1 = 233;
2515
2516 let all_vnode = (0..VirtualNode::COUNT_FOR_TEST).collect_vec();
2517 let build_bitmap = |indexes: &[usize]| {
2518 let mut builder = BitmapBuilder::zeroed(VirtualNode::COUNT_FOR_TEST);
2519 for i in indexes {
2520 builder.set(*i, true);
2521 }
2522 builder.finish()
2523 };
2524 let vnode = build_bitmap(&all_vnode);
2525
2526 let metadata = vec![1u8, 2u8];
2527
2528 let sender = Arc::new(tokio::sync::Mutex::new(None));
2529 let mock_subscriber: SinkCommittedEpochSubscriber = {
2530 let captured_sender = sender.clone();
2531 Arc::new(move |_sink_id: SinkId| {
2532 let (sender, receiver) = unbounded_channel();
2533 let captured_sender = captured_sender.clone();
2534 async move {
2535 let mut guard = captured_sender.lock().await;
2536 *guard = Some(sender);
2537 Ok((epoch0, receiver))
2538 }
2539 .boxed()
2540 })
2541 };
2542
2543 let (manager, (_join_handle, _stop_tx)) =
2544 SinkCoordinatorManager::start_worker_with_spawn_worker({
2545 let expected_param = param.clone();
2546 let metadata = metadata.clone();
2547 let db = db.clone();
2548 move |param, new_writer_rx| {
2549 let metadata = metadata.clone();
2550 let expected_param = expected_param.clone();
2551 let db = db.clone();
2552 tokio::spawn({
2553 let subscriber = mock_subscriber.clone();
2554 async move {
2555 assert_eq!(param, expected_param);
2557 CoordinatorWorker::execute_coordinator(
2558 db,
2559 param.clone(),
2560 new_writer_rx,
2561 MockTwoPhaseCoordinator::new_coordinator(
2562 move |_epoch, metadata_list, _schema_change| {
2563 let metadata =
2564 Itertools::exactly_one(metadata_list.into_iter())
2565 .unwrap();
2566 Ok(match metadata.metadata {
2567 Some(Metadata::Serialized(SerializedMetadata {
2568 metadata,
2569 })) => Some(metadata),
2570 _ => unreachable!(),
2571 })
2572 },
2573 move |_epoch, commit_metadata| {
2574 assert_eq!(commit_metadata, metadata);
2575 Ok(())
2576 },
2577 move |_epoch, _schema_change| unreachable!(),
2578 ),
2579 subscriber.clone(),
2580 )
2581 .await;
2582 }
2583 })
2584 }
2585 });
2586
2587 let build_client = |vnode| async {
2588 CoordinatorStreamHandle::new_with_init_stream(param.to_proto(), vnode, |rx| async {
2589 Ok(tonic::Response::new(
2590 manager
2591 .handle_new_request(ReceiverStream::new(rx).map(Ok).boxed())
2592 .await
2593 .unwrap()
2594 .boxed(),
2595 ))
2596 })
2597 .await
2598 .unwrap()
2599 .0
2600 };
2601
2602 let mut client = build_client(vnode.clone()).await;
2603
2604 let aligned_epoch = client.align_initial_epoch(1).await.unwrap();
2605 assert_eq!(aligned_epoch, 1);
2606
2607 client
2608 .commit(
2609 epoch1,
2610 SinkMetadata {
2611 metadata: Some(Metadata::Serialized(SerializedMetadata {
2612 metadata: metadata.clone(),
2613 })),
2614 },
2615 None,
2616 )
2617 .await
2618 .unwrap();
2619
2620 manager
2621 .stop_sink_coordinators_for_jobs(vec![JobId::from(1)])
2622 .await;
2623
2624 {
2625 let rows = list_rows(&db).await;
2626 assert_eq!(rows.len(), 1);
2627 assert_eq!(rows[0].1, epoch1 as i64);
2628 assert_eq!(rows[0].2, "PENDING");
2629
2630 set_epoch_aborted(&db, SinkId::from(1), epoch1).await;
2631 let rows = list_rows(&db).await;
2632 assert_eq!(rows.len(), 1);
2633 assert_eq!(rows[0].1, epoch1 as i64);
2634 assert_eq!(rows[0].2, "ABORTED");
2635 }
2636
2637 let mut client = build_client(vnode).await;
2638
2639 let aligned_epoch = client.align_initial_epoch(1).await.unwrap();
2640 assert_eq!(aligned_epoch, 1);
2641
2642 {
2643 let rows = list_rows(&db).await;
2644 assert!(rows.is_empty());
2645 }
2646 }
2647
2648 #[tokio::test]
2649 async fn test_flush_when_reschedule() {
2650 let db = prepare_db_backend().await;
2651
2652 let param = SinkParam {
2653 sink_id: SinkId::from(1),
2654 sink_name: "test".into(),
2655 properties: Default::default(),
2656 columns: vec![],
2657 downstream_pk: None,
2658 sink_type: SinkType::AppendOnly,
2659 ignore_delete: false,
2660 format_desc: None,
2661 db_name: "test".into(),
2662 sink_from_name: "test".into(),
2663 };
2664
2665 let epoch0 = 232;
2666 let epoch1 = 233;
2667
2668 let all_vnode = (0..VirtualNode::COUNT_FOR_TEST).collect_vec();
2669 let build_bitmap = |indexes: &[usize]| {
2670 let mut builder = BitmapBuilder::zeroed(VirtualNode::COUNT_FOR_TEST);
2671 for i in indexes {
2672 builder.set(*i, true);
2673 }
2674 builder.finish()
2675 };
2676 let vnode = build_bitmap(&all_vnode);
2677
2678 let metadata = vec![1u8, 2u8];
2679 let schema_change = PbSinkSchemaChange {
2680 original_schema: vec![PbField {
2681 data_type: Some(PbDataType {
2682 type_name: PbTypeName::Int32 as i32,
2683 ..Default::default()
2684 }),
2685 name: "col_v1".into(),
2686 }],
2687 op: Some(SinkSchemachangeOp::AddColumns(PbSinkAddColumnsOp {
2688 fields: vec![PbField {
2689 data_type: Some(PbDataType {
2690 type_name: PbTypeName::Varchar as i32,
2691 ..Default::default()
2692 }),
2693 name: "new_col".into(),
2694 }],
2695 })),
2696 };
2697
2698 let sender = Arc::new(tokio::sync::Mutex::new(None));
2699 let mock_subscriber: SinkCommittedEpochSubscriber = {
2700 let captured_sender = sender.clone();
2701 Arc::new(move |_sink_id: SinkId| {
2702 let (sender, receiver) = unbounded_channel();
2703 let captured_sender = captured_sender.clone();
2704 async move {
2705 let mut guard = captured_sender.lock().await;
2706 *guard = Some(sender);
2707 Ok((epoch0, receiver))
2708 }
2709 .boxed()
2710 })
2711 };
2712
2713 let (manager, (_join_handle, _stop_tx)) =
2714 SinkCoordinatorManager::start_worker_with_spawn_worker({
2715 let expected_param = param.clone();
2716 let metadata = metadata.clone();
2717 let schema_change = schema_change.clone();
2718 let db = db.clone();
2719 move |param, new_writer_rx| {
2720 let metadata = metadata.clone();
2721 let schema_change_for_pre_commit = schema_change.clone();
2722 let schema_change_for_commit = schema_change.clone();
2723 let expected_param = expected_param.clone();
2724 let db = db.clone();
2725 tokio::spawn({
2726 let subscriber = mock_subscriber.clone();
2727 async move {
2728 assert_eq!(param, expected_param);
2729 CoordinatorWorker::execute_coordinator(
2730 db,
2731 param.clone(),
2732 new_writer_rx,
2733 MockTwoPhaseCoordinator::new_coordinator(
2734 move |_epoch, metadata_list, schema_change| {
2735 assert_eq!(
2736 schema_change,
2737 Some(schema_change_for_pre_commit.clone())
2738 );
2739 let metadata =
2740 Itertools::exactly_one(metadata_list.into_iter())
2741 .unwrap();
2742 Ok(match metadata.metadata {
2743 Some(Metadata::Serialized(SerializedMetadata {
2744 metadata,
2745 })) => Some(metadata),
2746 _ => unreachable!(),
2747 })
2748 },
2749 move |_epoch, commit_metadata| {
2750 assert_eq!(commit_metadata, metadata);
2751 Ok(())
2752 },
2753 move |_epoch, schema_change| {
2754 assert_eq!(schema_change, schema_change_for_commit.clone());
2755 Ok(())
2756 },
2757 ),
2758 subscriber.clone(),
2759 )
2760 .await;
2761 }
2762 })
2763 }
2764 });
2765
2766 let build_client = |vnode| async {
2767 CoordinatorStreamHandle::new_with_init_stream(param.to_proto(), vnode, |rx| async {
2768 Ok(tonic::Response::new(
2769 manager
2770 .handle_new_request(ReceiverStream::new(rx).map(Ok).boxed())
2771 .await
2772 .unwrap()
2773 .boxed(),
2774 ))
2775 })
2776 .await
2777 };
2778
2779 let (mut client1, _) = build_client(vnode.clone()).await.unwrap();
2780
2781 let aligned_epoch = client1.align_initial_epoch(1).await.unwrap();
2782 assert_eq!(aligned_epoch, 1);
2783
2784 client1
2785 .commit(
2786 epoch1,
2787 SinkMetadata {
2788 metadata: Some(Metadata::Serialized(SerializedMetadata {
2789 metadata: metadata.clone(),
2790 })),
2791 },
2792 Some(schema_change.clone()),
2793 )
2794 .await
2795 .unwrap();
2796
2797 {
2798 let rows = list_rows(&db).await;
2799 assert_eq!(rows.len(), 1);
2800 assert_eq!(rows[0].1, epoch1 as i64);
2801 assert_eq!(rows[0].2, "PENDING");
2802 assert_eq!(rows[0].4, Some(schema_change.clone()));
2803 }
2804
2805 let mut build_client2_future = pin!(build_client(vnode.clone()));
2806 assert!(
2807 poll_fn(|cx| Poll::Ready(build_client2_future.as_mut().poll(cx)))
2808 .await
2809 .is_pending()
2810 );
2811
2812 client1.stop().await.unwrap();
2813
2814 assert!(
2815 poll_fn(|cx| Poll::Ready(build_client2_future.as_mut().poll(cx)))
2816 .await
2817 .is_pending()
2818 );
2819
2820 {
2821 let guard = sender.lock().await;
2822 let sender = guard.as_ref().unwrap().clone();
2823 sender.send(epoch1).unwrap();
2824 }
2825
2826 let (_, init_epoch) = build_client2_future.await.unwrap();
2827 assert_eq!(init_epoch, Some(epoch1));
2828
2829 {
2830 let rows = list_rows(&db).await;
2831 assert_eq!(rows.len(), 1);
2832 assert_eq!(rows[0].1, epoch1 as i64);
2833 assert_eq!(rows[0].2, "COMMITTED");
2834 assert_eq!(rows[0].4, Some(schema_change.clone()));
2835 }
2836 }
2837}