Обернуть медленную функцию таймаутом через channel и select
unpredictableFunc() int64 работает неопределённо долго (внутри сетевой вызов), и менять её тело нельзя. Напишите predictableFunc, возвращающую её результат, если та успела к дедлайну, или ошибку, если дедлайн истёк первым.
Ограничения:
- Принимать
context.Context; если у него нет дедлайна, применить таймаут по умолчанию. - Не утекать goroutine worker при срабатывании таймаута.
func predictableFunc(ctx context.Context) (int64, error) {
// ваш код здесь
}
Допишите реализацию.
Запустите unpredictableFunc в goroutine, которая шлёт результат в буферизированный channel ёмкости 1, затем select между этим channel и ctx.Done(). Буфер здесь несущий: с небуферизированным channel worker навсегда блокируется на отправке после таймаута, утекая. Если у ctx нет дедлайна, оберните его context.WithTimeout с defer cancel().
- ✗Использовать небуферизированный channel и утечь goroutine worker при таймауте
- ✗Считать, что отмена
contextсама проникает в непрозрачный блокирующий вызов - ✗Забыть
defer cancel()послеcontext.WithTimeoutи утечь таймер
- →Почему небуферизированный channel результата приводит к утечке worker после таймаута?
- →Почему
context.WithTimeoutтребует парного вызоваcancel()?
Решение
func predictableFunc(ctx context.Context) (int64, error) {
if _, ok := ctx.Deadline(); !ok {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, time.Second)
defer cancel()
}
resCh := make(chan int64, 1) // буфер 1 — иначе worker утечёт при таймауте
go func() {
resCh <- unpredictableFunc()
}()
select {
case res := <-resCh:
return res, nil
case <-ctx.Done():
return 0, ctx.Err()
}
}
Почему буфер обязателен. При таймауте получатель уходит из select через ctx.Done() и больше не читает resCh. Если бы channel был небуферизированным, отправка resCh <- ... в worker заблокировалась бы навсегда — goroutine утекла бы. Буфер ёмкости 1 принимает значение без получателя, и worker завершается.
unpredictableFunc мы не можем прервать (тело менять нельзя), поэтому таймаут лишь освобождает вызывающего; сама фоновая goroutine доработает, но не зависнет.