Skip to main content

datanode/
alive_keeper.rs

1// Copyright 2023 Greptime Team
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;
16use std::sync::Arc;
17use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
18
19use api::v1::meta::GrantedRegion;
20use async_trait::async_trait;
21use common_error::ext::ErrorExt;
22use common_error::status_code::StatusCode;
23use common_meta::error::InvalidProtoMsgSnafu;
24use common_meta::heartbeat::handler::{
25    HandleControl, HeartbeatResponseHandler, HeartbeatResponseHandlerContext,
26};
27use common_telemetry::{debug, error, info, trace, warn};
28use snafu::OptionExt;
29use store_api::region_engine::{RegionRole, SettableRegionRoleState};
30use store_api::region_request::{RegionCloseRequest, RegionRequest};
31use store_api::storage::RegionId;
32#[cfg(test)]
33use tokio::sync::oneshot;
34use tokio::sync::{Mutex, mpsc};
35use tokio::task::JoinHandle;
36use tokio::time::{Duration, Instant};
37
38use crate::error::{self, Result};
39use crate::event_listener::{RegionServerEvent, RegionServerEventReceiver};
40use crate::region_server::RegionServer;
41
42/// [RegionAliveKeeper] manages all [CountdownTaskHandle]s.
43///
44/// [RegionAliveKeeper] starts a [CountdownTask] for each region. When the deadline is reached,
45/// the status of region be set to "readonly", ensures there is no side-effect in the entity system.
46///
47/// The deadline is controlled by the meta server. Datanode will send its opened regions info to meta sever
48/// via heartbeat. If the meta server decides some region could be resided in this Datanode,
49/// it will renew the lease of region, a deadline of [CountdownTask] will be reset.
50pub struct RegionAliveKeeper {
51    region_server: RegionServer,
52    tasks: Arc<Mutex<HashMap<RegionId, Arc<CountdownTaskHandle>>>>,
53    heartbeat_interval_millis: Arc<AtomicU64>,
54    started: Arc<AtomicBool>,
55
56    /// The epoch when [RegionAliveKeeper] is created. It's used to get a monotonically non-decreasing
57    /// elapsed time when submitting heartbeats to the meta server (because [Instant] is monotonically
58    /// non-decreasing). The heartbeat requests will carry the duration since this epoch, and the
59    /// duration acts like an "invariant point" for region's keep alive lease.
60    epoch: Instant,
61
62    countdown_task_handler_ext: Option<CountdownTaskHandlerExtRef>,
63}
64
65impl RegionAliveKeeper {
66    /// Returns an empty [RegionAliveKeeper].
67    pub fn new(
68        region_server: RegionServer,
69        countdown_task_handler_ext: Option<CountdownTaskHandlerExtRef>,
70        heartbeat_interval: Duration,
71    ) -> Self {
72        Self {
73            region_server,
74            tasks: Arc::new(Mutex::new(HashMap::new())),
75            heartbeat_interval_millis: Arc::new(AtomicU64::new(
76                heartbeat_interval.as_millis() as u64
77            )),
78            started: Arc::new(AtomicBool::new(false)),
79            epoch: Instant::now(),
80            countdown_task_handler_ext,
81        }
82    }
83
84    /// Update the heartbeat interval with the value received from Metasrv.
85    pub fn update_heartbeat_interval(&self, heartbeat_interval_millis: u64) {
86        self.heartbeat_interval_millis
87            .store(heartbeat_interval_millis, Ordering::Relaxed);
88    }
89
90    async fn find_handle(&self, region_id: RegionId) -> Option<Arc<CountdownTaskHandle>> {
91        self.tasks.lock().await.get(&region_id).cloned()
92    }
93
94    /// Add the countdown task for a specific region.
95    /// It will be ignored if the task exists.
96    pub async fn register_region(&self, region_id: RegionId) {
97        let handle = Arc::new(CountdownTaskHandle::new(
98            self.region_server.clone(),
99            self.countdown_task_handler_ext.clone(),
100            region_id,
101        ));
102
103        let should_start = {
104            let mut handles = self.tasks.lock().await;
105
106            // Check if already exists, return early if so
107            if handles.contains_key(&region_id) {
108                return;
109            }
110
111            // Insert new handle
112            handles.insert(region_id, handle.clone());
113
114            // Return whether we should start (check state inside lock)
115            self.started.load(Ordering::Relaxed)
116        };
117
118        if should_start {
119            handle
120                .start(self.heartbeat_interval_millis.load(Ordering::Relaxed))
121                .await;
122            info!("Region alive countdown for region {region_id} is started!");
123        } else {
124            info!(
125                "Region alive countdown for region {region_id} is registered but not started yet!"
126            );
127        }
128    }
129
130    /// Removes the countdown task for a specific region.
131    pub async fn deregister_region(&self, region_id: RegionId) {
132        if self.tasks.lock().await.remove(&region_id).is_some() {
133            info!("Deregister alive countdown for region {region_id}")
134        }
135    }
136
137    /// Renews the lease of regions to `deadline`.
138    async fn renew_region_leases(&self, regions: &[GrantedRegion], deadline: Instant) {
139        for region in regions {
140            let (role, region_id) = (region.role().into(), RegionId::from(region.region_id));
141            if let Some(handle) = self.find_handle(region_id).await {
142                handle
143                    .reset_deadline(role, deadline, region.extensions.clone())
144                    .await;
145            } else {
146                warn!(
147                    "Trying to renew the lease for region {region_id}, the keeper handler is not found!"
148                );
149                // Else the region alive keeper might be triggered by lagging messages, we can safely ignore it.
150            }
151        }
152    }
153
154    async fn close_staled_region(&self, region_id: RegionId) {
155        info!("Closing staled region: {region_id}");
156        let request = RegionRequest::Close(RegionCloseRequest::default());
157        if let Err(e) = self.region_server.handle_request(region_id, request).await
158            && e.status_code() != StatusCode::RegionNotFound
159        {
160            let _ = self
161                .region_server
162                .set_region_role(region_id, RegionRole::Follower);
163            error!(e; "Failed to close staled region {}, convert region to follower.", region_id);
164        }
165    }
166
167    /// Closes staled regions.
168    async fn close_staled_regions(&self, regions: &[u64]) {
169        for region_id in regions {
170            self.close_staled_region(RegionId::from_u64(*region_id))
171                .await;
172        }
173    }
174
175    #[cfg(test)]
176    async fn deadline(&self, region_id: RegionId) -> Option<Instant> {
177        let mut deadline = None;
178        if let Some(handle) = self.find_handle(region_id).await {
179            let (s, r) = oneshot::channel();
180            if handle.tx.send(CountdownCommand::Deadline(s)).await.is_ok() {
181                deadline = r.await.ok()
182            }
183        }
184        deadline
185    }
186
187    pub async fn start(
188        self: &Arc<Self>,
189        event_receiver: Option<RegionServerEventReceiver>,
190    ) -> Result<()> {
191        self.started.store(true, Ordering::Relaxed);
192
193        if let Some(mut event_receiver) = event_receiver {
194            let keeper = self.clone();
195            // Initializers region alive keeper.
196            // It makes sure all opened regions are registered to `RegionAliveKeeper.`
197            loop {
198                match event_receiver.0.try_recv() {
199                    Ok(RegionServerEvent::Registered(region_id)) => {
200                        keeper.register_region(region_id).await;
201                    }
202                    Ok(RegionServerEvent::Deregistered(region_id)) => {
203                        keeper.deregister_region(region_id).await;
204                    }
205                    Err(mpsc::error::TryRecvError::Disconnected) => {
206                        return error::UnexpectedSnafu {
207                            violated: "RegionServerEventSender closed",
208                        }
209                        .fail();
210                    }
211                    Err(mpsc::error::TryRecvError::Empty) => {
212                        break;
213                    }
214                }
215            }
216            let running = self.started.clone();
217
218            // Watches changes
219            common_runtime::spawn_global(async move {
220                loop {
221                    if !running.load(Ordering::Relaxed) {
222                        info!("RegionAliveKeeper stopped! Quits the watch loop!");
223                        break;
224                    }
225
226                    match event_receiver.0.recv().await {
227                        Some(RegionServerEvent::Registered(region_id)) => {
228                            keeper.register_region(region_id).await;
229                        }
230                        Some(RegionServerEvent::Deregistered(region_id)) => {
231                            keeper.deregister_region(region_id).await;
232                        }
233                        None => {
234                            info!("RegionServerEventSender closed! Quits the watch loop!");
235                            break;
236                        }
237                    }
238                }
239            });
240        }
241
242        let tasks = self.tasks.lock().await;
243        let interval = self.heartbeat_interval_millis.load(Ordering::Relaxed);
244        for task in tasks.values() {
245            task.start(interval).await;
246        }
247
248        info!(
249            "RegionAliveKeeper is started with region {:?}",
250            tasks.keys().map(|x| x.to_string()).collect::<Vec<_>>(),
251        );
252
253        Ok(())
254    }
255
256    pub fn epoch(&self) -> Instant {
257        self.epoch
258    }
259}
260
261#[async_trait]
262impl HeartbeatResponseHandler for RegionAliveKeeper {
263    fn is_acceptable(&self, ctx: &HeartbeatResponseHandlerContext) -> bool {
264        ctx.response.region_lease.is_some()
265    }
266
267    async fn handle(
268        &self,
269        ctx: &mut HeartbeatResponseHandlerContext,
270    ) -> common_meta::error::Result<HandleControl> {
271        let region_lease = ctx
272            .response
273            .region_lease
274            .as_ref()
275            .context(InvalidProtoMsgSnafu {
276                err_msg: "'region_lease' is missing in heartbeat response",
277            })?;
278        let start_instant = self.epoch + Duration::from_millis(region_lease.duration_since_epoch);
279        let deadline = start_instant + Duration::from_secs(region_lease.lease_seconds);
280
281        if let Some(mito) = self.region_server.mito_engine() {
282            mito.renew_node_lease(deadline.into_std());
283        }
284        self.renew_region_leases(&region_lease.regions, deadline)
285            .await;
286        self.close_staled_regions(&region_lease.closeable_region_ids)
287            .await;
288
289        Ok(HandleControl::Continue)
290    }
291}
292
293#[derive(Debug)]
294enum CountdownCommand {
295    /// Start this countdown task. The first deadline will be set to
296    /// 4 * `heartbeat_interval_millis`
297    Start(u64),
298    /// Reset countdown deadline to the given instance.
299    /// (NextRole, Deadline, ExtensionInfo)
300    Reset((RegionRole, Instant, HashMap<String, Vec<u8>>)),
301    /// Returns the current deadline of the countdown task.
302    #[cfg(test)]
303    Deadline(oneshot::Sender<Instant>),
304}
305
306pub type CountdownTaskHandlerExtRef = Arc<dyn CountdownTaskHandlerExt>;
307
308/// Extension trait for [CountdownTaskHandlerExt] to reset deadline of a region.
309#[async_trait]
310pub trait CountdownTaskHandlerExt: Send + Sync {
311    async fn reset_deadline(
312        &self,
313        region_server: &RegionServer,
314        region_id: RegionId,
315        role: RegionRole,
316        deadline: Instant,
317        extension_info: HashMap<String, Vec<u8>>,
318    );
319}
320
321struct CountdownTaskHandle {
322    tx: mpsc::Sender<CountdownCommand>,
323    handler: JoinHandle<()>,
324    region_id: RegionId,
325}
326
327impl CountdownTaskHandle {
328    /// Creates a new [CountdownTaskHandle] and starts the countdown task.
329    fn new(
330        region_server: RegionServer,
331        handler_ext: Option<CountdownTaskHandlerExtRef>,
332        region_id: RegionId,
333    ) -> Self {
334        let (tx, rx) = mpsc::channel(1024);
335
336        let mut countdown_task = CountdownTask {
337            region_server,
338            handler_ext,
339            region_id,
340            rx,
341        };
342        let handler = common_runtime::spawn_hb(async move {
343            countdown_task.run().await;
344        });
345
346        Self {
347            tx,
348            handler,
349            region_id,
350        }
351    }
352
353    /// Starts the [CountdownTask],
354    /// it will be ignored if the task started.
355    async fn start(&self, heartbeat_interval_millis: u64) {
356        if let Err(e) = self
357            .tx
358            .send(CountdownCommand::Start(heartbeat_interval_millis))
359            .await
360        {
361            warn!(
362                "Failed to start region alive keeper countdown: {e}. \
363                Maybe the task is stopped due to region been closed."
364            );
365        }
366    }
367
368    #[cfg(test)]
369    async fn deadline(&self) -> Option<Instant> {
370        let (tx, rx) = oneshot::channel();
371        if self.tx.send(CountdownCommand::Deadline(tx)).await.is_ok() {
372            return rx.await.ok();
373        }
374        None
375    }
376
377    async fn reset_deadline(
378        &self,
379        role: RegionRole,
380        deadline: Instant,
381        extension_info: HashMap<String, Vec<u8>>,
382    ) {
383        if let Err(e) = self
384            .tx
385            .send(CountdownCommand::Reset((role, deadline, extension_info)))
386            .await
387        {
388            warn!(
389                "Failed to reset region alive keeper deadline: {e}. \
390                Maybe the task is stopped due to region been closed."
391            );
392        }
393    }
394}
395
396impl Drop for CountdownTaskHandle {
397    fn drop(&mut self) {
398        debug!(
399            "Aborting region alive countdown task for region {}",
400            self.region_id
401        );
402        self.handler.abort();
403    }
404}
405
406struct CountdownTask {
407    region_server: RegionServer,
408    region_id: RegionId,
409    handler_ext: Option<CountdownTaskHandlerExtRef>,
410    rx: mpsc::Receiver<CountdownCommand>,
411}
412
413impl CountdownTask {
414    async fn run(&mut self) {
415        // 30 years. See `Instant::far_future`.
416        let far_future = Instant::now() + Duration::from_secs(86400 * 365 * 30);
417
418        // Make sure the alive countdown is not gonna happen before heartbeat task is started (the
419        // "start countdown" command will be sent from heartbeat task).
420        let countdown = tokio::time::sleep_until(far_future);
421        tokio::pin!(countdown);
422        let region_id = self.region_id;
423
424        let mut started = false;
425        loop {
426            tokio::select! {
427                command = self.rx.recv() => {
428                    match command {
429                        Some(CountdownCommand::Start(heartbeat_interval_millis)) => {
430                            if !started {
431                                // Set first deadline in 4 heartbeats (roughly after 12 seconds from now if heartbeat
432                                // interval is set to default 3 seconds), to make Datanode and Metasrv more tolerable to
433                                // network or other jitters during startup.
434                                let first_deadline = Instant::now() + Duration::from_millis(heartbeat_interval_millis) * 4;
435                                countdown.set(tokio::time::sleep_until(first_deadline));
436                                started = true;
437                            }
438                        },
439                        Some(CountdownCommand::Reset((role, deadline, extension_info))) => {
440                            let prev_role_leader = self.region_server.is_region_leader(self.region_id).unwrap_or(false);
441                            if let Err(err) = self.region_server.set_region_role_with_lease(self.region_id, role, deadline.into_std()) {
442                                if err.status_code() == StatusCode::RegionNotFound {
443                                    // Table metadata in metasrv is deleted after its regions are dropped.
444                                    // The datanode may still receive lease renewal responses that depend on the metadata
445                                    // during the short period before it is removed.
446                                    warn!(err; "Failed to set region role to {role} for region {region_id}");
447                                } else {
448                                    error!(err; "Failed to set region role to {role} for region {region_id}");
449                                }
450
451                            // Finalize leadership: persist backfilled metadata.
452                            } else if !prev_role_leader && role == RegionRole::Leader
453                                && let Err(err) = self
454                                    .region_server
455                                    .set_region_role_state_gracefully(self.region_id, SettableRegionRoleState::Leader)
456                                    .await
457                            {
458                                error!(err; "Failed to set region role state gracefully to {role} for region {region_id}");
459                            }
460
461                            if let Some(ext_handler) = self.handler_ext.as_ref() {
462                                ext_handler.reset_deadline(
463                                    &self.region_server,
464                                    self.region_id,
465                                    role,
466                                    deadline,
467                                    extension_info,
468                                ).await;
469                            }
470                            trace!(
471                                "Reset deadline of region {region_id} to approximately {} seconds later.",
472                                (deadline - Instant::now()).as_secs_f32(),
473                            );
474                            countdown.set(tokio::time::sleep_until(deadline));
475                        },
476                        None => {
477                            info!(
478                                "The handle of countdown task for region {region_id}\
479                                is dropped, RegionAliveKeeper out."
480                            );
481                            break;
482                        },
483                        #[cfg(test)]
484                        Some(CountdownCommand::Deadline(tx)) => {
485                            let _ = tx.send(countdown.deadline());
486                        }
487                    }
488                }
489                () = &mut countdown => {
490                    warn!("The region {region_id} lease is expired, convert region to follower.");
491                    if let Err(err) = self.region_server.set_region_role(self.region_id, RegionRole::Follower) {
492                        error!(err; "Failed to set region role to follower for region {region_id}");
493                    }
494                    // resets the countdown.
495                    let far_future = Instant::now() + Duration::from_secs(86400 * 365 * 30);
496                    countdown.as_mut().reset(far_future);
497                }
498            }
499        }
500    }
501}
502
503#[cfg(test)]
504mod test {
505
506    use api::v1::Rows;
507    use api::v1::meta::{HeartbeatResponse, RegionLease};
508    use common_meta::heartbeat::mailbox::HeartbeatMailbox;
509    use mito2::config::MitoConfig;
510    use mito2::test_util::{CreateRequestBuilder, TestEnv, build_rows, rows_schema};
511    use store_api::region_engine::RegionEngine;
512    use store_api::region_request::{
513        EnterStagingRequest, RegionPutRequest, StagingPartitionDirective,
514    };
515
516    use super::*;
517    use crate::tests::mock_region_server;
518
519    /// Handles a heartbeat response that grants `regions` for a heartbeat sent now.
520    async fn handle_lease_response(alive_keeper: &RegionAliveKeeper, regions: Vec<GrantedRegion>) {
521        let (tx, _rx) = mpsc::channel(1);
522        let response = HeartbeatResponse {
523            region_lease: Some(RegionLease {
524                regions,
525                duration_since_epoch: alive_keeper.epoch().elapsed().as_millis() as u64,
526                // The countdown of a granted region does not expire during the test.
527                lease_seconds: 86400,
528                closeable_region_ids: vec![],
529            }),
530            ..Default::default()
531        };
532        let mut ctx =
533            HeartbeatResponseHandlerContext::new(Arc::new(HeartbeatMailbox::new(tx)), response);
534        alive_keeper.handle(&mut ctx).await.unwrap();
535    }
536
537    #[tokio::test(flavor = "multi_thread")]
538    async fn lease_responses_let_regions_write() {
539        common_telemetry::init_default_ut_logging();
540        let mut region_server = mock_region_server();
541        let mut engine_env = TestEnv::with_prefix("region-alive-keeper-write-lease").await;
542        let engine = engine_env.create_engine(MitoConfig::default()).await;
543        engine.require_write_lease();
544        // The clock of the lease checks stands still, so only the lease propagation matters.
545        engine.freeze_lease_clock(std::time::Instant::now());
546        region_server.register_engine(Arc::new(engine));
547        let alive_keeper =
548            RegionAliveKeeper::new(region_server.clone(), None, Duration::from_millis(100));
549        let create = |region_id| {
550            let request = RegionRequest::Create(CreateRequestBuilder::new().build());
551            region_server.handle_request(region_id, request)
552        };
553        let schema = rows_schema(&CreateRequestBuilder::new().build());
554        let put = |region_id| {
555            let rows = Rows {
556                schema: schema.clone(),
557                rows: build_rows(0, 1),
558            };
559            let request = RegionRequest::Put(RegionPutRequest {
560                rows,
561                hint: None,
562                partition_expr_version: None,
563                skip_wal: false,
564            });
565            region_server.handle_request(region_id, request)
566        };
567
568        // Created before any heartbeat response, the region has no lease.
569        let region_id = RegionId::new(1024, 1);
570        create(region_id).await.unwrap();
571        assert!(put(region_id).await.unwrap_err().is_retryable());
572
573        // A response renews the lease of the node, under which regions created later write.
574        handle_lease_response(&alive_keeper, vec![]).await;
575        let created_region_id = RegionId::new(1024, 2);
576        create(created_region_id).await.unwrap();
577        put(created_region_id).await.unwrap();
578
579        // A Leader grant renews the lease of the region.
580        alive_keeper.register_region(region_id).await;
581        let grant = GrantedRegion {
582            region_id: region_id.as_u64(),
583            role: api::v1::meta::RegionRole::Leader.into(),
584            extensions: HashMap::new(),
585        };
586        handle_lease_response(&alive_keeper, vec![grant]).await;
587        // The countdown task has applied the grant once it answers.
588        alive_keeper.deadline(region_id).await.unwrap();
589        put(region_id).await.unwrap();
590    }
591
592    #[tokio::test(flavor = "multi_thread")]
593    async fn region_alive_keeper() {
594        common_telemetry::init_default_ut_logging();
595        let mut region_server = mock_region_server();
596        let mut engine_env = TestEnv::with_prefix("region-alive-keeper").await;
597        let engine = engine_env.create_engine(MitoConfig::default()).await;
598        let engine = Arc::new(engine);
599        region_server.register_engine(engine.clone());
600
601        let alive_keeper = Arc::new(RegionAliveKeeper::new(
602            region_server.clone(),
603            None,
604            Duration::from_millis(100),
605        ));
606
607        let region_id = RegionId::new(1024, 1);
608        let builder = CreateRequestBuilder::new();
609        region_server
610            .handle_request(region_id, RegionRequest::Create(builder.build()))
611            .await
612            .unwrap();
613        region_server
614            .set_region_role(region_id, RegionRole::Leader)
615            .unwrap();
616
617        // Register a region before starting.
618        alive_keeper.register_region(region_id).await;
619        assert!(alive_keeper.find_handle(region_id).await.is_some());
620
621        info!("Start the keeper");
622        alive_keeper.start(None).await.unwrap();
623
624        // The started alive keeper should assign deadline to this region.
625        let deadline = alive_keeper.deadline(region_id).await.unwrap();
626        assert!(deadline >= Instant::now());
627        assert_eq!(engine.role(region_id).unwrap(), RegionRole::Leader);
628
629        info!("Wait for lease expired");
630        // Sleep to wait lease expired.
631        tokio::time::sleep(Duration::from_millis(500)).await;
632        assert!(alive_keeper.find_handle(region_id).await.is_some());
633        assert_eq!(engine.role(region_id).unwrap(), RegionRole::Follower);
634
635        info!("Renew the region lease");
636        // Renew lease then sleep.
637        alive_keeper
638            .renew_region_leases(
639                &[GrantedRegion {
640                    region_id: region_id.as_u64(),
641                    role: api::v1::meta::RegionRole::Leader.into(),
642                    extensions: HashMap::new(),
643                }],
644                Instant::now() + Duration::from_millis(200),
645            )
646            .await;
647        tokio::time::sleep(Duration::from_millis(100)).await;
648        assert!(alive_keeper.find_handle(region_id).await.is_some());
649        let deadline = alive_keeper.deadline(region_id).await.unwrap();
650        assert!(deadline >= Instant::now());
651        assert_eq!(engine.role(region_id).unwrap(), RegionRole::Leader);
652
653        info!("Wait for lease expired");
654        // Sleep to wait lease expired.
655        tokio::time::sleep(Duration::from_millis(200)).await;
656        assert!(alive_keeper.find_handle(region_id).await.is_some());
657        assert_eq!(engine.role(region_id).unwrap(), RegionRole::Follower);
658
659        let deadline = alive_keeper.deadline(region_id).await.unwrap();
660        assert!(deadline > Instant::now() + Duration::from_secs(86400 * 365 * 29));
661    }
662
663    #[tokio::test(flavor = "multi_thread")]
664    async fn countdown_task() {
665        let region_server = mock_region_server();
666
667        let countdown_handle =
668            CountdownTaskHandle::new(region_server, None, RegionId::new(9999, 2));
669
670        // If countdown task is not started, its deadline is set to far future.
671        assert!(
672            countdown_handle.deadline().await.unwrap()
673                > Instant::now() + Duration::from_secs(86400 * 365 * 29)
674        );
675
676        // The first deadline should be set to 4 * heartbeat_interval_millis.
677        // We assert it to be greater than 3 * heartbeat_interval_millis to avoid flaky test.
678        let heartbeat_interval_millis = 100;
679        countdown_handle.start(heartbeat_interval_millis).await;
680        assert!(
681            countdown_handle.deadline().await.unwrap()
682                > Instant::now() + Duration::from_millis(heartbeat_interval_millis * 3)
683        );
684        tokio::time::sleep(Duration::from_millis(heartbeat_interval_millis * 5)).await;
685
686        // No effect.
687        countdown_handle.start(heartbeat_interval_millis).await;
688        assert!(
689            countdown_handle.deadline().await.unwrap()
690                > Instant::now() + Duration::from_secs(86400 * 365 * 29)
691        );
692
693        // Reset deadline.
694        countdown_handle
695            .reset_deadline(
696                RegionRole::Leader,
697                Instant::now() + Duration::from_millis(heartbeat_interval_millis * 5),
698                HashMap::new(),
699            )
700            .await;
701        assert!(
702            countdown_handle.deadline().await.unwrap()
703                > Instant::now() + Duration::from_millis(heartbeat_interval_millis * 4)
704        );
705    }
706
707    #[tokio::test(flavor = "multi_thread")]
708    async fn renew_staging_leader_keeps_region_in_staging() {
709        let mut region_server = mock_region_server();
710        let mut engine_env = TestEnv::with_prefix("region-alive-keeper-staging").await;
711        let engine = engine_env.create_engine(MitoConfig::default()).await;
712        let engine = Arc::new(engine);
713        region_server.register_engine(engine.clone());
714
715        let alive_keeper = Arc::new(RegionAliveKeeper::new(
716            region_server.clone(),
717            None,
718            Duration::from_millis(100),
719        ));
720
721        let region_id = RegionId::new(1024, 2);
722        region_server
723            .handle_request(
724                region_id,
725                RegionRequest::Create(CreateRequestBuilder::new().build()),
726            )
727            .await
728            .unwrap();
729        region_server
730            .handle_request(
731                region_id,
732                RegionRequest::EnterStaging(EnterStagingRequest {
733                    partition_directive: StagingPartitionDirective::RejectAllWrites,
734                }),
735            )
736            .await
737            .unwrap();
738
739        alive_keeper.register_region(region_id).await;
740        alive_keeper
741            .renew_region_leases(
742                &[GrantedRegion {
743                    region_id: region_id.as_u64(),
744                    role: api::v1::meta::RegionRole::StagingLeader.into(),
745                    extensions: HashMap::new(),
746                }],
747                Instant::now() + Duration::from_millis(3000),
748            )
749            .await;
750
751        assert_eq!(engine.role(region_id).unwrap(), RegionRole::StagingLeader);
752    }
753
754    #[tokio::test(flavor = "multi_thread")]
755    async fn renew_staging_leader_exit_into_leader() {
756        common_telemetry::init_default_ut_logging();
757        let mut region_server = mock_region_server();
758        let mut engine_env = TestEnv::with_prefix("region-alive-keeper-staging-exit").await;
759        let engine = engine_env.create_engine(MitoConfig::default()).await;
760        let engine = Arc::new(engine);
761        region_server.register_engine(engine.clone());
762
763        let alive_keeper = Arc::new(RegionAliveKeeper::new(
764            region_server.clone(),
765            None,
766            Duration::from_millis(100),
767        ));
768
769        let region_id = RegionId::new(1024, 2);
770        region_server
771            .handle_request(
772                region_id,
773                RegionRequest::Create(CreateRequestBuilder::new().build()),
774            )
775            .await
776            .unwrap();
777        region_server
778            .handle_request(
779                region_id,
780                RegionRequest::EnterStaging(EnterStagingRequest {
781                    partition_directive: StagingPartitionDirective::RejectAllWrites,
782                }),
783            )
784            .await
785            .unwrap();
786
787        alive_keeper.register_region(region_id).await;
788        alive_keeper
789            .renew_region_leases(
790                &[GrantedRegion {
791                    region_id: region_id.as_u64(),
792                    role: api::v1::meta::RegionRole::Leader.into(),
793                    extensions: HashMap::new(),
794                }],
795                Instant::now() + Duration::from_millis(3000),
796            )
797            .await;
798
799        tokio::time::sleep(Duration::from_millis(100)).await;
800        assert_eq!(engine.role(region_id).unwrap(), RegionRole::Leader);
801    }
802
803    #[tokio::test(flavor = "multi_thread")]
804    async fn renew_staging_leader_does_not_promote_normal_leader_into_staging() {
805        let mut region_server = mock_region_server();
806        let mut engine_env = TestEnv::with_prefix("region-alive-keeper-non-staging").await;
807        let engine = engine_env.create_engine(MitoConfig::default()).await;
808        let engine = Arc::new(engine);
809        region_server.register_engine(engine.clone());
810
811        let alive_keeper = Arc::new(RegionAliveKeeper::new(
812            region_server.clone(),
813            None,
814            Duration::from_millis(100),
815        ));
816
817        let region_id = RegionId::new(1024, 4);
818        region_server
819            .handle_request(
820                region_id,
821                RegionRequest::Create(CreateRequestBuilder::new().build()),
822            )
823            .await
824            .unwrap();
825        region_server
826            .set_region_role(region_id, RegionRole::Leader)
827            .unwrap();
828
829        alive_keeper.register_region(region_id).await;
830        alive_keeper
831            .renew_region_leases(
832                &[GrantedRegion {
833                    region_id: region_id.as_u64(),
834                    role: api::v1::meta::RegionRole::StagingLeader.into(),
835                    extensions: HashMap::new(),
836                }],
837                Instant::now() + Duration::from_millis(3000),
838            )
839            .await;
840        tokio::time::sleep(Duration::from_millis(100)).await;
841        assert_eq!(engine.role(region_id).unwrap(), RegionRole::Leader);
842    }
843}