I have three commands to run, but I'd like to make sure the two first are running before running the third one.
Currently, it does run A and B then C.
- I run A and B in goroutines
- I communicate their name through
chan
if there's nostderr
- the
main
functions pushes the names received through chan into a slice - once the slice contains all names of module A and B it starts C
Some context
I'm in the process of learning goroutines and chan as a hobbyist. It's not clear to me how to output exec.Command("foo", "bar").Run()
in a reliable way while it's running. It's not clear either how to handle errors received by each process through chan.
The reason why I need A and B to run before C is because A and B are graphql microservices, C needs them to run in order to get their schemas through HTTP and start doing some graphql federation (f.k.a. graphql stitching)
Inconsistencies
- With my current approach, I will know if A and B are running only if they print something I guess.
- I don't like that each subsequent
stdout
will hit an if statement, just to know if the process is running. - My error handling is not as clean as I'd like it to be.
Question
How could I have a more reliable way to ensure that A and B are running, event if they don't print anything and that they did not throw errors?
package main
import (
"bufio"
"fmt"
"log"
"os/exec"
"reflect"
"sort"
"strings"
"sync"
)
var wg sync.WaitGroup
var modulesToRun = []string{"micro-post", "micro-hello"}
func main() {
// Send multiple values to chan
// https://stackoverflow.com/a/50857250/9077800
c := make(chan func() (string, error))
go runModule([]string{"go", "run", "micro-post"}, c) // PROCESS A
go runModule([]string{"go", "run", "micro-hello"}, c) // PROCESS B
modulesRunning := []string{}
for {
msg, err := (<-c)()
if err != nil {
log.Fatalln(err)
}
if strings.HasPrefix(msg, "micro-") && err == nil {
modulesRunning = append(modulesRunning, msg)
if CompareUnorderedSlices(modulesToRun, modulesRunning) {
go runModule([]string{"go", "run", "micro-federation"}, c) // PROCESS C
}
}
}
}
func runModule(commandArgs []string, o chan func() (string, error)) {
cmd := exec.Command(commandArgs[0], commandArgs[1], commandArgs[2]+"/main.go")
// Less verbose solution to stream output with io?
// var stdBuffer bytes.Buffer
// mw := io.MultiWriter(os.Stdout, &stdBuffer)
// cmd.Stdout = mw
// cmd.Stderr = mw
c := make(chan struct{})
wg.Add(1)
// Stream command output
// https://stackoverflow.com/a/38870609/9077800
go func(cmd *exec.Cmd, c chan struct{}) {
defer wg.Done()
stdout, err := cmd.StdoutPipe()
if err != nil {
close(o)
panic(err)
}
stderr, err := cmd.StderrPipe()
if err != nil {
close(o)
panic(err)
}
<-c
outScanner := bufio.NewScanner(stdout)
for outScanner.Scan() {
m := outScanner.Text()
fmt.Println(commandArgs[2]+":", m)
o <- (func() (string, error) { return commandArgs[2], nil })
}
errScanner := bufio.NewScanner(stderr)
for errScanner.Scan() {
m := errScanner.Text()
fmt.Println(commandArgs[2]+":", m)
o <- (func() (string, error) { return "bad", nil })
}
}(cmd, c)
c <- struct{}{}
cmd.Start()
wg.Wait()
close(o)
}
// CompareUnorderedSlices orders slices before comparing them
func CompareUnorderedSlices(a, b []string) bool {
if len(a) != len(b) {
return false
}
sort.Strings(a)
sort.Strings(b)
return reflect.DeepEqual(a, b)
}