3 ms·
It's a weird example, because it moves listener creation elsewhere, which is not always possible (e.g. consider if the goroutine would be spawning an external p
by drdaeman 3y ago
It's a weird example, because it moves listener creation elsewhere, which is not always possible (e.g. consider if the goroutine would be spawning an external program instead of setting up the listener of its own).
Go has excellent primitive for this, a channel:
// Setup a channel. We'll send one and only one messsage over it, then close it.
// The message will be either an error (if something went wrong)
// or nil, if everything is fine and we can proceed.
ready := make(chan error)
go func() {
// No matter how this goroutine exits, when it exits the channel will be closed
defer close(ready)
// Do whatever we must: open a socket, run a subprocess, or something else
lis, err := net.Listen(...)
if err != nil {
// Let it be known we've failed
ready <- err
return
}
defer lis.Close()
// Announce the "parent" goroutine we're ready, without errors
ready <- nil
...
}()
// Here, we block and wait for our "child" goroutine to tell
// us something or at least die and close the socket.
// You can extend this and e.g. use `select` if you want to implement a timeout
err, ok := <-ready
if err != nil {
return fmt.Errorf("failed to start listener goroutine: %w", err)
} else if !ok {
return errors.New("listener goroutine died unexpectedly")
}
If there are multiple tasks and the case is not complex, then use sync.WaitGroup to count the children and a shared variable to convey the error status.
- lloydatkinson 3y agoMultiple languages have channels, I’m not sure why people only discuss this in terms of Go. .NET and Rust are the first two I can name off the top of my head.
- sapiogram 3y agoIt pisses me off so much that Go doesn't have a built-in way to wait for a goroutine to complete, similar to how every other language I've used allows joining threads. Would avoid all this, solve some extra problems to boot, and be much less error-prone.
- wmanley 3y ago> It's a weird example, because it moves listener creation elsewhere, which is not always possible (e.g. consider if the goroutine would be spawning an external program instead of setting up the listener of its own). Just addressing the case of spawning an external program: if you have control of the external program you’re spawning it can be better to pass the listening socket in using the systemd $LISTEN_PID protocol. It’s easy too implement, language independent and you get systemd integration for free. See also: https://blog.williammanley.net/2014/01/26/Improve-integration-test-sandboxing-with-systemd-socket-passing.html https://blog.williammanley.net/2014/01/26/Improve-integratio...
- duckydude20 3y agoidk, i am new to goland but same thing, tls issue. my tests were very flaky. sometimes they were failing sometimes they weren't. i initially thought it was wsl and goland issue. but after it happened 2-3 times. i ran with goroutines test with -count 50 but it didn't work in goland. then i issued in terminal. which i got to see yup my tests are actually flaky. i know this is not the best way. but i added a timeout of 10sec. also while asserting i am polling to know if i got a connection. i understand and i can make it way better. but for now it's good enough. more importantly, it's better not scatter these kind of codes everywhere. also making things explicit is better always. like now server.Start is Blocking. and in my tests i am spawning a goroutine, rather than it doing implicitly. another thing, testing dependencies. i am going to test concurrent handler. and it's not dependent on tls or tcp. i can just use a fake and test the concurrency. i like this design. scattering these throughout will make every other test integration tests. increasing time to execute and that is bad. btw, it would have been better if go lang runs tests in parallel.