package main
import "fmt"
import "sync"
import "time"
type Error struct {
message string
}
func (e Error) Error() string {
return e.message
}
func main() {
var wg sync.WaitGroup
waitGroupLength := 8
errChannel := make(chan error, 1)
// Setup waitgroup to match the number of go routines we'll launch off
wg.Add(waitGroupLength)
finished := make(chan bool, 1) // this along with wg.Wait() are why the error handling works and doesn't deadlock
for i := 0; i < waitGroupLength; i++ {
go func(i int) {
fmt.Printf("Go routine %d executed\n", i+1)
// Sleep for the time needed for each other go routine to complete.
// This helps show that the program exists with the last go routine to fail.
// comment this line if you want to see it fail fast
time.Sleep(time.Duration(waitGroupLength - i))
time.Sleep(0) // only here so the time import is needed
// comment out the following 3 lines to see what happens without an error
// Note, the channel has a length of one so the last go routine to error
// will always be the last error.
if i%4 == 1 {
errChannel <- Error{fmt.Sprintf("Errored on routine %d", i+1)}
}
// Mark the wait group as Done so it does not hang
wg.Done()
}(i)
}
// Put the wait group in a go routine.
// By putting the wait group in the go routine we ensure either all pass
// and we close the "finished" channel or we wait forever for the wait group
// to finish.
//
// Waiting forever is okay because of the blocking select below.
go func() {
wg.Wait()
close(finished)
}()
// This select will block until one of the two channels returns a value.
// This means on the first failure in the go routines above the errChannel will release a
// value first. Because there is a "return" statement in the err check this function will
// exit when an error occurs.
//
// Due to the blocking on wg.Wait() the finished channel will not get a value unless all
// the go routines before were successful because not all the wg.Done() calls would have
// happened.
select {
case <-finished:
case err := <-errChannel:
if err != nil {
fmt.Println("error ", err)
return
}
}
fmt.Println("Successfully executed all go routines")
}
func main() {
cmd := os.Args[1] // the first argument is a command we’ll execute on all servers
hosts := os.Args[2:] // other arguments (starting from the second one) – the list of servers
results := make(chan string, 10) // we’ll write results into the buffered channel of strings
timeout := time.After(5 * time.Second) // in 5 seconds the message will come to timeout channel
// initialize the structure with the configuration for ssh packat.
// makeKeyring() function will be written later
config := &ssh.ClientConfig{
User: os.Getenv("LOGNAME"),
Auth: []ssh.ClientAuth{makeKeyring()},
}
// running one goroutine (light-weight alternative of OS thread) per server,
// executeCmd() function will be written later
for _, hostname := range hosts {
go func(hostname string) {
results <- executeCmd(cmd, hostname, config)
}(hostname)
}
// collect results from all the servers or print "Timed out",
// if the total execution time has expired
for i := 0; i < len(hosts); i++ {
select {
case res := <-results:
fmt.Print(res)
case <-timeout:
fmt.Println("Timed out!")
return
}
}
}