Les notifications par e-mail échouent si des en-têtes en double existent

Voir Email notifications fail if duplicate headers exist - #14 by simonk pour la description du bug
Ajouté ci-dessus par @pfaffman

Texte original ci-dessous

Bonjour,

J’utilise une installation Discourse assez petite depuis quelques années. Le trafic est assez faible, il a donc fallu un certain temps pour remarquer que l’envoi d’e-mails (notifications, résumés) avait manifestement cessé de fonctionner il y a quelques mois. Les analyses indiquent une mise à niveau vers la version 2.8.0.beta7 vers le 22-10-2022, nous étions auparavant sur la version 2.8.0.beta4. Au moins, je n’ai reçu aucun e-mail de cette installation concernant des publications ou des messages.

Les e-mails s’accumulent dans Sidekiq, avec un message que je ne parviens pas à relier, ni à trouver quoi que ce soit d’adapté en cherchant – il y a eu des rapports de messages undefined method, mais aucune des conditions ne correspond à la mienne. (Ce n’est pas TLS, ce n’est pas un délai d’attente vers le serveur de messagerie, ce n’est pas le plugin d’événements, et le correctif pour les médias sécurisés devrait déjà être appliqué – de plus, les messages d’erreur exacts étaient différents.)

Erreur dans Sidekiq :

