github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/worker/runtime/process_killer.go (about)

     1  package runtime
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"syscall"
     7  	"time"
     8  
     9  	"github.com/containerd/containerd"
    10  )
    11  
    12  //go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . ProcessKiller
    13  
    14  type ProcessKiller interface {
    15  
    16  	// Kill terminates a single process.
    17  	//
    18  	Kill(
    19  		ctx context.Context,
    20  		proc containerd.Process,
    21  		signal syscall.Signal,
    22  		waitPeriod time.Duration,
    23  	) error
    24  }
    25  
    26  type processKiller struct{}
    27  
    28  func NewProcessKiller() *processKiller {
    29  	return &processKiller{}
    30  }
    31  
    32  // Kill delivers a signal to a process, waiting for a maximum of `waitPeriod`
    33  // for a status to be reported back.
    34  //
    35  // In case no status is reported within the grace period time span,
    36  // ErrGracePeriodTimeout is returned.
    37  //
    38  // ps.: even in the case of a SIGKILL being used as the signal, `Kill` will
    39  //      wait for a `waitPeriod` for a status to be reported back. This way one
    40  //      can detect cases where not even a SIGKILL changes the status of a
    41  //      process (e.g., if the process is frozen due to a blocking syscall that
    42  //      never returns, or because it's suspended - see [1]).
    43  //
    44  // [1]: https://github.com/pf-qiu/concourse/v6/issues/4477
    45  //
    46  func (p processKiller) Kill(
    47  	ctx context.Context,
    48  	proc containerd.Process,
    49  	signal syscall.Signal,
    50  	waitPeriod time.Duration,
    51  ) error {
    52  	waitCtx, cancel := context.WithTimeout(ctx, waitPeriod)
    53  	defer cancel()
    54  
    55  	statusC, err := proc.Wait(waitCtx)
    56  	if err != nil {
    57  		return fmt.Errorf("proc wait: %w", err)
    58  	}
    59  
    60  	err = proc.Kill(ctx, signal)
    61  	if err != nil {
    62  		return fmt.Errorf("proc kill w/ signal %d: %w", signal, err)
    63  	}
    64  
    65  	select {
    66  	case <-waitCtx.Done():
    67  		err = waitCtx.Err()
    68  		if err == context.DeadlineExceeded {
    69  			return ErrGracePeriodTimeout
    70  		}
    71  
    72  		return fmt.Errorf("waitctx done: %w", err)
    73  	case status := <-statusC:
    74  		err = status.Error()
    75  		if err != nil {
    76  			return fmt.Errorf("waiting for exit status: %w", err)
    77  		}
    78  	}
    79  
    80  	return nil
    81  }