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 }