gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/cmd/test_app/zombies.go (about) 1 // Copyright 2021 The gVisor Authors. 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 15 package main 16 17 import ( 18 "context" 19 "fmt" 20 "log" 21 "os" 22 "os/exec" 23 "strconv" 24 "strings" 25 sys "syscall" 26 "time" 27 28 "github.com/google/subcommands" 29 "golang.org/x/sys/unix" 30 "gvisor.dev/gvisor/runsc/flag" 31 ) 32 33 func fatalf(s string, args ...any) { 34 fmt.Fprintf(os.Stderr, s+"\n", args...) 35 os.Exit(1) 36 } 37 38 // zombieTest creates an orphaned process that will be reparented to PID 1 39 // (or the nearest subreaper) and expect that it is reaped. 40 // 41 // The setup involves three different processes: 42 // 43 // 1. The zombiemonitor process starts the zombieparent process and reads the 44 // zombiechild process pid from zombieparent's stdout. It waits on 45 // zombieparent, and after that dies, zombiechild will be reparented to PID 1 46 // (or nearest subreaper). The zombiemonitor kills zombiechild and expects 47 // that it will be reaped. 48 // 49 // 2. The zombieparent process starts the zombiechild process, writes the 50 // zombiechild process pid to stdout, and exits, causing zombiechild to be 51 // reparented to PID 1 (or nearest subreaper). 52 // 53 // 3. zombiechild just waits until it is killed. 54 type zombieTest struct{} 55 56 // Name implements subcommands.Command.Name. 57 func (*zombieTest) Name() string { 58 return "zombie_test" 59 } 60 61 // Synopsis implements subcommands.Command.Synopsys. 62 func (*zombieTest) Synopsis() string { 63 return "creates an orphaned grandchild and expects to be reparented and reaped." 64 } 65 66 // Usage implements subcommands.Command.Usage. 67 func (*zombieTest) Usage() string { 68 return "Usage: zombie_test [zombieparent|zombiechild]" 69 } 70 71 // SetFlags implements subcommands.Command.SetFlags. 72 func (*zombieTest) SetFlags(f *flag.FlagSet) {} 73 74 // Execute implements subcommands.Command.Execute. 75 func (zt *zombieTest) Execute(ctx context.Context, f *flag.FlagSet, args ...any) subcommands.ExitStatus { 76 n := f.NArg() 77 if n > 1 { 78 log.Fatal(zt.Usage()) 79 } 80 if n == 0 { 81 // Run the monitor, which is the main entrypoint of this program. 82 runOrphanMonitor() 83 return subcommands.ExitSuccess 84 } 85 // One argument passed 86 switch f.Arg(0) { 87 case "zombieparent": 88 runZombieParent() 89 case "zombiechild": 90 runZombieChild() 91 default: 92 log.Fatal(zt.Usage()) 93 } 94 95 return subcommands.ExitSuccess 96 } 97 98 func runOrphanMonitor() { 99 // Start the zombieparent and read its output. The call to 100 // CombinedOutput() will wait() on zombieparent, so when it returns we 101 // know that zombiechild has been orphaned and reparented to PID 1. 102 cmd := exec.Command("/proc/self/exe", "zombie_test", "zombieparent") 103 out, err := cmd.CombinedOutput() 104 if err != nil { 105 log.Fatalf("failed to exec zombieparent: %v\noutput: %s\n", err, string(out)) 106 } 107 108 // Parse zombiechild pid from zombieparent output. 109 zombieChildPid, err := strconv.Atoi(strings.TrimSpace(string(out))) 110 if err != nil { 111 log.Fatalf("failed to parse zombieparent output: %q", string(out)) 112 } 113 fmt.Printf("started zombiechild with pid %d\n", zombieChildPid) 114 115 // Kill the zombiechild. 116 fmt.Printf("killing zombiechild\n") 117 if err := unix.Kill(zombieChildPid, unix.SIGTERM); err != nil { 118 log.Fatalf("error killing for zombiechild: %v", err) 119 } 120 121 // Wait for zombiechild to be reaped by PID 1. 122 if err := waitForZombieReaped(zombieChildPid, 10*time.Second); err != nil { 123 log.Fatalf("error waiting for zombiechild to be reaped: %v", err) 124 } 125 fmt.Printf("zombiechild has been reaped\n") 126 127 // Success. 128 } 129 130 func runZombieParent() { 131 // Start the zombiechild, and write the pid to stdout. 132 cmd := exec.Command("/proc/self/exe", "zombie_test", "zombiechild") 133 if err := cmd.Start(); err != nil { 134 log.Fatalf("failed to exec zombiechild: %v", err) 135 } 136 fmt.Fprint(os.Stdout, strconv.Itoa(cmd.Process.Pid)) 137 138 // Die. This will cause zombiechild to be reparented. 139 } 140 141 func runZombieChild() { 142 // Sleep for a long time. We will be killed before this exits. 143 time.Sleep(1 * time.Minute) 144 } 145 146 // waitForZombieReaped sends a harmless signal to the given pid until it gets 147 // ESRCH, indicating that the process has been reaped, or until the timeout is 148 // reached. 149 func waitForZombieReaped(pid int, timeout time.Duration) error { 150 deadline := time.Now().Add(timeout) 151 for { 152 if time.Now().After(deadline) { 153 return fmt.Errorf("pid %d was not reaped after %v", pid, timeout) 154 } 155 156 err := unix.Kill(pid, 0) 157 if err == nil { 158 fmt.Printf("pid %d still exists\n", pid) 159 time.Sleep(1 * time.Second) 160 continue 161 } 162 if errno := err.(sys.Errno); errno != unix.ESRCH { 163 return fmt.Errorf("unexpected error signalling pid %d: %v", pid, err) 164 } 165 fmt.Printf("pid %d has been reaped\n", pid) 166 return nil 167 } 168 }