// Copyright 2020 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package internal import ( "context" "os" "os/exec" "time" ) // PeriodicallyDo calls f every period until the provided context is cancelled. func PeriodicallyDo(ctx context.Context, period time.Duration, f func(context.Context, time.Time)) { ticker := time.NewTicker(period) defer ticker.Stop() for { select { case <-ctx.Done(): return case now := <-ticker.C: f(ctx, now) } } } // WaitOrStop waits for the already-started command cmd by calling its // Wait method. // // If cmd does not return before ctx is done, WaitOrStop sends it the // given interrupt signal. If killDelay is positive, WaitOrStop waits // that additional period for Wait to return before sending os.Kill. func WaitOrStop(ctx context.Context, cmd *exec.Cmd, interrupt os.Signal, killDelay time.Duration) error { if cmd.Process == nil { panic("WaitOrStop called with a nil cmd.Process — missing Start call?") } if interrupt == nil { panic("WaitOrStop requires a non-nil interrupt signal") } errc := make(chan error) go func() { select { case errc <- nil: return case <-ctx.Done(): } err := cmd.Process.Signal(interrupt) if err == nil { err = ctx.Err() // Report ctx.Err() as the reason we interrupted. } else if err.Error() == "os: process already finished" { errc <- nil return } if killDelay > 0 { timer := time.NewTimer(killDelay) select { // Report ctx.Err() as the reason we interrupted the process... case errc <- ctx.Err(): timer.Stop() return // ...but after killDelay has elapsed, fall back to a stronger signal. case <-timer.C: } // Wait still hasn't returned. // Kill the process harder to make sure that it exits. // // Ignore any error: if cmd.Process has already terminated, we still // want to send ctx.Err() (or the error from the Interrupt call) // to properly attribute the signal that may have terminated it. _ = cmd.Process.Kill() } errc <- err }() waitErr := cmd.Wait() if interruptErr := <-errc; interruptErr != nil { return interruptErr } return waitErr }