Notificações por e-mail falham se houver cabeçalhos duplicados

Veja Email notifications fail if duplicate headers exist - #14 by simonk para a descrição do bug
Adicionado acima por @pfaffman

Texto original a seguir

Olá,

Estou executando uma instalação do Discourse bastante pequena há alguns anos. É de tráfego razoavelmente baixo, por isso demorou um pouco para notar que o envio de e-mails (notificações, resumos) obviamente começou a falhar há alguns meses. Forense aponta para a atualização para 2.8.0.beta7 por volta de 22/10/2022, anteriormente estávamos na 2.8.0.beta4. Pelo menos eu não recebi nenhum e-mail dessa instalação sobre postagens ou mensagens.

Os e-mails se acumulam no Sidekiq, com uma mensagem que não consigo relacionar, nem encontrar nada que se encaixe ao pesquisar por ela — houve relatos de mensagens undefined method, mas nenhuma das condições corresponde à minha. (Não é TLS, não é timeout para o servidor de e-mail, não é o plugin de eventos, e a correção de mídia segura já deve ter sido aplicada — além disso, as mensagens de erro exatas eram diferentes.)

Erro no Sidekiq:

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

A parte após #<Array: é diferente para cada e-mail retido. Reinstalei o Discourse em uma nova VM ontem à noite e restaurei de um backup recente — mas aparentemente o problema de e-mail foi restaurado com os dados :thinking:

Como a taxa de erro começou a subir no final de outubro, tenho bastante certeza de que isso foi introduzido pela 2.8.0.beta7:

Qualquer ajuda, ou dica para depurar o problema ainda mais, é muito apreciada.

1 curtida

Você tem algum plugin instalado? Se você tiver plugins não padrão, deverá removê-los.

Já tentei isso, o problema persiste :frowning:

## Plugins vão aqui
## veja https://meta.discourse.org/t/19157 para detalhes
hooks:
  after_code:
    - exec:
        cd: $home/plugins
        cmd:
          - git clone https://github.com/discourse/docker_manager.git

## Quaisquer comandos personalizados para executar após a compilação

EDIT: Para constar:

Plugins pretendidos:

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

A instalação antiga tem:

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

Esta é uma instalação padrão? Se você tem um contêiner de dados separado, ele foi reconstruído? O postgres está atualizado? (Embora eu ache que ainda há uma verificação para isso no código)

Sim, é uma instalação padrão; Pelo que vejo, há apenas um contêiner em execução. (Iniciar uma nova VM, redirecionar DNS, apt update, apt dist-upgrade, reiniciar, git pull, ./discourse-setup, Configuração baseada na web, carregar backup, restaurar backup, reativar e-mail, ver os erros novamente.) Observe que a instalação antiga conseguiu enviar o link do backup por e-mail, e o envio de e-mails de teste ainda funciona na nova instalação — aparentemente, apenas os e-mails relacionados a postagens falham.

Para ser mais preciso, é uma instalação mínima do Debian 10, na qual instalei o 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

E um contêiner:

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-\u003e80/tcp, :::80-\u003e80/tcp, 0.0.0.0:443-\u003e443/tcp, :::443-\u003e443/tcp   app

Editar:

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.

Na verdade, a primeira restauração falhou. database_restorer.rb falhou em restore_dump devido a links duplicados no post_id 3841. Acabei substituindo esses links em um post de 2017 por uma captura de tela deles na instalação antiga e fazendo outro backup; só então consegui restaurar o backup na nova instalação. Como você mencionou o postgres, isso aconteceu durante CREATE INDEX com ERROR: could not create unique index \"unique_post_links\". Mais informações: EXCEPTION: psql failed: DETAIL: Key (topic_id, post_id, url)=(1300, 3841, [redacted]) is duplicated.

Embora eu não ache que isso esteja diretamente relacionado, pensei em mencionar.

Portanto, se ninguém souber uma maneira fácil de consertar isso, vamos depurar isso corretamente. Mas preciso da sua ajuda, pois o Discourse é uma aplicação bastante complexa que usa um monte de tecnologias com as quais não estou familiarizado. Então: Como os e-mails são “enviados” para o Sidekiq?

Qual componente do Discourse está dando ao Sidekiq um método “value”?

