Уведомления по электронной почте не работают при наличии дублирующихся заголовков

См. описание ошибки по ссылке: Email notifications fail if duplicate headers exist - #14 by simonk
Выше добавлено пользователем @pfaffman

Ниже приведён оригинальный текст:

Здравствуйте,

Я уже несколько лет использую довольно небольшую установку Discourse. Трафик там невысокий, поэтому я заметил проблему с отправкой писем (уведомлений, дайджестов) только несколько месяцев назад. Расследование указывает на обновление до версии 2.8.0.beta7 примерно 22 октября 2022 года; до этого мы использовали версию 2.8.0.beta4. По крайней мере, я не получал от этой установки никаких писем о публикациях или сообщениях.

Письма накапливаются в Sidekiq с сообщением об ошибке, которое мне не удаётся соотнести с какой-либо известной проблемой, и при поиске по нему ничего подходящего не находится. Хотя были сообщения об ошибках типа undefined method, ни одно из описанных условий не совпадает с моей ситуацией. (Это не проблема TLS, не тайм-аут подключения к почтовому серверу, не плагин событий, и исправление для безопасной медиа-отправки уже должно быть применено — к тому же, точные сообщения об ошибках были другими.)

Ошибка в Sidekiq:

Wrapped NoMethodError: undefined method `value' for #<Array:0x00007f7fd5277d68> Did you mean? values_at

Часть после #<Array: различается для каждого задержанного письма. Вчера я переустановил Discourse на новую виртуальную машину и восстановил данные из свежей резервной копии, но, похоже, проблема с отправкой писем тоже была восстановлена вместе с данными :thinking:

Поскольку частота ошибок начала расти в конце октября, я почти уверен, что проблема была введена в версии 2.8.0.beta7:

Буду очень признателен за любую помощь или подсказки по дальнейшей отладке этой проблемы.

1 лайк

У вас установлены какие-либо плагины? Если у вас есть нестандартные плагины, вам следует удалить их.

Попробовал это уже, проблема сохраняется :frowning:

## Плагины размещаются здесь
## подробности см. по адресу https://meta.discourse.org/t/19157
hooks:
  after_code:
    - exec:
        cd: $home/plugins
        cmd:
          - git clone https://github.com/discourse/docker_manager.git

## Любые пользовательские команды для выполнения после сборки

РЕДАКТИРОВАНИЕ: Для сведения:

Планируемые плагины:

root@discourse:/var/discourse# grep -B5 "git clone" containers/app.yml-with-plugins 
hooks:
  after_code:
    - exec:
        cd: $home/plugins
        cmd:
          - git clone https://github.com/discourse/docker_manager.git
          - git clone https://github.com/discourse/discourse-spoiler-alert.git
          - git clone https://github.com/discourse/discourse-prometheus.git
          - git clone https://github.com/discourse/discourse-solved.git
          - git clone https://github.com/discourse/discourse-chat-integration.git
          - git clone https://github.com/discourse/discourse-voting.git
          - git clone https://github.com/discourse/discourse-checklist.git
          - git clone https://github.com/discourse/discourse-whos-online.git
          - git clone https://github.com/discourse/discourse-calendar.git
          - git clone https://github.com/discourse/discourse-affiliate.git
          - git clone https://github.com/discourse/discourse-reactions.git
          - git clone https://github.com/discourse/discourse-surveys.git

В старой установке:

root@discourse-old:/var/discourse# grep -B5 "git clone" containers/app.yml
hooks:
  after_code:
    - exec:
        cd: $home/plugins
        cmd:
          - git clone https://github.com/discourse/docker_manager.git
          - git clone https://github.com/discourse/discourse-spoiler-alert.git
          - git clone https://github.com/discourse/discourse-prometheus.git
          - git clone https://github.com/discourse/discourse-solved.git
          - git clone https://github.com/discourse/discourse-chat-integration.git
          - git clone https://github.com/discourse/discourse-voting.git
          - git clone https://github.com/discourse/discourse-checklist.git
          - git clone https://github.com/davidtaylorhq/discourse-whos-online.git

Это стандартная установка? Если у вас отдельный контейнер с данными, был ли он пересобран? Актуальна ли версия PostgreSQL? (Хотя, кажется, в коде всё ещё есть проверка этого).

Да, это стандартная установка; насколько я вижу, запущен только один контейнер. (Запустите новую виртуальную машину, перенастройте DNS, выполните apt update, apt dist-upgrade, перезагрузитесь, выполните git pull, ./discourse-setup, настройку через веб-интерфейс, загрузите резервную копию, восстановите из неё, снова включите отправку почты — и ошибки проявятся снова.) Обратите внимание, что старая установка могла отправлять ссылку на резервную копию, а отправка тестовых писем в новой установке всё ещё работает — похоже, что проблемы возникают только с почтой, связанной с публикациями.

Если быть более точным, это минимальная установка Debian 10, на которую я установил Discourse:

root@discourse:~# history
    1  apt update
    2  apt dist-upgrade
    3  reboot ; exit
    4  git clone https://github.com/discourse/discourse_docker.git /var/discourse
    5  apt install git rsync
    6  git clone https://github.com/discourse/discourse_docker.git /var/discourse
    7  cd /var/discourse
       [attach /dev/vdb, fdisk, mkfs.ext4, mount as /var/lib/docker]
   18  apt-get install git apt-transport-https ca-certificates curl gnupg2 software-properties-common -y
   19  df -h
   20  curl -fsSL https://download.docker.com/linux/$(. /etc/os-release; echo "$ID")/gpg | apt-key add -
   21  add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/$(. /etc/os-release; echo "$ID") $(lsb_release -cs) stable"
   22  apt-get update -y
   23  apt-get install docker-ce -y
   24  ./discourse-setup

И один контейнер:

root@discourse:~# docker ps
CONTAINER ID   IMAGE                 COMMAND        CREATED        STATUS        PORTS                                                                      NAMES
ac408a70305d   local_discourse/app   "/sbin/boot"   12 hours ago   Up 12 hours   0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp   app

Редактирование:

root@discourse:~# apt update
Hit:1 https://download.docker.com/linux/debian buster InRelease
Hit:2 http://deb.debian.org/debian buster InRelease
Get:3 http://deb.debian.org/debian buster-updates InRelease [51,9 kB]
Get:4 http://security.debian.org/debian-security buster/updates InRelease [65,4 kB]
Fetched 117 kB in 2s (60,5 kB/s)    
Reading package lists... Done
Building dependency tree       
Reading state information... Done
All packages are up to date.

Однако первые попытки восстановления не увенчались успехом. Файл database_restorer.rb завершился ошибкой в функции restore_dump из-за дублирующихся ссылок в post_id 3841. В итоге я заменил эти ссылки в сообщении 2017 года на скриншот с их отображением из старой установки и создал новую резервную копию; только после этого мне удалось восстановить резервную копию на новой установке. Как вы упомянули, проблема с PostgreSQL возникла во время выполнения CREATE INDEX с ошибкой: ERROR: could not create unique index "unique_post_links". Дополнительная информация: EXCEPTION: psql failed: DETAIL: Key (topic_id, post_id, url)=(1300, 3841, [redacted]) is duplicated.

Хотя я не думаю, что это напрямую связано, я всё же решил упомянуть об этом.

Итак, если никто не знает простого способа исправить это, давайте отладим это правильно. Но мне нужна ваша помощь, так как Discourse — это довольно сложное приложение, использующее множество технологий, с которыми я не знаком. Итак: как письма «отправляются» в Sidekiq?

Какой компонент Discourse передаёт Sidekiq метод «value»?

Из-за того, что уведомления по электронной почте перестали работать после обновления, Discourse теперь, к сожалению, практически бесполезен. Вместо немедленного внимания темы теперь требуют дней, чтобы получить его, если вообще получают, так как в 2022 году люди не занимаются активным опросом. Нет уведомления ⇒ ничего не произошло ⇒ нет необходимости проверять сайт :frowning:

1 лайк

Похоже на повреждённый индекс. Я не припоминаю, чтобы сталкивался с этим в PostgreSQL 13. Звучит так, будто вы исправили это на старом сайте, а затем обновили его, сделав резервную копию и восстановив её на новом сайте?

Похоже, проблема связана с чем-то в коде, отправляющем уведомления, но на самом деле это проблема с Sidekiq.

Как я уже говорил, старый сайт работает. Я сделал резервную копию и попытался восстановить её на чистой установке, но восстановление не удалось. Я изменил сообщение, указанное как причина проблемы, после чего восстановление на чистой установке прошло успешно. Однако проблема с Sidekiq всё ещё сохраняется.

Старый сайт также работает на PostgreSQL 13 (но существует уже несколько лет, так что скорее всего изначально он не запускался с этой версией :slightly_smiling_face:)

root@discourse-old:/var/discourse# ./launcher enter app
x86_64 arch detected.
root@discourse-app:/var/www/discourse# psql --version
psql (PostgreSQL) 13.5 (Debian 13.5-1.pgdg110+1)

Таким образом, согласно заключительным комментариям в этом посте, база данных Discourse может быть повреждена — и может быть исправлена.

Попробовал с новым пользователем: письмо с регистрацией пришло корректно. Но уведомления на ответы к его постам не приходят — Sidekiq выдаёт ошибку.

Значит, по-моему, Discourse передаёт Sidekiq некорректную информацию, когда instructs его отправлять уведомления (в отличие от регистрационных писем). Как продолжить отладку?

Хорошо, если индексы исправлены, это говорит мне о том, что где-то вызывается функция с массивом вместо модели, у которой есть метод value. Проблема не в самом Sidekiq, а в функции, которую Sidekiq вызывает.

Похоже, что вызывается что-то, что возвращает массив вместо одного элемента, но я не могу точно сказать, что именно. Думаю, вам нужно посмотреть в /var/discourse/shared/standalone/logs/rails/production.log (или в файл с очень похожим путём, если моя память или пальцы меня подводят). Затем вы можете найти эту ошибку в логах (или вызвать её снова, чтобы она появилась в конце файла). Там вы получите больше информации о том, что именно не работает.

Спасибо, но это не очень информативно:

Started POST "/sidekiq/retries" for 185.39.142.187 at 2022-04-11 16:31:35 +0000
start
Started GET "/sidekiq/retries" for 185.39.142.187 at 2022-04-11 16:31:35 +0000
  Rendered email/notification.html.erb (Duration: 42.8ms | Allocations: 4323)
  Rendered layouts/email_template.html.erb (Duration: 0.3ms | Allocations: 29)
Job exception: undefined method `value' for #<Array:0x00007ff393af6c78>
Did you mean?  values_at

