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