Имейлите се проваляха. Тази част беше очаквана — счупени SMTP данни за достъп по време на миграция. Неочакваното беше друго: те никога не спряха да се провалят.
Таблото на Horizon: зелено. Работниците: здрави. Redis: расте бавно. Никакви аларми, никакви грешки в логовете. Само тихо натрупване на задачи, които опитваха, опитваха и пак опитваха.
Забелязах го само защото паметта на Redis не падна обратно, след като поправих SMTP конфигурацията. Нещо още беше вътре и дъвчеше повторни опити. Хиляди.
Предположих, че опашката ще се справи. Това е сделката: една задача се проваля, пробва още няколко пъти, пада в failed_jobs. Продължаваш нататък.
Освен ако задачата не е Mailable.
Когато изпратиш Mailable към опашка, Laravel го обвива в задача. maxTries на тази задача идва от свойството $tries на Mailable-а. Ако не го зададеш — а защо би, документацията едва го споменава — то се сериализира като null.
null не значи „използвай стойността по подразбиране на supervisor-а“. null значи „без лимит“. Horizon вижда null и си мисли: тази задача иска да опитва отново завинаги. И го прави.
Оказа се, че е известен бъг. Laravel Horizon issue #1346. Флагът --tries на supervisor-а се игнорира, когато сериализираният payload на задачата носи maxTries: null. Собствената декларация на задачата печели, а тя казва: никога не спирай.
Двадесет и девет Mailable класа. Всеки един без изрично свойство $tries. Всеки един потенциално безсмъртен.
Поправката е почти обидно проста:
class WelcomeEmail extends Mailable implements ShouldQueue
{
public int $tries = 2;
public int $maxExceptions = 2;
}Две свойства. Двадесет и девет файла. Това е.
Един първоначален опит, един retry, после failed_jobs. Така, както предполагах, че винаги е работело.
Тествам го така, както би тествал капан за мишки. Чупя SMTP конфигурацията нарочно. Пускам един имейл към опашката. Гледам Horizon. Два опита. Failed job. Край. Без призраци в опашката.
После поправям останалите двадесет и осем.
Три урока, сгъстени:
nullне е „default“. В сериализираните payload-и на задачиmaxTries: nullзначи без лимит. Supervisor конфигурацията ти е предложение, не правило.- Зелените dashboard-и лъжат. Horizon показваше здрави worker-и, които щастливо обработваха задачи, които никога нямаше да приключат.
- Framework default-ите не винаги са разумни. Laravel не задава
$triesна Mailables. Ти трябва да го направиш. Документацията няма да те предупреди, докато вече нямаш пожар.
Най-страшните бъгове са онези, които изглеждат като нормална работа. Този изглеждаше така — седмици наред.

Коментари