Как стать автором
Обновить

Комментарии 2

"в редакцию пришло письмо":

Уже вроде обсуждали в этом канале, стандартный context обеспечивает закрытие по дереву вверх, но это не graceful shutdown (для Graceful Shutdown требуется ожидание обслуживающего детей родителя пока он не обслужит всех своих детей и все его дети не закроются, после чего и сам родитель уже может благополучно закрыться). На деле же при отмене стандартного контекста все горутины отменяющейся ветви получают сигнал закрывающего канала (не важно в каком порядке) после чего все одновременно (и родители и дети) начинают закрываться без ожидания детей, что может привести просто к блочке (когда ребенок отправляет родителю сообщение, а родитель уже завершил свою работу). Я на прошлой неделе уже писал о том что реализовал контекст, который позволяет дожидаться детей путём обертывания горутины ребенка в вызов метода (мой проект: https://github.com/mcfly722/context ) но в процессе разработки данного контекста мной был понят очень важный аспект того, что мы используем вообще не ту структуру данных. Т.е. из контекстов нам нужно вообще НЕ ДЕРЕВО, нам нужен направленный ГРАФ! Это значит что у каждого контекста не должно быть ограничения только в одного родителя (родители являются инфраструктурой для своих детей) поэтому их должно быть множество, а не строго один. И соответственно также должна соблюдаться последовательность закрытия (пока есть контекст который зависит от своих родителей (своей инфраструктуры), его родителей (инфраструктуру) нельзя закрывать). На реализацию я стартанул проект dependency, как будут по нему новости отпишу в чатик.

вот этот issue: https://github.com/golang/go/issues/51075



Да, с закрытием контекста не всё так просто. Часто чтобы обработать реквест нужно сходить в базу данных. И вот если сигнал остановки пришёл между получением запроса и ещё до похода в базу - мы пойдём в базу с просроченным контекстом. В результате в метриках мы увидим ошибку, хотя ничего нестандартного и не происходило.

В случае с веб сервером отмена контекста на самом деле и не очень то нужна. Ведь обработка http запросов (не websocket) предполагает, что мы получили реквест, отдали данные и завершили работу. Так что тут будет достаточно вызвать стандартный Shutdown самого сервера и все его хэндлеры вскоре завершаться сами собой.

А вот с воркерами история немного другая. Лично я предпочитаю передавать в воркеры не только контекст, но и ещё сигнальный канал (один на всё приложение). Соответственно воркеры в селекте проверяют не ctx.Done(), а именно этот сигнальный канал.

Пример:

func main() {
  ctx, cancel := context.WithCancel(context.Background()) 
  defer cancel()

  closing := make(chan bool) // signal channel
  wg := &sync.WaitGroup{}
  
  wg.Add(1)
  go func(ctx context.Context, closing chan bool) {
  	defer wg.Done()
  
  	ticker := time.NewTicker(time.Second)
  	for {
  		select {
  		case <-closing:
  			return
  		case <-ticker.C:
  			// do some work, ctx is always valid
  			fmt.Println("something")
  		}
  	}
  }(ctx, closing)
  
  c := make(chan os.Signal, 1)
  signal.Notify(c, os.Interrupt, syscall.SIGTERM)
  
  <-c // wait for stop signal
  
  close(closing) // initiate graceful shutdown
  
  wg.Wait() // wait until workers are done
}

Да, кода получается больше и здесь ещё нет обработки ошибок в воркерах и не обрабатывается ситуация, когда воркеры не завершились за отведённое время. Но в целом идея примерно такая.
Зато при редеплое приложения вы не обнаружите на графиках кучу ошибок, взявшихся неизвестно откуда.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории