如果存在重复的标头,电子邮件通知将失败

请参阅 Email notifications fail if duplicate headers exist - #14 by simonk 了解 bug 描述
以上由 @pfaffman 添加

原文如下

大家好,

我运行一个小型 Discourse 安装已有数年。流量相当小,因此花了些时间才注意到几个月前电子邮件(通知、摘要)的发送显然开始失败。取证指向 2022-10-22 左右升级到 2.8.0.beta7,之前我们使用的是 2.8.0.beta4。至少我没有收到过来自该安装的关于帖子或消息的任何电子邮件。

电子邮件堆积在 Sidekiq 中,附带一条我无法关联的消息,也找不到任何匹配的搜索结果——有关于 undefined method 消息的报告,但没有一个条件与我的情况相符。(不是 TLS,不是到邮件服务器的超时,不是 events 插件,而且安全媒体修复应该已经到位——此外,确切的错误消息也不同。)

Sidekiq 中的错误:

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

#<Array: 之后的部分对于每个待处理的电子邮件都不同。我昨晚将 Discourse 重装到了一台新虚拟机,并从最新的备份恢复——但似乎电子邮件问题也随着数据一起恢复了 :thinking:

由于错误率在 10 月下旬开始上升,我相当确定这是由 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

这是标准安装吗?如果有一个单独的数据容器,它是否已重建?Postgres 是否已更新?(尽管我认为代码中仍有对此的检查)

是的,这是一个标准安装;据我所知,只有一个容器在运行。(启动一个新虚拟机,重新指向 DNS,apt update,apt dist-upgrade,reboot,git pull,./discourse-setup,基于 Web 的设置,上传备份,恢复备份,重新启用邮件,再次看到错误。)请注意,旧安装能够通过电子邮件发送备份链接,并且在新安装中发送测试电子邮件仍然有效——似乎只有与帖子相关的电子邮件才会失败。

更确切地说,这是一个最小化的 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-\u003e80/tcp, :::80-\u003e80/tcp, 0.0.0.0:443-\u003e443/tcp, :::443-\u003e443/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.rbrestore_dump 中失败,因为 post_id 3841 中存在重复链接。我最终在 2017 年的一个帖子中替换了这些链接,用的是旧安装的截图,然后又进行了一次备份;只有这样,我才能在新安装上恢复备份。正如您提到的 postgres,这发生在 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 的哪个组件给了 Sitekiq 一个“value”方法?

在升级后电子邮件通知停止工作后,Discourse 现在变得相当无用。主题不再能立即得到关注,而是需要几天时间才能得到关注,因为在 2022 年人们不会主动轮询。没有通知 ⇒ 没发生什么 ⇒ 无需检查网站 :frowning:

1 个赞

这看起来像是索引损坏。我不认为我在 PostgreSQL 13 上见过这种情况。听起来您在旧站点上修复了这些问题,然后通过备份和恢复到新站点进行了升级?

看起来问题与发送通知的代码有关,但它与 sidekiq 有关。

如我所说,旧站点正在运行,我进行了备份并将其放在了一个新的安装上,恢复失败了。我修改了被认为是罪魁祸首的帖子,直到在新安装上恢复成功。但之后又遇到了 Sitekiq 的问题。

旧站点也运行 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 发送通知(与注册邮件相反)时,提供了一些错误的信息。如何进一步调试?

好的,所以如果索引被修复了,那么这对我来说就表明有什么东西被调用时传入的是一个数组,而不是一个具有 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 为空。

错误是否显示在 Admin -\u003e Logs -\u003e Error Logs 中?如果显示,您可以获取完整的堆栈跟踪,这可能会有所帮助。

1 个赞

哦,不错——是的,确实如此:

所以,如果我理解正确的话,

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

在 Discourse 代码中,是 Sidekiq 退出(bail out)的地方吗?

这是从……调用的

 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 程序员)

我认为这是 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 个赞

在我看来,这看起来像是正在发生的事情(我建议他们将某项内容视为数组而不是单个项目,但无法想象这怎么会是真的),而且这是一个 bug。

我将更改此主题的标题和类别。

3 个赞

我今天合并了一个修复程序,如果我们检测到重复的标题,我们将使用 Discourse 核心中定义的标题,而不是站点设置中的自定义标题:

4 个赞

此主题已在 2 天后自动关闭。不再允许回复。