Реализуйте RunService, который запускает набор фоновых задач (например,
metrics, broker, cache cleanup). Каждая задача получает один и тот же
ctx и обязана слушать ctx.Done().
Правила:
- Все задачи запускаются как горутины с переданным
ctx.
- Когда внешний код отменяет
ctx, всем задачам даётся grace на завершение.
- Функция возвращается, когда все задачи завершились, либо когда истёк
grace.
- Если все задачи успели завершиться в пределах
grace — возвращается nil.
- Если хотя бы одна задача не уложилась в
grace — возвращается ошибка
(принудительное завершение).
// RunService запускает фоновые задачи; каждая слушает ctx.Done(). Когда внешний
// код отменяет ctx, всем задачам даётся grace на завершение. Возвращает, когда
// все завершились, либо по истечении grace.
func RunService(ctx context.Context, grace time.Duration, tasks ...func(ctx context.Context)) error {
// Ваша реализация
}
Подсказка: sync.WaitGroup на все задачи; после <-ctx.Done() ждите
закрытия done-канала (go func(){ wg.Wait(); close(done) }()) против
time.After(grace) в select.
На что смотрит интервьюер:
- Каждая задача запускается с общим
ctx (древовидная отмена).
- Ожидание завершения через
WaitGroup + done-канал.
select между завершением всех задач и time.After(grace).
- Корректный
nil при graceful и ошибка при принудительном завершении —
без зависания на «зависшей» задаче.