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  }