// When updating an order, we need to send order data to several (possibly thousands) third-party services // We wrote the code, everything was fine at first, but over time our service started consuming a lot of resources func (s *orderService) SendOrder(ctx context.Context, hosts []string, order Order) { for i := 0; i < len(hosts); i++ { go func() { // Imagine this is a long network call response, err := s.httpClient.Send(ctx, hosts[i], order) if err != nil { s.logger.Error(ctx, "failed to send", err) return } s.logger.Info(ctx, "success", response) }() } }
sobes.tech AI
Answer from AI
The provided code has an issue with using the loop variable i inside a goroutine:
for i := 0; i < len(hosts); i++ {
go func() {
response, err := s.httpClient.Send(ctx, hosts[i], order)
// ...
}()
}
Here, i is used inside an anonymous function, but by the time the goroutine runs, the value of i may have changed, leading to errors (e.g., out-of-bounds access).
Additionally, launching thousands of goroutines simultaneously when there are many services (thousands or more) can lead to excessive resource consumption (memory, CPU, network connections).
Recommendations:
- Fix the closure of the variable
i:
for i := 0; i < len(hosts); i++ {
host := hosts[i] // local copy
go func(h string) {
response, err := s.httpClient.Send(ctx, h, order)
// ...
}(host)
}
- Limit the number of concurrently running goroutines:
Use a semaphore or worker pool to avoid creating too many goroutines at once.
Example with a semaphore:
sem := make(chan struct{}, 100) // maximum 100 concurrent goroutines
for _, host := range hosts {
sem <- struct{}{} // acquire semaphore
go func(h string) {
defer func() { <-sem }() // release semaphore
response, err := s.httpClient.Send(ctx, h, order)
if err != nil {
s.logger.Error(ctx, "failed to send", err)
return
}
s.logger.Info(ctx, "success", response)
}(host)
}
// wait for all goroutines to finish
for i := 0; i < cap(sem); i++ {
sem <- struct{}{}
}
- Use a worker pool:
Create a fixed number of workers that read from a channel with hosts and send requests.
- Handle errors and timeouts:
For robustness, it is important to control timeouts and handle errors properly.
In summary, to prevent resource consumption from growing uncontrollably, you should control concurrency and handle variables in closures correctly.