Graceful shutdown пула
Ночной деплой порвал начатые загрузки: процесс убили, а воркеры не успели доделать начатое. Сергей потребовал, чтобы деплой больше не рвал чужие загрузки, и Миша сделал graceful shutdown — перестал принимать новое и дал начатому завершиться.
graceful shutdown — перестать принимать новое, дать начатому завершиться: для этого перестают слать в tasks, дают воркерам дочитать канал и ждут их через WaitGroup.
Что значит «грациозно»
Graceful shutdown — это завершение без потерь: новые задачи не принимаются, а уже начатые доводятся до конца. Для пула это значит: производитель перестаёт отправлять в tasks, воркеры дочитывают канал (range сам закончится по закрытию), и главная горутина ждёт их через wg.Wait.
Остановка по сигналу
// производитель (wg.Add для воркеров выполнялся при их старте, L95):
loop:
for {
select {
case <-stop: // сигнал остановки (ctx, SIGTERM)
break loop
case t, ok := <-input:
if !ok { // вход закрылся — то же, что сигнал остановки
break loop
}
select { // политика явная: принятую, но ещё не
case tasks <- t: // отправленную задачу доводим до канала
case <-stop: // ...или отбрасываем до отправки
break loop
}
}
}
close(tasks) // новых задач не будет
wg.Wait() // ждём, пока воркеры доработают очередь
В этом фрагменте два места, где код легко сломать. Приём без ok (case t:= <-input) на закрытом канале вечно отдаёт zero value: производитель зальёт tasks нулевыми задачами и никогда не дойдёт до остановки. А отправка tasks <- t вне select может надолго заблокировать производителя (все воркеры заняты), и он перестанет видеть stop. Поэтому приём — с ok, отправка — внутри select с веткой stop. Политика выбрана явно: задача, принятая из input, но ещё не попавшая в tasks, отбрасывается; «начатое доводится» касается только задач, уже отправленных в канал, — их дорабатывает close + range + wg.Wait.
Ключевая последовательность: сначала закрыть канал задач, потом ждать. Закрыть и не ждать — рискуешь выйти раньше, чем начатые задачи доделались: close(tasks) не прерывает работающую горутину, воркер доведёт текущий process до конца, но завершившийся main оборвёт его вместе с результатом. Гарантию «начатое доведено» даёт только wg.Wait. Если ждать, не закрыв — воркеры ждут задач вечно.
Что происходит с начатым
Воркер, читающий через range, после close(tasks) дорабатывает всё, что уже в канале, и только потом выходит — это свойство range по закрытому каналу, ничего специально «догонять» не нужно. Но договор «начатое доводится» верен при двух условиях. Первое: process обязан возвращаться — зависший воркер не выйдет из цикла никогда, и wg.Wait повиснет вечно, поэтому таймаут на доработку из расширения — штатная часть схемы, а не страховка на крайний случай. Второе: если у пула есть канал результатов, его нужно вычитывать (до или параллельно с wg.Wait) — иначе воркеры встанут на отправке результата и до wg.Done не дойдут.
Вы узнаете:
- как таймаут на доработку работает
- как
signal.NotifyContextловит SIGTERM/SIGINT
Graceful shutdown: перестать принимать новое → закрыть tasks → wg.Wait. Close не прерывает исполнение; завершённость начатого гарантирует только ожидание.
10 000 ₽ · 60 минут · онлайн