Wrapped NoMethodError: undefined method `value' for #\u003cArray:0x00007f7fd5277d68\u003e Did you mean? values_at

La partie après #\u003cArray: est différente pour chaque e-mail conservé. J’ai réinstallé Discourse sur une nouvelle VM hier soir et restauré à partir d’une sauvegarde récente – mais apparemment, le problème d’e-mail a été restauré avec les données :thinking:

Comme le taux d’erreur a commencé à augmenter fin octobre, je suis presque sûr que cela a été introduit par la version 2.8.0.beta7 :

Toute aide, ou indice pour déboguer davantage le problème, est très appréciée.

1 « J'aime »

Avez-vous des plugins installés ? Si vous avez des plugins non standard, vous devriez les supprimer.

J’ai déjà essayé, le problème persiste :frowning:

## Les plugins vont ici
## voir https://meta.discourse.org/t/19157 pour les détails
hooks:
  after_code:
    - exec:
        cd: $home/plugins
        cmd:
          - git clone https://github.com/discourse/docker_manager.git

## Toutes les commandes personnalisées à exécuter après la compilation

EDIT : Pour information :

Plugins prévus :

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

L’ancienne installation a :

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

Est-ce une installation standard ? Si vous avez un conteneur de données séparé, a-t-il été reconstruit ? PostgreSQL est-il à jour ? (Bien que je pense qu’il y ait toujours une vérification pour cela dans le code)

Oui, c’est une installation standard ; d’après ce que je vois, un seul conteneur est en cours d’exécution. (Démarrer une nouvelle VM, rediriger le DNS, apt update, apt dist-upgrade, redémarrer, git pull, ./discourse-setup, configuration via le web, télécharger la sauvegarde, restaurer la sauvegarde, réactiver le courrier, voir à nouveau les erreurs.) Notez que l’ancienne installation a pu envoyer le lien de la sauvegarde par e-mail, et l’envoi d’e-mails de test fonctionne toujours dans la nouvelle installation — seuls les e-mails liés aux publications semblent échouer.

Pour être plus précis, il s’agit d’une installation minimale de Debian 10, sur laquelle j’ai installé 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

Et un conteneur :

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

Modification :

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.

Les premières restaurations ont en fait échoué, cependant. database_restorer.rb a échoué dans restore_dump en raison de liens dupliqués dans le post_id 3841. J’ai fini par remplacer ces liens dans un post de 2017 par une capture d’écran d’eux sur l’ancienne installation et par faire une autre sauvegarde ; ce n’est qu’alors que j’ai pu restaurer la sauvegarde sur la nouvelle installation. Comme vous avez mentionné postgres, cela s’est produit lors de CREATE INDEX avec ERROR: could not create unique index \"unique_post_links\". Informations supplémentaires : EXCEPTION: psql failed: DETAIL: Key (topic_id, post_id, url)=(1300, 3841, [redacted]) is duplicated.

Bien que je ne pense pas que ce soit directement lié, j’ai pensé qu’il était bon de le mentionner.

Alors, si personne ne connaît de moyen simple de résoudre ce problème, déboguons-le correctement. Mais j’ai besoin de votre aide, car Discourse est une application plutôt complexe utilisant un tas de technologies avec lesquelles je ne suis pas familier. Alors : Comment les e-mails sont-ils « envoyés » à Sidekiq ?

Quel composant de Discourse donne à Sitekiq une méthode « value » ?

Avec les notifications par e-mail qui cessent de fonctionner après une mise à niveau, Discourse est malheureusement plutôt inutile maintenant. Au lieu d’une attention immédiate, les sujets prennent maintenant des jours pour attirer l’attention, le cas échéant, car les gens ne font pas de sondages actifs en 2022. Aucune notification ⇒ rien ne s’est passé ⇒ pas besoin de vérifier le site :frowning:

1 « J'aime »

Cela ressemble à un index corrompu. Je ne pense pas avoir vu cela sur postgres 13. On dirait que vous avez corrigé cela sur un ancien site et que vous avez mis à niveau en sauvegardant et en restaurant vers un nouveau site ?

Il semble que le problème ait à voir avec quelque chose dans le code qui envoie des notifications, mais c’est un problème avec sidekiq.

Comme je l’ai dit, l’ancien site fonctionne, j’ai fait une sauvegarde et l’ai mise sur une nouvelle installation, la restauration a échoué. J’ai modifié le post identifié comme coupable jusqu’à ce que la restauration sur la nouvelle installation fonctionne. Seulement pour voir avoir le problème avec Sitekiq à nouveau/toujours.

L’ancien site exécute également postgres 13 (mais remonte à plusieurs années, donc il n’a très probablement pas commencé avec cette version :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)

Donc, selon les commentaires de clôture de ce post, la base de données de Discourse peut être corrompue — et être réparée.

J’ai essayé avec un nouvel utilisateur, il reçoit correctement son e-mail d’inscription. Mais les notifications sur les réponses à ses posts, non ; Sidekiq génère une erreur.

Donc, pour moi, cela signifie que Discourse donne des informations erronées à Sidekiq lorsqu’il lui demande d’envoyer des notifications (par opposition aux e-mails d’inscription). Comment déboguer davantage ?

OK, donc si les index sont réparés, cela me suggère que quelque chose est appelé avec un tableau plutôt qu’un modèle qui a value. Le problème n’est pas avec sidekiq, en soi, mais avec la fonction que sidekiq provoque pour être appelée.

Donc, il semble que quelque chose soit appelé qui renvoie un tableau plutôt qu’un seul élément, mais je ne peux pas deviner quoi. Je pense que vous devrez regarder dans /var/discourse/shared/standalone/logs/rails/production.log (ou quelque chose de très similaire si mes doigts ou ma mémoire me font défaut). Ensuite, vous pourrez rechercher cette erreur dans ces journaux (ou la provoquer à nouveau pour qu’elle se retrouve à la fin du fichier). Vous obtiendrez plus d’informations sur ce qui échoue là-bas.

Merci, mais cela ne dit pas grand-chose :

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 vide.

L’erreur s’affiche-t-elle dans Admin -\u003e Journaux -\u003e Journaux d’erreurs ? Si oui, vous pouvez obtenir une trace complète de la pile qui pourrait aider.

1 « J'aime »

Oh, sympa — oui, c’est le cas :

Donc, si je comprends bien,

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

est – dans le code de Discourse ? – là où Sidekiq abandonne ?

Ceci est appelé depuis…

 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.

Donc, cela pourrait être un en-tête personnalisé ?

J’ai effectivement une entrée là :
Screenshot 2022-04-12 at 11-27-31 Administration - Freifunk Kreis GT

J’ai réinitialisé cela aux valeurs par défaut, et… en fait, il semble que les notifications par e-mail soient bien envoyées à nouveau, comme l’ont vérifié les lecteurs.

Merci !

Une question demeure, cependant : pourquoi « Auto-Submitted: auto-generated|Precedence: bulk » cause-t-il cet échec ? Il est indiqué que les en-têtes personnalisés doivent être séparés par « | ».

1 « J'aime »

(Avertissement : pas un programmeur Ruby)

Je pense que c’est un comportement particulièrement désagréable dans la bibliothèque d’e-mails que Discourse utilise. Voici la fonction header_value :

Pour autant que je puisse en juger, @message.header[name] appelle cette méthode :

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

Conformément aux RFC, de nombreux champs peuvent apparaître plus d’une fois. Nous retournerons une chaîne de caractères de la valeur s’il n’y a qu’un seul en-tête, ou s’il y a plus d’un en-tête correspondant, nous retournerons un tableau de valeurs dans l’ordre où elles apparaissent dans l’en-tête, ordonnées de haut en bas.

Discourse définit automatiquement un en-tête Precedence, donc parce que vous en ajoutez un également via le paramètre email_custom_headers, il y a maintenant deux en-têtes Precedence, et @message.header[\"Precedence\"] renvoie un tableau au lieu d’une chaîne de caractères.

Je pense que ce bug sera déclenché chaque fois que email_custom_headers contiendra un en-tête qui existe déjà sur l’objet message.

5 « J'aime »

Voici ce qui me semble se passer (j’ai suggéré que quelque chose était un tableau au lieu d’un élément unique, mais je n’arrivais pas à imaginer comment cela pouvait être vrai) et c’est un bug.

Je vais changer le titre et la catégorie de ce sujet.

3 « J'aime »

J’ai fusionné un correctif pour cela aujourd’hui. Si nous détectons un en-tête en double, nous utilisons simplement celui défini dans le cœur de Discourse plutôt que celui personnalisé du paramètre du site :

4 « J'aime »

Ce sujet a été automatiquement fermé après 2 jours. Les nouvelles réponses ne sont plus autorisées.