Com as notificações por e-mail parando de funcionar após uma atualização, o Discourse é infelizmente bastante inútil agora. Em vez de atenção imediata, os tópicos agora levam dias para receber atenção, se houver, pois as pessoas não fazem polling ativo em 2022. Sem notificação ⇒ nada aconteceu ⇒ não há necessidade de verificar o site :frowning:

1 curtida

Isso parece um índice corrompido. Não acho que já vi isso no postgres 13. Parece que você corrigiu isso em um site antigo e fez o upgrade fazendo backup e restaurando para um novo site?

Parece que o problema tem a ver com algo no código que está enviando notificações, mas é um problema com o sidekiq.

Como eu disse, o site antigo está funcionando, fiz um backup e coloquei isso em uma instalação nova, a restauração falhou. Modifiquei a postagem notada como a culpada até que a restauração na nova instalação funcionasse. Apenas para ver que o problema com o Sitekiq persistia/ainda estava lá.

O site antigo também executa o postgres 13 (mas remonta a vários anos, então é mais provável que não tenha começado com essa versão :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)

Então, de acordo com os comentários de encerramento desta postagem, o Banco de Dados do Discourse pode ficar corrompido — e ser corrigido.

Tentei com um novo usuário, ele recebe o e-mail de registro corretamente. Mas notificações sobre respostas às suas postagens, não; o Sidekiq falha.

Então, para mim, isso significa que o Discourse está fornecendo informações incorretas ao Sidekiq quando o instrui a enviar Notificações (em contraste com o e-mail de registro). Como depurar mais?

OK, então se os índices forem reparados, isso sugere que algo está sendo chamado com um array em vez de um modelo que tenha value. O problema não é com o sidekiq, em si, mas com a função que o sidekiq está causando para ser chamada.

Então, parece que algo está sendo chamado que está retornando um array em vez de um único item, mas não consigo adivinhar o quê. Acho que você precisará olhar em /var/discourse/shared/standalone/logs/rails/production.log (ou algo muito parecido se meus dedos ou memória estiverem falhando). Então você pode procurar por esse erro nesses logs (ou fazê-lo acontecer novamente para que ele fique no final do arquivo). Você obterá mais informações sobre o que está falhando lá.

Obrigado, mas na verdade não está dizendo muito:

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 está vazio.

O erro aparece em Admin -\u003e Logs -\u003e Registros de Erro? Se sim, você pode obter um rastreamento de pilha completo que pode ajudar.

1 curtida

Ah, legal — sim, ele faz:

Então, se entendi corretamente,

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

é – no código do Discourse? – onde o Sidekiq falha?

Isso é chamado de…

 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.

Então poderia ser um cabeçalho personalizado?

Eu realmente tenho uma entrada lá:

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

Eu redefini isso para os padrões e… na verdade, parece que as notificações por e-mail estão sendo enviadas novamente, conforme verificado pelos leitores.

Obrigado!

Resta uma pergunta: por que »Auto-Submitted: auto-generated|Precedence: bulk« está causando essa falha? Ele afirma que cabeçalhos personalizados devem ser separados por »|«.

1 curtida

(Aviso: não sou programador Ruby)

Acho que este é um comportamento particularmente desagradável na biblioteca de e-mail que o Discourse está usando. Aqui está a função header_value:

Pelo que entendi, @message.header[name] está chamando este método:

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

Conforme a RFC, muitos campos podem aparecer mais de uma vez, retornaremos uma string do valor se houver apenas um cabeçalho, ou se houver mais de um cabeçalho correspondente, retornaremos um array de valores na ordem em que aparecem no cabeçalho, ordenados de cima para baixo.

O Discourse define automaticamente um cabeçalho Precedence, então, como você também está adicionando um através da configuração email_custom_headers, agora existem dois cabeçalhos Precedence, e @message.header[\"Precedence\"] está retornando um array em vez de uma string.

Acho que este bug será acionado sempre que email_custom_headers contiver um cabeçalho que já existe no objeto de mensagem.

5 curtidas

Para mim, isso parece ser o que está acontecendo (sugeri que algo era um array em vez de um único item, mas não conseguia imaginar como isso poderia ser verdade) e é um bug.

Mudarei o título e a categoria deste tópico.

3 curtidas

Eu mesclei uma correção para isso hoje, se detectarmos um cabeçalho duplicado, usaremos apenas o definido no núcleo do Discourse em vez do personalizado da configuração do site:

4 curtidas

Este tópico foi fechado automaticamente após 2 dias. Novas respostas não são mais permitidas.