Suppose I have the following function, doWork
, that starts some work in a goroutine and returns a Result
to check for completion and error:
func doWork() *Result {
r := Result{doneCh: make(chan struct{})}
go func() {
var err error
defer func() {
r.err = err
close(r.doneCh)
}()
// do some work
}()
return &r
}
where Result
is the following struct:
type Result struct {
doneCh chan struct{}
err error
}
// doneCh returns a closed chan when the work is done.
func (r *Result) Done() <-chan struct{} {
return r.doneCh
}
// Err returns a non-nil err if the work failed.
// Don't call Err until Done returns a closed chan.
func (r *Result) Err() error {
return r.err
}
is this code thread safe if I set err
before closing doneCh
:
defer func() {
r.err = err
close(r.doneCh)
}()
or is the compiler free to order the r.err = err
and close(r.doneCh)
instructions as it likes, in which case I'd need a mutex to prevent concurrent read/writes on error.
The compiler may not reorder the assignment and close statement, so you do not need a mutex if callers are well-behaved and do as instructed by your docs.
This is explained in The Go Memory Model, Channel Communication.
It is thread-safe only if your comments are obeyed and Err()
is never called until a read from Done()
returns.
You could simply make Err()
blocking though by re-implementing it as:
func (r *Result) Err() error {
<-r.doneCh
return r.err
}
Which would guarantee that Err()
only returns after done is complete. Given that err will be nil until the work errors, you have no way of telling if Err()
is returning successfully because work was finished or because it hasn't completed or errored yet unless you block on Done()
first, in which case why not just make Err()
blocking?
Have you tried using chan error
and testing if the channel is opened or closed on reception?
package main
import (
"errors"
"fmt"
)
func delegate(work func(ch chan error)) {
ch := make(chan error)
go work(ch)
for {
err, opened := <- ch
if !opened {
break
}
// Handle errors
fmt.Println(err)
}
}
func main() {
// Example: error
delegate(func(ch chan error) {
defer close(ch)
// Do some work
fmt.Println("Something went wrong.")
ch <- errors.New("Eyyyyy")
})
// Example: success
delegate(func(ch chan error) {
defer close(ch)
// Do some work
fmt.Println("Everything went fine.")
})
// Example: error
delegate(func(ch chan error) {
defer close(ch)
// Do some work
fmt.Println("Something went wrong more than once.")
ch <- errors.New("Eyyyyy 1")
ch <- errors.New("Eyyyyy 2")
ch <- errors.New("Eyyyyy 3")
})
}