I have written go code to call multiple http request independently and combine the results.
Sometime values are missing on combined method.
func profile(req *http.Request) (UserMe, error, UserRating, error) {
wgcall := &sync.WaitGroup{}
uChan := make(chan ResUser)
rChan := make(chan ResRating)
// variable inits
var meResp UserMe
var ratingResp UserRating
go func() {
res := <-uChan
meResp = res.Value
}()
go func() {
res := <-rChan
ratingResp = res.Value
}()
wgcall.Add(2)
go me(req, wgcall, uChan)
go rate(req, wgcall, rChan)
wgcall.Wait()
logrus.Info(meResp) // sometimes missing
logrus.Info(ratingResp) // sometimes missing
return meResp, meErr, ratingResp, ratingErr
}
But the me and rating calls returns the values from api requests as expected.
func me(req *http.Request, wg *sync.WaitGroup, ch chan ResUser) {
defer wg.Done()
// http call return value correclty
me := ...
ch <- ResUser{
Value := // value from rest
}
logrus.Info(fmt.Sprintf("User calls %v" , me)) // always return the values
close(ch)
}
func rate(req *http.Request, wg *sync.WaitGroup, ch chan ResRating) {
defer wg.Done()
// make http call
rating := ...
ch <- ResRating{
Value := // value from rest
}
logrus.Info(fmt.Sprintf("Ratings calls %v" , rating)) // always return the values
close(ch)
}
Issue is: meResp and ratingResp on profile function not getting the values always. sometime only meResp or ratingResp, sometimes both as expected.
But me and rate function calls getting values always.
Can help me fix this plz ?
There's a race condition in your code.
There is no barrier to ensure the goroutines in the profile
method which read from uChan
and rChan
have populated the variables meResp
and ratingResp
before you return from profile
.
You can simplify your code immensely by dropping the use of channels and the inline declared goroutines in profile
. Instead, simply populate the response values directly. There is no benefit provided by using channels or goroutines to read from them in this circumstance as you only intend to send one value and you have a requirement that the values produced by both HTTP calls are present before returning.
You might do this by modifying the signature of me
and rate
to receive a pointer to the location to write their output, or by wrapping their calls with a small function which receives their output value and populates the value in profile
. Importantly, the WaitGroup
should only be signalled after the value has been populated:
wgcall := &sync.WaitGroup{}
var meResp UserMe
var ratingResp RatingMe
wgcall.Add(2)
// The "me" and "rate" functions should be refactored to
// drop the wait group and channel arguments.
go func() {
meResp = me(req)
wgcall.Done()
}()
go func() {
ratingResp = rate(req)
wgcall.Done()
}()
wgcall.Wait()
// You are guaranteed that if "me" and "rate" returned valid values,
// they are populated in "meResp" and "ratingResp" at this point.
// Do whatever you need here, such as logging or returning.