Here is one case of Data Race Patterns in Go
Mixed use of message passing (channels) and shared memory makes code complex and susceptible to data races
func (f *Future) Start() {
go func() {
resp, err := f.f()
f.resp = resp
f.err = err // data race
f.ch <- struct{}{} // may block forever
}()
}
func (f *Future) Wait(ctx context.Context) error {
select {
case <-f.ch:
return nil
case <-ctx.Done():
f.err = ctx.Err() // data race
return ctx.Err()
}
}
Here is one data race for f.err
, which could be accessed both in Start
and Wait
.
Another issue is that f.ch <- struct{}{}
may block forever when ctx.Done()
happened before f.ch <- struct{}{}
.
What is the elegant way to handle it in Golang?
The idea comes to my mind first as below, add context to Start
function
func (f *Future) Wait(ctx context.Context) error {
select {
case <-f.ch:
return nil
case <-ctx.Done():
f.err = ctx.Err()
return ctx.Err()
}
}
func (f *Future) StartWithContext(ctx context.Context) {
go func() {
resp, err := f.f()
select {
case <-ctx.Done():
return
default:
f.resp = resp
f.err = err
f.ch <- struct{}{}
}
}()
}
We want to know if something missing in my solution? or is there a better solution to this issue?