fail

Файл shared/standalone/log/rails/production_errors.log пуст.

Отображается ли ошибка в разделе Администрирование → Журналы → Журнал ошибок? Если да, вы можете получить полную трассировку стека, что может помочь.

1 лайк

О, отлично — да, так и есть:

Итак, если я правильно понимаю,

 433    def header_value(name)
 434      header = @message.header[name]
 435      return nil unless header
*436      header.value
 437    end

— в коде Discourse — именно здесь Sidekiq прерывает выполнение?

Это вызывается из …

 228      MessageBuilder.custom_headers(SiteSetting.email_custom_headers).each do |key, _|
*229        value = header_value(key)
 230
 231        # Remove Auto-Submitted header for group private message emails, it does
 232        # not make sense there and may hurt deliverability.

Значит, проблема может быть в пользовательском заголовке?

У меня действительно есть такая запись:

Screenshot 2022-04-12 at 11-27-31 Administration - Freifunk Kreis GT

Я сбросил это значение по умолчанию, и … на самом деле кажется, что уведомления по электронной почте снова отправляются, что подтвердили получатели.

Спасибо!

Однако остаётся один вопрос: почему »Auto-Submitted: auto-generated|Precedence: bulk« вызывает эту ошибку? В инструкции сказано, что пользовательские заголовки должны разделяться символом »|«.

