golang.org/x/playground@v0.0.0-20230418134305-14ebe15bcd59/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  // WaitOrStop waits for the already-started command cmd by calling its Wait method.
    15  //
    16  // If cmd does not return before ctx is done, WaitOrStop sends it the given interrupt signal.
    17  // If killDelay is positive, WaitOrStop waits that additional period for Wait to return before sending os.Kill.
    18  func WaitOrStop(ctx context.Context, cmd *exec.Cmd, interrupt os.Signal, killDelay time.Duration) error {
    19  	if cmd.Process == nil {
    20  		panic("WaitOrStop called with a nil cmd.Process — missing Start call?")
    21  	}
    22  	if interrupt == nil {
    23  		panic("WaitOrStop requires a non-nil interrupt signal")
    24  	}
    25  
    26  	errc := make(chan error)
    27  	go func() {
    28  		select {
    29  		case errc <- nil:
    30  			return
    31  		case <-ctx.Done():
    32  		}
    33  
    34  		err := cmd.Process.Signal(interrupt)
    35  		if err == nil {
    36  			err = ctx.Err() // Report ctx.Err() as the reason we interrupted.
    37  		} else if err.Error() == "os: process already finished" {
    38  			errc <- nil
    39  			return
    40  		}
    41  
    42  		if killDelay > 0 {
    43  			timer := time.NewTimer(killDelay)
    44  			select {
    45  			// Report ctx.Err() as the reason we interrupted the process...
    46  			case errc <- ctx.Err():
    47  				timer.Stop()
    48  				return
    49  			// ...but after killDelay has elapsed, fall back to a stronger signal.
    50  			case <-timer.C:
    51  			}
    52  
    53  			// Wait still hasn't returned.
    54  			// Kill the process harder to make sure that it exits.
    55  			//
    56  			// Ignore any error: if cmd.Process has already terminated, we still
    57  			// want to send ctx.Err() (or the error from the Interrupt call)
    58  			// to properly attribute the signal that may have terminated it.
    59  			_ = cmd.Process.Kill()
    60  		}
    61  
    62  		errc <- err
    63  	}()
    64  
    65  	waitErr := cmd.Wait()
    66  	if interruptErr := <-errc; interruptErr != nil {
    67  		return interruptErr
    68  	}
    69  	return waitErr
    70  }
    71  
    72  // PeriodicallyDo calls f every period until the provided context is cancelled.
    73  func PeriodicallyDo(ctx context.Context, period time.Duration, f func(context.Context, time.Time)) {
    74  	ticker := time.NewTicker(period)
    75  	defer ticker.Stop()
    76  	for {
    77  		select {
    78  		case <-ctx.Done():
    79  			return
    80  		case now := <-ticker.C:
    81  			f(ctx, now)
    82  		}
    83  	}
    84  }