@@ -66,6 +66,7 @@ impl NodeConfig {
6666 let service = AtomaServiceConfig :: from_file_path ( path) ;
6767 let state = AtomaStateManagerConfig :: from_file_path ( path) ;
6868 let daemon = AtomaDaemonConfig :: from_file_path ( path) ;
69+
6970 Self {
7071 sui,
7172 p2p,
@@ -165,6 +166,13 @@ async fn main() -> Result<()> {
165166 let ( event_subscriber_sender, event_subscriber_receiver) = flume:: unbounded ( ) ;
166167 let ( state_manager_sender, state_manager_receiver) = flume:: unbounded ( ) ;
167168 let ( p2p_event_sender, p2p_event_receiver) = flume:: unbounded ( ) ;
169+
170+ // Start the heartbeat service
171+ start_heartbeat_service (
172+ shutdown_receiver. clone ( ) ,
173+ config. service . heartbeat_url . clone ( ) ,
174+ ) ;
175+
168176 info ! (
169177 target = "atoma-node-service" ,
170178 event = "keystore_path" ,
@@ -491,3 +499,72 @@ fn handle_tasks_results(
491499 ) ?;
492500 Ok ( ( ) )
493501}
502+
503+ /// Starts a heartbeat service that pings a health check endpoint every minute.
504+ ///
505+ /// This function spawns a background task that sends a GET request to a health check
506+ /// service at regular intervals to indicate the daemon is still running.
507+ ///
508+ /// # Arguments
509+ /// * `shutdown_receiver` - A receiver that signals when the service should shut down
510+ /// * `heartbeat_url` - The URL of the heartbeat service
511+ #[ allow( clippy:: redundant_pub_crate) ]
512+ fn start_heartbeat_service ( mut shutdown_receiver : watch:: Receiver < bool > , heartbeat_url : String ) {
513+ tokio:: spawn ( async move {
514+ let client = reqwest:: Client :: new ( ) ;
515+ let interval = std:: time:: Duration :: from_secs ( 60 ) ;
516+
517+ tracing:: info!(
518+ target = "atoma_daemon" ,
519+ event = "heartbeat-service-start" ,
520+ url = %heartbeat_url. clone( ) ,
521+ interval_secs = %interval. as_secs( ) ,
522+ "Starting heartbeat service"
523+ ) ;
524+
525+ loop {
526+ tokio:: select! {
527+ ( ) = tokio:: time:: sleep( interval) => {
528+ // Send heartbeat ping
529+ match client. get( heartbeat_url. clone( ) ) . send( ) . await {
530+ Ok ( response) => {
531+ if response. status( ) . is_success( ) {
532+ tracing:: debug!(
533+ target = "atoma_daemon" ,
534+ event = "heartbeat-ping" ,
535+ status = %response. status( ) ,
536+ "Sent heartbeat ping successfully"
537+ ) ;
538+ } else {
539+ tracing:: warn!(
540+ target = "atoma_daemon" ,
541+ event = "heartbeat-ping-failed" ,
542+ status = %response. status( ) ,
543+ "Heartbeat ping returned non-success status"
544+ ) ;
545+ }
546+ } ,
547+ Err ( e) => {
548+ tracing:: error!(
549+ target = "atoma_daemon" ,
550+ event = "heartbeat-ping-error" ,
551+ error = %e,
552+ "Failed to send heartbeat ping"
553+ ) ;
554+ }
555+ }
556+ }
557+ result = shutdown_receiver. changed( ) => {
558+ if result. is_err( ) || * shutdown_receiver. borrow( ) {
559+ tracing:: info!(
560+ target = "atoma_daemon" ,
561+ event = "heartbeat-service-shutdown" ,
562+ "Heartbeat service shutting down"
563+ ) ;
564+ break ;
565+ }
566+ }
567+ }
568+ }
569+ } ) ;
570+ }
0 commit comments