1 лайк

(Отказ от ответственности: я не программист Ruby)

Мне кажется, это особенно неприятное поведение в библиотеке mail, которую использует Discourse. Вот функция header_value:

Насколько я могу судить, @message.header[name] вызывает этот метод:

https://www.rubydoc.info/github/mikel/mail/Mail%2FHeader:[]

Согласно RFC, многие поля могут встречаться более одного раза; мы вернем строку со значением, если заголовок только один, или, если совпадающих заголовков больше одного, вернем массив значений в порядке их появления в заголовке сверху вниз.

Discourse автоматически устанавливает заголовок Precedence, поэтому, поскольку вы также добавляете его через настройку email_custom_headers, теперь есть два заголовка Precedence, и @message.header["Precedence"] возвращает массив вместо строки.

Мне кажется, эта ошибка будет возникать каждый раз, когда в email_custom_headers содержится заголовок, который уже есть в объекте сообщения.

5 лайков

Мне кажется, что происходит именно это (я предположил, что что-то является массивом, а не отдельным элементом, но не мог представить, как это может быть правдой), и это баг.

Я изменю заголовок и категорию этой темы.

3 лайка

Сегодня я объединил исправление для этой проблемы: если мы обнаруживаем дублирующийся заголовок, мы используем тот, который определён в ядре Discourse, а не пользовательский из настроек сайта:

4 лайка

Эта тема была автоматически закрыта через 2 дня. Новые ответы больше не принимаются.