Есть набор взаимозаменяемых обработчиков: реплики, зеркала, разные провайдеры
одного API. Нужно запустить запрос q во всех параллельно и вернуть результат
того, кто первым ответил успешно, не дожидаясь остальных. Если все упали,
вернуть ошибку, которая пришла последней.
type Handler func(ctx context.Context, q string) (string, error)
var ErrNoHandlers = errors.New("no handlers")
func FirstSuccess(ctx context.Context, q string, handlers []Handler) (string, error)
Требования
- Все обработчики стартуют сразу и работают параллельно.
- Функция возвращает результат на первом успехе (
err == nil) и не ждёт
медленные обработчики. Ответ с ошибкой успехом не считается, даже если строка
непустая.
- После возврата контекст, переданный обработчикам, отменён: оставшиеся
должны узнать, что их результат больше не нужен.
- Все обработчики вернули ошибку — возвращается последняя по времени ошибка.
- Пустой список обработчиков —
ErrNoHandlers.
- Если отменили внешний
ctx (или истёк его дедлайн), функция сразу возвращает
ctx.Err(), даже если обработчики контекст игнорируют.
- Утечек нет: когда обработчики, на которых не стали ждать, всё-таки завершатся,
от
FirstSuccess не должно остаться ни одной горутины.
- Код проходит
go test -race.
Пример
res, err := FirstSuccess(ctx, "go", []Handler{
slowMirror, // 2 с
brokenMirror, // сразу ошибка
fastMirror, // 50 мс, успех
})
// res — ответ fastMirror примерно через 50 мс; ctx slowMirror отменён
Разбор и эталонное решение — после своей попытки: на собеседовании подсказок не будет.
Сначала попробуй сам — подсказки не дают готовый ответ.