@@ -21,7 +21,7 @@ use axum::{
2121use catalog_metastore:: InMemoryMetastore ;
2222use clap:: Parser ;
2323use dotenv:: dotenv;
24- use executor:: service:: CoreExecutionService ;
24+ use executor:: service:: { CoreExecutionService , ExecutionService , TIMEOUT_SIGNAL_INTERVAL_SECONDS } ;
2525use executor:: utils:: Config as ExecutionConfig ;
2626use opentelemetry:: trace:: TracerProvider ;
2727use opentelemetry_sdk:: Resource ;
@@ -218,8 +218,9 @@ async fn async_main(
218218 . expect ( "Failed to bind to address" ) ;
219219 let addr = listener. local_addr ( ) . expect ( "Failed to get local address" ) ;
220220 tracing:: info!( %addr, "Listening on http" ) ;
221+ let timeout = opts. timeout . unwrap ( ) ;
221222 axum:: serve ( listener, router)
222- . with_graceful_shutdown ( shutdown_signal ( ) )
223+ . with_graceful_shutdown ( shutdown_signal ( execution_svc . clone ( ) , timeout ) )
223224 . await
224225 . expect ( "Failed to start server" ) ;
225226
@@ -337,7 +338,7 @@ fn setup_tracing(opts: &cli::CliOpts) -> SdkTracerProvider {
337338 clippy:: redundant_pub_crate,
338339 clippy:: cognitive_complexity
339340) ]
340- async fn shutdown_signal ( ) {
341+ async fn shutdown_signal ( execution_svc : Arc < dyn ExecutionService > , timeout : u64 ) {
341342 let ctrl_c = async {
342343 signal:: ctrl_c ( )
343344 . await
@@ -355,13 +356,21 @@ async fn shutdown_signal() {
355356 #[ cfg( not( unix) ) ]
356357 let terminate = std:: future:: pending :: < ( ) > ( ) ;
357358
359+ let timeout = execution_svc. timeout_signal (
360+ tokio:: time:: Duration :: from_secs ( TIMEOUT_SIGNAL_INTERVAL_SECONDS ) ,
361+ tokio:: time:: Duration :: from_secs ( timeout) ,
362+ ) ;
363+
358364 tokio:: select! {
359365 ( ) = ctrl_c => {
360366 tracing:: warn!( "Ctrl+C received, starting graceful shutdown" ) ;
361367 } ,
362368 ( ) = terminate => {
363369 tracing:: warn!( "SIGTERM received, starting graceful shutdown" ) ;
364370 } ,
371+ ( ) = timeout => {
372+ tracing:: warn!( "No sessions in use & no running queries - timeout, starting graceful shutdown" ) ;
373+ }
365374 }
366375
367376 tracing:: warn!( "signal received, starting graceful shutdown" ) ;
@@ -371,3 +380,54 @@ async fn shutdown_signal() {
371380#[ derive( OpenApi ) ]
372381#[ openapi( ) ]
373382pub struct ApiDoc ;
383+
384+ #[ cfg( test) ]
385+ mod tests {
386+ use api_snowflake_rest_sessions:: session:: SessionStore ;
387+ use executor:: models:: QueryContext ;
388+ use executor:: service:: ExecutionService ;
389+ use executor:: service:: make_test_execution_svc;
390+ use executor:: session:: to_unix;
391+ use std:: sync:: atomic:: Ordering ;
392+ use std:: time:: Duration ;
393+ use time:: OffsetDateTime ;
394+
395+ #[ tokio:: test]
396+ #[ allow( clippy:: expect_used, clippy:: too_many_lines) ]
397+ async fn test_timeout_signal ( ) {
398+ let execution_svc = make_test_execution_svc ( ) . await ;
399+
400+ let df_session_id = "fasfsafsfasafsass" . to_string ( ) ;
401+ let user_session = execution_svc
402+ . create_session ( & df_session_id)
403+ . await
404+ . expect ( "Failed to create a session" ) ;
405+
406+ execution_svc
407+ . query ( & df_session_id, "SELECT SLEEP(5)" , QueryContext :: default ( ) )
408+ . await
409+ . expect ( "Failed to execute query (session deleted)" ) ;
410+
411+ user_session
412+ . expiry
413+ . store ( to_unix ( OffsetDateTime :: now_utc ( ) ) , Ordering :: Relaxed ) ;
414+
415+ let session_store = SessionStore :: new ( execution_svc. clone ( ) ) ;
416+
417+ tokio:: task:: spawn ( {
418+ let session_store = session_store. clone ( ) ;
419+ async move {
420+ session_store
421+ . continuously_delete_expired ( Duration :: from_secs ( 1 ) )
422+ . await ;
423+ }
424+ } ) ;
425+
426+ let timeout = execution_svc. timeout_signal ( Duration :: from_secs ( 1 ) , Duration :: from_secs ( 3 ) ) ;
427+ tokio:: select! {
428+ ( ) = timeout => {
429+ tracing:: warn!( "No sessions in use & no running queries - timeout, starting graceful shutdown" ) ;
430+ }
431+ }
432+ }
433+ }
0 commit comments