panic в горутине приводит к плачевным последствиям. Каков идеоматический подход?

393 views
Skip to first unread message

Денис Новосибирский

unread,
Aug 3, 2016, 7:23:25 AM8/3/16
to Golang Russian
Всем привет!

Словил такую проблему. Есть daemon-рассыльщик писем, который запускает несколько горутин. Каждая из них достаёт из базы письма и отправляет через внешний рассыльщик. Получив ответ от внешнего сервиса, горутина обновляет статус "отправлено" у письма-записи в базе. НО, в горутине оказалась ошибка, которая вызывает panic. Воркер соответственно падает. Получилось так, что некоторые горутины успевают отправить письмо, но не успевают обновить запись в базе. При перезапуске воркера это письмо отправляется повторно.

Какие есть подходы, чтобы избегать подобных проблем? Нужно, видимо, думать в сторону атомарности-транзакционности.

Daniel Podolsky

unread,
Aug 3, 2016, 7:36:33 AM8/3/16
to gola...@googlegroups.com
панику рековерить, статус отправки проверять, базу обновлять
> --
> Вы получили это сообщение, поскольку подписаны на группу "Golang Russian".
> Чтобы отменить подписку на эту группу и больше не получать от нее сообщения,
> отправьте письмо на электронный адрес
> golang-ru+...@googlegroups.com.
> Чтобы настроить другие параметры, перейдите по ссылке
> https://groups.google.com/d/optout.

Ilya Ozherelyev

unread,
Aug 3, 2016, 10:58:05 AM8/3/16
to Golang Russian
Не делать панику, сделать канал ошибок, с обработкой. 

среда, 3 августа 2016 г., 14:23:25 UTC+3 пользователь Денис Новосибирский написал:

Daniel Podolsky

unread,
Aug 3, 2016, 11:31:59 AM8/3/16
to gola...@googlegroups.com
> Не делать панику, сделать канал ошибок, с обработкой.
панику может кидать чужой код

Alex Lurye

unread,
Aug 3, 2016, 2:47:21 PM8/3/16
to gola...@googlegroups.com
Исправить чужой код и убрать панику. Это же open source.

Паника - это ошибки уровня memory corruption, bad CPU, etc. Их не нужно ловить - надо просто падать. У вас нет возможности восстановиться после них, т.к. уже нет доверия исполняющей платформе. В очень редких случаях panic можно использовать для прерывания сложнозапутанного кода, но паника должна перехватываться внутри самой библиотеки и никогда не доставаться её

On Wed, Aug 3, 2016 at 8:31 AM Daniel Podolsky <onok...@gmail.com> wrote:
> Не делать панику, сделать канал ошибок, с обработкой.
панику может кидать чужой код

--
Вы получили это сообщение, поскольку подписаны на группу Golang Russian.

Чтобы отменить подписку на эту группу и больше не получать от нее сообщения, отправьте письмо на электронный адрес golang-ru+...@googlegroups.com.
Настройки подписки и доставки писем: https://groups.google.com/d/optout.

Alex Lurye

unread,
Aug 3, 2016, 2:47:30 PM8/3/16
to gola...@googlegroups.com
... не доставаться её клиентам.

Daniel Podolsky

unread,
Aug 3, 2016, 2:51:04 PM8/3/16
to gola...@googlegroups.com
в реальности панику бросют самые разные куски кода. например - кастинг
интерфейсов.

2016-08-03 21:47 GMT+03:00 'Alex Lurye' via Golang Russian
<gola...@googlegroups.com>:

Alex Lurye

unread,
Aug 3, 2016, 2:53:37 PM8/3/16
to gola...@googlegroups.com
Кастинг интерфейсов надо делать со вторым аргументом:

if sr, ok := req.(StreamingRequest); ok {
  // do whatever with sr
}

Если ожидается строго объект определённого типа, то возвращать ошибку, а не паниковать:
sr, ok := req.(StreamingRequest)
if !ok {
  return fmt.Errorf("received %T request, want StreamingRequest", req)
}

Daniel Podolsky

unread,
Aug 3, 2016, 2:56:00 PM8/3/16
to gola...@googlegroups.com
мне все это не надо объяснять.

но получить из чужой либы панику, причем не означающую, что "платформе
больше нельзя доверять" - легче легкого

Alex Lurye

unread,
Aug 3, 2016, 2:57:12 PM8/3/16
to gola...@googlegroups.com
Естественно. Надо просто понимать, что чужие либы в Go распространяются, слава богу, в исходных кодах, и их можно и нужно фиксить.

Vladimir Skipor

unread,
Aug 3, 2016, 4:14:44 PM8/3/16
to Golang Russian

панику рековерить
 Поддерживаю. Причины паник, разумеется, стоит исправлять, но стоит их ловить на верху каждой созданной горутины, где она, с не нулевой вероятностью: может произойти, и нас не устроит аварийное завершение программы в таком случае.
Я бы сделал чтобы: Отправляющие горутины через канал возвращали что-нибудь вроде struct{EmailId, error}, в случае паники делали revover, и отправляли стектрейс в ошибке. 
Родительская горутина была panic-safe (без использования стороннего кода, небезопасных операций без проверок), собирала все результаты, и одной транзакцией обновляла базу. Обращение к базе тоже стоит завернуть так, чтобы в случае паники мы не теряли данные об отправке: пробовали записать снова, или сохраняли локально. 

Денис Новосибирский

unread,
Aug 3, 2016, 10:30:00 PM8/3/16
to Golang Russian
Спасибо за ответы!
Паника получилась ненамеренно в моем коде, в котором допущена ошибка. Ошибка, конечно, будет исправлена, но хочется выработать общий подход, чтобы исключить подобные проблемы с дублированием писем в будущем.

среда, 3 августа 2016 г., 18:23:25 UTC+7 пользователь Денис Новосибирский написал:
Reply all
Reply to author
Forward
0 new messages