@@ -1023,6 +1023,93 @@ func TestKillPid(t *testing.T) {
10231023 }
10241024}
10251025
1026+ // TestSignalProcessGroup verifies that SignalProcessGroup kills all
1027+ // processes in the targeted process group while leaving other groups
1028+ // running.
1029+ func TestSignalProcessGroup (t * testing.T ) {
1030+ for name , conf := range configs (t , false /* noOverlay */ ) {
1031+ t .Run (name , func (t * testing.T ) {
1032+ app , err := testutil .FindFile ("test/cmd/test_app/test_app" )
1033+ if err != nil {
1034+ t .Fatal ("error finding test_app:" , err )
1035+ }
1036+
1037+ spec := testutil .NewSpecWithArgs (app , "task-tree-pgid" )
1038+ _ , bundleDir , cleanup , err := testutil .SetupContainer (spec , conf )
1039+ if err != nil {
1040+ t .Fatalf ("error setting up container: %v" , err )
1041+ }
1042+ defer cleanup ()
1043+
1044+ args := Args {
1045+ ID : testutil .RandomContainerID (),
1046+ Spec : spec ,
1047+ BundleDir : bundleDir ,
1048+ }
1049+ cont , err := New (conf , args )
1050+ if err != nil {
1051+ t .Fatalf ("error creating container: %v" , err )
1052+ }
1053+ defer cont .Destroy ()
1054+ if err := cont .Start (conf ); err != nil {
1055+ t .Fatalf ("error starting container: %v" , err )
1056+ }
1057+
1058+ // Wait for all 3 processes: init, child, grandchild.
1059+ if err := waitForProcessCount (cont , 3 ); err != nil {
1060+ t .Fatalf ("timed out waiting for processes: %v" , err )
1061+ }
1062+
1063+ // Collect PGIDs.
1064+ procs , err := cont .Processes ()
1065+ if err != nil {
1066+ t .Fatalf ("failed to get process list: %v" , err )
1067+ }
1068+ t .Logf ("before signal: %s" , procListToString (procs ))
1069+
1070+ // Init (PID 1) is in PGID 1.
1071+ // Child + grandchild should share a PGID != 1.
1072+ pgidA := int32 (1 )
1073+ pgidBCount := make (map [int32 ]int )
1074+ for _ , p := range procs {
1075+ if int32 (p .PGID ) != pgidA {
1076+ pgidBCount [int32 (p .PGID )]++
1077+ }
1078+ }
1079+
1080+ // Find the PGID shared by child+grandchild.
1081+ var pgidB int32
1082+ for pgid , n := range pgidBCount {
1083+ if n == 2 {
1084+ pgidB = pgid
1085+ }
1086+ }
1087+ if pgidB == 0 {
1088+ t .Fatalf ("expected child and grandchild to share a PGID distinct from init (%d); got: %v" , pgidA , pgidBCount )
1089+ }
1090+ t .Logf ("PGID_init=%d, PGID_target=%d (%d processes)" , pgidA , pgidB , pgidBCount [pgidB ])
1091+
1092+ // Signal the target PGID (both child and grandchild should die, init survives).
1093+ if err := cont .SignalProcessGroup (unix .SIGKILL , pgidB ); err != nil {
1094+ t .Fatalf ("SignalProcessGroup(%d): %v" , pgidB , err )
1095+ }
1096+
1097+ if err := waitForProcessCount (cont , 1 ); err != nil {
1098+ procs , procsErr := cont .Processes ()
1099+ t .Fatalf ("expected only init to survive: %v; processes: %s / %v" , err , procListToString (procs ), procsErr )
1100+ }
1101+
1102+ procs , err = cont .Processes ()
1103+ if err != nil {
1104+ t .Fatalf ("failed to get process list: %v" , err )
1105+ }
1106+ if len (procs ) != 1 || procs [0 ].PID != 1 {
1107+ t .Fatalf ("expected only PID 1 to survive, got: %s" , procListToString (procs ))
1108+ }
1109+ })
1110+ }
1111+ }
1112+
10261113// testCheckpointRestore creates a container that continuously writes successive
10271114// integers to a file. To test checkpoint and restore functionality, the
10281115// container is checkpointed and the last number printed to the file is
0 commit comments