Skip to main content

risingwave_meta/manager/sink_coordination/
manager.rs

1// Copyright 2023 RisingWave Labs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::collections::{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        /// Streaming jobs whose sink coordinators should be stopped.
65        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    /// Let lifecycle tests control when coordinator stop requests complete.
130    #[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    /// Sender to coordinator worker. Drop the sender as a stop signal
264    request_sender: Option<UnboundedSender<SinkWriterCoordinationHandle>>,
265    /// Notify when the coordinator worker stops
266    finish_notifiers: Vec<CollectionNotifier>,
267}
268
269struct ManagerWorker {
270    request_rx: mpsc::Receiver<ManagerRequest>,
271    // Make it option so that it can be polled with &mut SinkManagerWorker
272    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 the sender as a signal to notify the coordinator worker to stop.
393            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 the sender to notify the coordinator worker to stop
459                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                // Launch the coordinator worker task if it is the first
516                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                            // validate the start request
928                            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            // commit epoch1
1006            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        // commit epoch2
1042        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                            // validate the start request
1134                            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                            // validate the start request
1284                            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        // commit epoch
1319        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                                // validate the start request
1393                                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        // commit epoch
1428        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                            // validate the start request
1531                            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            // commit epoch1
1604            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                // commit epoch2
1659                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    /// After a replace job finishes, recovery builds the coordinator from writers that already have
2106    /// the new schema, while the schema change can still be pending in the meta store. The pending
2107    /// change must be committed before any writer receives its start response, because writers
2108    /// validate the sink table against their new schema once they start.
2109    #[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        // Keep the sender alive so that the coordinator keeps waiting for new committed epochs.
2164        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                                        // Fail the first attempt so that a writer started before
2210                                        // the retry would observe an uncommitted schema change.
2211                                        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                            // validate the start request
2309                            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                            // validate the start request
2424                            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        // wait max 5 seconds for the commit to be processed
2491        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                            // validate the start request
2570                            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        // wait max 10 seconds for the commit to be processed
2642        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                            // validate the start request
2720                            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}