@@ -15,6 +15,10 @@ use crate::agent_task_scheduler::{
1515use homeboy_core:: api_jobs:: { Job , JobEvent , JobEventKind , JobStore , RemoteRunnerJobRequest } ;
1616use homeboy_core:: test_support:: with_isolated_home;
1717use sha2:: { Digest , Sha256 } ;
18+ #[ cfg( unix) ]
19+ use std:: io:: { BufRead , BufReader } ;
20+ #[ cfg( unix) ]
21+ use std:: process:: Stdio ;
1822use std:: sync:: { Arc , Mutex } ;
1923
2024#[ test]
@@ -1941,25 +1945,56 @@ fn mark_running_reclaims_stale_running_record() {
19411945 } ) ;
19421946}
19431947
1948+ #[ cfg( unix) ]
19441949#[ test]
19451950fn cancel_run_signals_live_running_record ( ) {
19461951 with_isolated_home ( |_| {
19471952 let plan = test_plan ( ) ;
19481953 submit_plan ( & plan, Some ( "run-cancel-live" ) ) . expect ( "submitted" ) ;
19491954 mark_running ( "run-cancel-live" ) . expect ( "marked running" ) ;
19501955
1956+ // The test binary cannot be a cancellation target: process cleanup
1957+ // correctly excludes the current PID. Use a separate owner with a
1958+ // descendant that ignores SIGTERM, proving the SIGKILL path reaps both.
1959+ let mut child = std:: process:: Command :: new ( "sh" )
1960+ . args ( [
1961+ "-c" ,
1962+ "trap '' TERM; (trap '' TERM; exec sleep 30) & echo $!; wait" ,
1963+ ] )
1964+ . stdout ( Stdio :: piped ( ) )
1965+ . spawn ( )
1966+ . expect ( "spawn live owned process tree" ) ;
1967+ let stdout = child. stdout . take ( ) . expect ( "child stdout" ) ;
1968+ let mut stdout = BufReader :: new ( stdout) ;
1969+ let mut descendant_pid = String :: new ( ) ;
1970+ stdout
1971+ . read_line ( & mut descendant_pid)
1972+ . expect ( "read descendant pid" ) ;
1973+ let descendant_pid: u32 = descendant_pid. trim ( ) . parse ( ) . expect ( "descendant pid" ) ;
1974+ let owner_pid = child. id ( ) ;
1975+ let mut running = store:: read_record ( "run-cancel-live" ) . expect ( "running record" ) ;
1976+ running. metadata [ "runner_pid" ] = json ! ( owner_pid) ;
1977+ store:: write_record ( & running) . expect ( "persist owned process identity" ) ;
1978+
19511979 let cancelled = cancel_run ( "run-cancel-live" , None ) . expect ( "live run cancelled" ) ;
19521980
19531981 assert_eq ! ( cancelled. state, AgentTaskRunState :: Cancelled ) ;
19541982 assert_eq ! ( cancelled. tasks[ 0 ] . state, AgentTaskState :: Cancelled ) ;
19551983 assert_eq ! (
19561984 cancelled. metadata[ "live_cancellation" ] [ "owner_pid" ] ,
1957- json!( std :: process :: id ( ) )
1985+ json!( owner_pid )
19581986 ) ;
19591987 assert_eq ! (
19601988 cancelled. metadata[ "live_cancellation" ] [ "signal" ] ,
1961- json!( "SIGTERM " )
1989+ json!( "SIGKILL " )
19621990 ) ;
1991+ assert ! ( cancelled. metadata[ "live_cancellation" ] [ "killed_pids" ]
1992+ . as_array( )
1993+ . expect( "SIGKILL targets" )
1994+ . iter( )
1995+ . any( |pid| pid == & json!( descendant_pid) ) ) ;
1996+ assert ! ( !homeboy_core:: process:: pid_is_running( owner_pid) ) ;
1997+ assert ! ( !homeboy_core:: process:: pid_is_running( descendant_pid) ) ;
19631998 } ) ;
19641999}
19652000
0 commit comments