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 }