Middle+
// When updating an order, we need to send order data to multiple services (third-party APIs) // the number of services is growing (may be thousands+) // we wrote code, initially everything was fine, but over time our service started consuming a lot of memory 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 inside a goroutine — the variable hosts[i] may change by the time the goroutine executes, leading to incorrect calls.
Additionally, with a large number of services (thousands or more), launching thousands of goroutines simultaneously will result in high memory and resource consumption.
Recommendations for fixing:
- Pass the variable into the closure to avoid capturing the changing variable:
for i := 0; i < len(hosts); i++ {
host := hosts[i]
go func() {
response, err := s.httpClient.Send(ctx, host, order)
if err != nil {
s.logger.Error(ctx, "failed to send", err)
return
}
s.logger.Info(ctx, "success", response)
}()
}
- Limit the number of concurrently running goroutines using a semaphore or worker pool, to prevent creating thousands of goroutines at once:
sem := make(chan struct{}, 100) // limit to 100 parallel calls
for _, host := range hosts {
sem <- struct{}{}
go func(h string) {
defer func() { <-sem }()
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{}{}
}
This way, we avoid memory leaks and control system load.