wusel
(Kai 'wusel' Siering)
2022 年4 月 2 日 21:33
1
请参阅 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 重装到了一台新虚拟机,并从最新的备份恢复——但似乎电子邮件问题也随着数据一起恢复了
由于错误率在 10 月下旬开始上升,我相当确定这是由 2.8.0.beta7 引入的:
任何帮助或进一步调试问题的提示都将不胜感激。
1 个赞
pfaffman
(Jay Pfaffman)
2022 年4 月 3 日 01:08
2
您是否安装了任何插件?如果您有非标准插件,则应将其删除。
wusel
(Kai 'wusel' Siering)
2022 年4 月 3 日 01:25
3
已经试过了,问题仍然存在
## 插件放在这里
## 详情请参阅 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
pfaffman
(Jay Pfaffman)
2022 年4 月 3 日 08:55
4
这是标准安装吗?如果有一个单独的数据容器,它是否已重建?Postgres 是否已更新?(尽管我认为代码中仍有对此的检查)
wusel
(Kai 'wusel' Siering)
2022 年4 月 3 日 09:42
5
是的,这是一个标准安装;据我所知,只有一个容器在运行。(启动一个新虚拟机,重新指向 DNS,apt update,apt dist-upgrade,reboot,git pull,./discourse-setup,基于 Web 的设置,上传备份,恢复备份,重新启用邮件,再次看到错误。)请注意,旧安装能够通过电子邮件发送备份链接,并且在新安装中发送测试电子邮件仍然有效——似乎只有与帖子相关的电子邮件才会失败。
wusel
(Kai 'wusel' Siering)
2022 年4 月 3 日 10:04
6
更确切地说,这是一个最小化的 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
编辑:
pfaffman:
postgres 是否已更新?
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 年的一个帖子中替换了这些链接,用的是旧安装的截图,然后又进行了一次备份;只有这样,我才能在新安装上恢复备份。正如您提到的 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。
虽然我不认为这直接相关,但我想还是提一下。
wusel
(Kai 'wusel' Siering)
2022 年4 月 10 日 23:53
7
所以,如果没人知道如何轻松修复它,让我们来好好调试一下。但我需要你们的帮助,因为 Discourse 是一个相当复杂的应用程序,使用了许多我不熟悉的技术。那么:邮件是如何“发送到”Sidekiq 的?
Discourse 的哪个组件给了 Sitekiq 一个“value”方法?
在升级后电子邮件通知停止工作后,Discourse 现在变得相当无用。主题不再能立即得到关注,而是需要几天时间才能得到关注,因为在 2022 年人们不会主动轮询。没有通知 ⇒ 没发生什么 ⇒ 无需检查网站
1 个赞
pfaffman
(Jay Pfaffman)
2022 年4 月 11 日 01:03
8
wusel:
EXCEPTION: psql failed: DETAIL: Key (topic_id, post_id, url)=(1300, 3841, [redacted]) is duplicated .
这看起来像是索引损坏。我不认为我在 PostgreSQL 13 上见过这种情况。听起来您在旧站点上修复了这些问题,然后通过备份和恢复到新站点进行了升级?
看起来问题与发送通知的代码有关,但它与 sidekiq 有关。
wusel
(Kai 'wusel' Siering)
2022 年4 月 11 日 16:17
9
如我所说,旧站点正在运行,我进行了备份并将其放在了一个新的安装上,恢复失败了。我修改了被认为是罪魁祸首的帖子,直到在新安装上恢复成功。但之后又遇到了 Sitekiq 的问题。
旧站点也运行 PostgreSQL 13(但可以追溯到几年前,所以它很可能不是从那个版本开始的 )
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 发送通知(与注册邮件相反)时,提供了一些错误的信息。如何进一步调试?
pfaffman
(Jay Pfaffman)
2022 年4 月 11 日 16:26
10
好的,所以如果索引被修复了,那么这对我来说就表明有什么东西被调用时传入的是一个数组,而不是一个具有 value 的模型。问题本身不在于 sidekiq,而在于 sidekiq 导致被调用的那个函数。
所以听起来像是某个东西被调用了,但它返回的是一个数组而不是单个项目,但我猜不到具体是哪个。我认为你需要查看 /var/discourse/shared/standalone/logs/rails/production.log(如果我的手指或记忆出了问题,可能会是类似的文件)。然后在这些日志中查找那个错误(或者让它再次发生,这样它就会在文件末尾)。你应该能从中获得更多关于什么出错了的信息。
wusel
(Kai 'wusel' Siering)
2022 年4 月 11 日 16:33
11
不过,这并没有提供太多信息:
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 为空。
simonk
(Simon King)
2022 年4 月 12 日 08:49
12
错误是否显示在 Admin -\u003e Logs -\u003e Error Logs 中?如果显示,您可以获取完整的堆栈跟踪,这可能会有所帮助。
1 个赞
wusel
(Kai 'wusel' Siering)
2022 年4 月 12 日 10:08
13
哦,不错——是的,确实如此:
所以,如果我理解正确的话,
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.
所以可能是自定义标头?
我确实有一个条目在那里:
我已经将其重置为默认值,并且……实际上,电子邮件通知似乎确实又在发送了,读者已经验证了这一点。
谢谢!
不过还有一个问题:为什么“Auto-Submitted: auto-generated|Precedence: bulk”会导致此失败?它说明自定义标头应以“|”分隔。
1 个赞
simonk
(Simon King)
2022 年4 月 12 日 10:37
14
(免责声明:不是 Ruby 程序员)
我认为这是 Discourse 使用的邮件库中特别糟糕的行为。这是 header_value 函数:
# a bunch of characters not render correctly in the email
part content_type: "text/html; charset=utf-8", body: html_part.body.decoded
part content_type: "text/plain; charset=utf-8", body: text_part.body.decoded
end
@message.parts.unshift(content)
end
end
def header_value(name)
header = @message.header[name]
return nil unless header
header.value
end
def skip(reason_type, custom_reason: nil)
attributes = {
email_type: @email_type,
to_address: to_address,
user_id: @user&.id,
reason_type: reason_type
据我所知,@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 个赞
pfaffman
(Jay Pfaffman)
2022 年4 月 12 日 10:52
15
在我看来,这看起来像是正在发生的事情(我建议他们将某项内容视为数组而不是单个项目,但无法想象这怎么会是真的),而且这是一个 bug。
我将更改此主题的标题和类别。
3 个赞
martin
(Martin Brennan)
2022 年5 月 11 日 03:47
25
我今天合并了一个修复程序,如果我们检测到重复的标题,我们将使用 Discourse 核心中定义的标题,而不是站点设置中的自定义标题:
main ← issue/do-not-use-custom-headers-outbound-override-mail
opened 01:18AM - 11 May 22 UTC
When we build and send emails using MessageBuilder and Email::Sender
we add cus… tom headers defined in SiteSetting.email_custom_headers.
However this was causing errors in cases where the custom headers
defined a header that we already specify in outbound emails (e.g.
the Precedence: list header for topic/post emails).
This commit makes it so we always use the header value defined in Discourse
core if there is a duplicate, discarding the custom header value
from the site setting.
cf. https://meta.discourse.org/t/email-notifications-fail-if-duplicate-headers-exist/222960/14
4 个赞