golang.org/x/build@v0.0.0-20240506185731-218518f32b70/internal/internal.go (about)

     1  // Copyright 2020 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package internal
     6  
     7  import (
     8  	"context"
     9  	"os"
    10  	"os/exec"
    11  	"time"
    12  )
    13  
    14  // PeriodicallyDo calls f every period until the provided context is cancelled.
    15  func PeriodicallyDo(ctx context.Context, period time.Duration, f func(context.Context, time.Time)) {
    16  	ticker := time.NewTicker(period)
    17  	defer ticker.Stop()
    18  	for {
    19  		select {
    20  		case <-ctx.Done():
    21  			return
    22  		case now := <-ticker.C:
    23  			f(ctx, now)
    24  		}
    25  	}
    26  }
    27  
    28  // WaitOrStop waits for the already-started command cmd by calling its
    29  // Wait method.
    30  //
    31  // If cmd does not return before ctx is done, WaitOrStop sends it the
    32  // given interrupt signal. If killDelay is positive, WaitOrStop waits
    33  // that additional period for Wait to return before sending os.Kill.
    34  func WaitOrStop(ctx context.Context, cmd *exec.Cmd, interrupt os.Signal, killDelay time.Duration) error {
    35  	if cmd.Process == nil {
    36  		panic("WaitOrStop called with a nil cmd.Process — missing Start call?")
    37  	}
    38  	if interrupt == nil {
    39  		panic("WaitOrStop requires a non-nil interrupt signal")
    40  	}
    41  
    42  	errc := make(chan error)
    43  	go func() {
    44  		select {
    45  		case errc <- nil:
    46  			return
    47  		case <-ctx.Done():
    48  		}
    49  
    50  		err := cmd.Process.Signal(interrupt)
    51  		if err == nil {
    52  			err = ctx.Err() // Report ctx.Err() as the reason we interrupted.
    53  		} else if err.Error() == "os: process already finished" {
    54  			errc <- nil
    55  			return
    56  		}
    57  
    58  		if killDelay > 0 {
    59  			timer := time.NewTimer(killDelay)
    60  			select {
    61  			// Report ctx.Err() as the reason we interrupted the process...
    62  			case errc <- ctx.Err():
    63  				timer.Stop()
    64  				return
    65  			// ...but after killDelay has elapsed, fall back to a stronger signal.
    66  			case <-timer.C:
    67  			}
    68  
    69  			// Wait still hasn't returned.
    70  			// Kill the process harder to make sure that it exits.
    71  			//
    72  			// Ignore any error: if cmd.Process has already terminated, we still
    73  			// want to send ctx.Err() (or the error from the Interrupt call)
    74  			// to properly attribute the signal that may have terminated it.
    75  			_ = cmd.Process.Kill()
    76  		}
    77  
    78  		errc <- err
    79  	}()
    80  
    81  	waitErr := cmd.Wait()
    82  	if interruptErr := <-errc; interruptErr != nil {
    83  		return interruptErr
    84  	}
    85  	return waitErr
    86  }