Есть список источников (зеркала, DNS-серверы, регионы). Функция probe
опрашивает один источник и возвращает время его ответа. Нужно опросить все
источники, но не больше limit одновременно, и вернуть имя того, у кого
время ответа минимальное. Если хоть один источник ответил ошибкой, результат
считается недостоверным: вернуть эту ошибку.
type Prober func(ctx context.Context, name string) (time.Duration, error)
var ErrNoSources = errors.New("no sources")
func PickFastest(ctx context.Context, sources []string, limit int, probe Prober) (name string, latency time.Duration, err error)
Требования
- Каждый источник опрашивается ровно один раз. Одновременно выполняется не
больше
limit вызовов probe (limit < 1 считается как 1), и лимит
действительно используется: если источников хватает, работают limit проб.
- Все пробы успешны → имя и latency источника с минимальным временем. При
равенстве побеждает тот, кто раньше в
sources.
- Первая же ошибка
probe → функция сразу возвращает её (можно обернуть,
но errors.Is должен её находить). Контекст запущенных проб отменяется,
а те, что ещё ждут своей очереди, не запускаются.
- Отмена или дедлайн внешнего
ctx → сразу ctx.Err(), даже если probe
контекст игнорирует.
- Пустой
sources → ErrNoSources.
- Утечек нет: когда пробы, на которых не стали ждать, завершатся, от
PickFastest не остаётся горутин. Код проходит go test -race.
Пример
name, lat, err := PickFastest(ctx, []string{"eu", "us", "asia"}, 2, probe)
// probe: eu → 120мс, us → 80мс, asia → 300мс
// name = "us", lat = 80мс, err = nil
В исходной версии задачи probe была глобальной функцией-заглушкой. Здесь она
передаётся параметром.
Устно: как написать юнит-тесты на такую функцию? Как проверить, что лимит
параллелизма соблюдается, и не получить флакающий тест?