Реализуйте Sleep, который выходит раньше, если его context.Context отменён
Напишите прерываемую версию time.Sleep. Она должна ждать всю длительность и вернуть true, но вернуть false сразу, если контекст отменён раньше.
Ограничения:
- Не утекать нижележащим таймером, когда контекст отменяется до истечения длительности.
- Не делать busy-wait и не опрашивать в цикле.
// Sleep ждёт d или возвращает false раньше, если ctx отменён.
func Sleep(ctx context.Context, d time.Duration) bool {
// your code here
}
Допишите реализацию.
select по двум case: case <-ctx.Done(): return false и case <-timer.C: return true. Не используйте time.After, который течёт нижележащим таймером до срабатывания; вместо этого t := time.NewTimer(d); defer t.Stop(), чтобы при отмене таймер освобождался сразу.
- ✗Использовать
time.Sleep, а потом проверять контекст, что не даёт вернуться до истечения всей длительности - ✗Использовать
time.Afterв select, который течёт таймером до срабатывания, если контекст отменён первым - ✗Добавлять лишнюю goroutine, когда один таймер плюс
ctx.Done()уже покрывают оба случая
- →Почему
time.Afterтечёт своим таймером, аtime.NewTimerплюсStop— нет? - →Создаёт ли проблему вызов
Stopна таймере, который уже сработал, в этом случае?
Решение
select ждёт сразу два события: истечение таймера и отмену контекста. Какое наступит первым — та ветка и сработает.
func Sleep(ctx context.Context, d time.Duration) bool {
t := time.NewTimer(d)
defer t.Stop() // освобождаем таймер при любом выходе
select {
case <-ctx.Done():
return false // отменён раньше срока
case <-t.C:
return true // дождались полной длительности
}
}
Почему NewTimer + Stop, а не time.After
time.After(d) под капотом создаёт *time.Timer, но не отдаёт ссылку на него. Если контекст отменяется раньше d, таймер всё равно живёт в куче и в очереди рантайма до самого срабатывания — это утечка на каждый отменённый вызов. В горячем пути (например, цикле с коротким d и частыми отменами) такие таймеры копятся. time.NewTimer отдаёт *Timer, поэтому defer t.Stop() освобождает его сразу при отмене.
t.Stop() безопасно вызывать и на уже сработавшем таймере — он просто вернёт false. Дренировать t.C здесь не нужно: мы либо уже прочитали из него (return true), либо больше им не пользуемся.