Как остановить обработку после получения первых двух успешных запросов в Go
Что проверяют
Умеете ли вы остановить конкурентную работу досрочно и не оставить висящих горутин. Задача короткая, но в ней легко получить утечку, панику или вечное ожидание. Следом обычно спрашивают, что будет, если успешных ответов окажется меньше двух.
Как отвечать
Каркас решения:
- контекст:
ctx, cancel := context.WithCancel(parent)иdefer cancel(). Каждый запрос строится черезhttp.NewRequestWithContext(ctx, ...), чтобы отмена реально обрывала сетевые вызовы; - буферизованный канал:
results := make(chan result, len(urls)). Буфер на все запросы гарантирует, что горутина отправит результат и завершится, даже когда его уже никто не читает; - цикл чтения: успех увеличивает счётчик, на втором вызываем
cancel()и выходим. Ошибки тоже считаем: условие выхода «два успеха или пришли все ответы», иначе при нехватке успешных цикл будет ждать вечно; - итог: если после всех ответов успехов меньше двух, возвращаем ошибку с собранными причинами.
Отменённые запросы вернут context.Canceled, настоящими ошибками их считать не нужно. Если важно дождаться выхода всех горутин, например в тесте с goleak, добавьте sync.WaitGroup и ждите его после cancel(). С Go 1.22 переменная цикла своя на каждой итерации, копировать её перед запуском горутины больше не нужно.
errgroup.WithContext удобен для ограничения параллелизма через SetLimit, но сам отменяет контекст на первой ошибке, а не на успехе, так что условие «два успеха» всё равно пишете вы.
Где спотыкаются
Закрывают канал со стороны читателя. Горутины, которые ещё отправляют результаты, падают с паникой send on closed channel. Закрывает канал только отправитель и только когда отправлять больше некому.
Берут небуферизованный канал и выходят после второго ответа. Остальные горутины навсегда блокируются на отправке, это классическая утечка.
Держат wg.Wait() до чтения результатов или не передают контекст в запросы. Программа формально «останавливается», но ждёт самый медленный запрос целиком.