SQL 错误:`screened_ip_addresses`(API 返回 500)

大家好,

在调用 API 创建新帖子(在现有主题中)时,我收到了 500 错误。在日志中我看到:

ActiveRecord::StatementInvalid (PG::InvalidTextRepresentation: ERROR:  invalid input syntax for type inet: ""
LINE 1: ..._addresses".* FROM "screened_ip_addresses" WHERE ('' <<= ip_...
                                                             ^
)
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/rack-mini-profiler-2.0.1/lib/patches/db/pg.rb:69:in `exec_params'

Failed to handle exception in exception app middleware : PG::InvalidTextRepresentation: ERROR:  invalid input syntax for type inet: ""
LINE 1: ..._addresses".* FROM "screened_ip_addresses" WHERE ('' <<= ip_...
                                                             ^

这是我的受审查 IP 列表——除了下面的截图外,我还将调用 API 的机器的 IP 加入了白名单。(我使用系统级 API 密钥从旧论坛软件批量导入旧主题/消息。)

出于好奇,我也通过 API 查询了受审查 IP 列表……结果相同。(https://mydiscourse.com/admin/logs/screened_ip_addresses.json)

我不确定还需要检查什么。:man_shrugging:t2:

有人知道:

1. 是什么导致了这个错误,以及
2. 我现在该如何修复它,并防止未来再次发生?

求助 :slight_smile:

谢谢!

或者这更多是关于其他 SQL 语句试图向该表插入数据?

能否发布您用于发起 API 请求的代码(请移除凭据),以便我们了解您是如何进行 API 请求的?

2 个赞

@blake,感谢回复。为了完全确认这不是我代码的问题,我在 Insomnia 中构建了基本的 API 调用(类似 Postman,但在我看来更简单/易用)。不幸的是,结果相同,但至少现在非常明确:

这是我准备的创建新帖子的调用(我已经将“帖子中的最小单词数”设置为 1):

以下是结果

以及这些测试调用在日志中出现的两条错误消息:

错误 1:

消息(报告了 15 份副本)

ActiveRecord::StatementInvalid (PG::InvalidTextRepresentation: 错误:inet 类型的无效输入语法:""
LINE 1: ..._addresses".* FROM "screened_ip_addresses" WHERE ('' <<= ip_...
                                                             ^
)
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/rack-mini-profiler-2.0.1/lib/patches/db/pg.rb:69:in `exec_params'

回溯

rack-mini-profiler-2.0.1/lib/patches/db/pg.rb:69:in `exec_params'
rack-mini-profiler-2.0.1/lib/patches/db/pg.rb:69:in `exec_params'
activerecord-6.0.1/lib/active_record/connection_adapters/postgresql_adapter.rb:672:in `block (2 levels) in exec_no_cache'
activesupport-6.0.1/lib/active_support/dependencies/interlock.rb:48:in `block in permit_concurrent_loads'
activesupport-6.0.1/lib/active_support/concurrency/share_lock.rb:187:in `yield_shares'
activesupport-6.0.1/lib/active_support/dependencies/interlock.rb:47:in `permit_concurrent_loads'
activerecord-6.0.1/lib/active_record/connection_adapters/postgresql_adapter.rb:671:in `block in exec_no_cache'
activerecord-6.0.1/lib/active_record/connection_adapters/abstract_adapter.rb:718:in `block (2 levels) in log'
/usr/local/lib/ruby/2.6.0/monitor.rb:235:in `mon_synchronize'
activerecord-6.0.1/lib/active_record/connection_adapters/abstract_adapter.rb:717:in `block in log'

错误 2:

消息(报告了 15 份副本)

异常应用中间件处理异常失败:PG::InvalidTextRepresentation: 错误:inet 类型的无效输入语法:""
LINE 1: ..._addresses".* FROM "screened_ip_addresses" WHERE ('' <<= ip_...
                                                             ^


回溯

rack-mini-profiler-2.0.1/lib/patches/db/pg.rb:69:in `exec_params'
rack-mini-profiler-2.0.1/lib/patches/db/pg.rb:69:in `exec_params'
activerecord-6.0.1/lib/active_record/connection_adapters/postgresql_adapter.rb:672:in `block (2 levels) in exec_no_cache'
activesupport-6.0.1/lib/active_support/dependencies/interlock.rb:48:in `block in permit_concurrent_loads'
activesupport-6.0.1/lib/active_support/concurrency/share_lock.rb:187:in `yield_shares'
activesupport-6.0.1/lib/active_support/dependencies/interlock.rb:47:in `permit_concurrent_loads'
activerecord-6.0.1/lib/active_record/connection_adapters/postgresql_adapter.rb:671:in `block in exec_no_cache'
activerecord-6.0.1/lib/active_record/connection_adapters/abstract_adapter.rb:718:in `block (2 levels) in log'
/usr/local/lib/ruby/2.6.0/monitor.rb:235:in `mon_synchronize'
activerecord-6.0.1/lib/active_record/connection_adapters/abstract_adapter.rb:717:in `block in log'

我刚刚创建了这个用户,所以我想知道这是否与信任级别有关的 bug,或者是新用户尚未完全授权的其他问题。我会对此进行一些实验,看看能否找出什么能让帖子通过 API 发布。

但无论如何,似乎我确实发现了一个 bug…?!

所以我是这样绕过这个 bug 的……我开始进行实验:

  1. 我将 API 调用中的用户改为 ‘system’,结果成功了。
  2. 于是我心想,嗯,接着把用户改成了一个之前没试过的第三方用户,这也成功了。
  3. 然后我又把用户改回了原始用户名,这意味着我实际上是在重复之前导致 500 错误的那个完全相同的 API 调用。但这次却成功了。:man_shrugging:t2:

如果问题再次出现,我会看看能否找到可靠复现的方法。在此期间,也许有比我更熟悉 Discourse 代码的人 :wink: 可以帮忙看一下那部分代码,确认是否有明显的 bug 跳出来。

1 个赞

嗯,这里肯定有些不对劲。我又运行了我的脚本,它开始批量创建话题并发布消息……这一次,换了一个不同的用户,我又遇到了 500 错误。

我用 Insomnia 测试该用户名对应的 API……果然,返回 500 错误。我切换到 ‘system’ 用户,请求就成功了;再换回原来的用户名,又出现 500 错误!

抛出 500 错误的用户(以及除 ‘system’ 外所有我导入的用户)都是 TL1 级别。我已经调整了限制,让 TL1 可以发帖,但问题依旧。

这感觉确实和某种速率限制有关?我原本怀疑是 nginx 或上游某个组件导致的 500 错误,但我已经移除了 nginx 中的所有速率限制,而错误日志中明确显示该 500 错误与那个 SQL bug 相关。

我注意到 SQL 错误似乎发生在引用 IP 地址的列附近……有没有可能是速率限制的工作方式存在某些异常,从而引发这个问题?我尝试通过 VPN 登录(以更改我的 IP),但依然收到 500 错误。

与此同时,我发现问题出在 rack-mini-profiler 上,而这个组件其实并不需要。让我试试把它关闭,看看能否解决问题……不行。现在我的管理员账号上不再显示 mini-profiler,但我仍然收到相同的 HTTP 500 错误,错误日志中的错误信息也完全一样::disappointed_face:

更新:将用户级别改为 TL3 后,问题似乎消失了;但切换回 TL1 时,问题又出现了。我正在验证这个假设……不行。有时这样能行,但有时即使我手动修改用户的 TL 级别,该用户似乎仍然“卡住”了。

@blake@staff 的其他成员……求助! :wink: 还有什么可以尝试的吗?有没有什么我可以完全清除(与抛出错误的代码相关)并重置为出厂设置的?

1 个赞

根据堆栈跟踪,这看起来像是一个可能的网络问题,有时用户的 IP 地址似乎无法被检测到?或者管理员日志、被屏蔽的 IP 地址中存在数据库数据错误?

1 个赞

同意——<<= 表示“左侧包含在右侧内”,因此看起来是一个无效/空的 IP 地址被传递到了应用程序。

(我很好奇为什么应用程序没有任何可用的 IP 地址;我唯一的猜测是请求是通过 Unix 套接字传入的,且没有包含转发 IP 信息的头部)

2 个赞

同意,但我无法理解为什么 IP 地址有时存在,有时却不存在。在我的导入脚本中,在至少 5 到 7 次成功的 API 调用之后,我收到了这个 500 错误。这就是为什么我认为问题可能出在数据库中的损坏数据上。

是的,这很奇怪。但与此同时,如果我使用一个“无效”的用户名调用,它会失败;而使用“system”或其他用户名调用,则能成功。所以我认为我们可能是在指向数据库中的损坏数据。

有人能给我一些简单的 Ruby 命令行指令吗?我想用来清除或重置任何可能出错的数据库表。

我认为你的数据库本身没有问题,因为错误是在检查“被屏蔽 IP 地址表”中是否存在空白 IP 地址,而不是检查你的数据库中是否存在空的 IP 地址。

不过有趣的是,这段代码在发出导致 500 错误的 SQL 请求之前,会先检查 ip_address 是否存在。

你能描述一下你的 Discourse 实例是如何部署的吗?是通过导入设置的吗?你使用的是最新版本吗?它有多少内存?你遵循了这份指南吗?

你能检查一下这个站点设置吗:max new accounts per registration ip(每个注册 IP 允许的最大新账户数)?我不确定这在此情况下是否重要,但也许它导致了问题。

在你的批量脚本中,能否降低执行速度,在每次请求之间增加 1 秒的暂停,看看是否有改善?

这些 API 调用的目的是什么?如果这是一个用于构建导入用户和帖子的单次脚本,那么也许在服务器上直接运行的导入脚本(不通过 API 调用)会更好?

抱歉问了这么多问题,但我们之前从未遇到过这个问题,而且关于 screened_ip_addresses 的代码已经很久没有更新了。我并不是说代码库中不存在 bug,但经过快速查看,目前还没有发现明显的问题。

当然——完全是基于 Docker,部署在 Digital Ocean 上。我严格按照那份优秀的指南操作。

好的,我刚刚将该设置从默认的 3 改为了 99999。没有变化,仍然出现 500 错误。

试过了,没有区别。请注意,即使只有一个“坏”账户,通过 Insomnia 调用,我仍然会得到 500 错误。所以目前看来,就像那个账户被“污染”了一样,即使我只用该账户进行一次“创建消息”的 API 调用(之前或之后都没有其他调用),我仍然会得到 500 错误。不过,我的导入脚本也确实遇到了 500 错误;-)

是的,我是一名经验丰富的程序员,但完全不懂 RoR/Ruby,所以我无法直接使用你们提供的现成选项,尽管我承认它们很可能比我手动遍历现有论坛并通过 API 实时创建用户等方式更优越。因此才有了我的市场帖子……我当然希望能自己把所有问题都解决,但我也有一个严格的截止日期;-)

完全理解,也很感谢你对此事的关注。

所以,我可以提供一些可能非常有帮助的信息:既然这是一个现成的安装,我没有进行任何自定义,而且这个 bug 很容易复现(无需我的代码,只需使用 Insomnia 即可),并且我还没有正式启用论坛,我可以把 Digital Ocean 实例的 root 登录信息、我的 API 密钥等提供给你,我完全没问题让你在那里进行任何操作。我的 Discourse 论坛目前只有一些空分类和几条我们设置的特殊欢迎消息,基本上还是空的,还没有真实用户(只有管理员)。所以,如果你需要测试,创建或删除主题和消息等,都没问题。

这绝对是最快的方式,让你能亲眼看到并验证这个 bug。而且,既然你会以 root 身份进入系统,你也可以随意操作任何底层的 Discourse 设置,以查明问题所在。

E

1 个赞

如果禁用单点登录(SSO),会发生什么?

1 个赞

没有区别,仍然抛出 500 错误。

另一个想法:是否所有已生效的账户都是管理员?我知道某些应用级别的速率限制对管理员是绕过的。

不过,一般来说,直接编写一个导入脚本要容易得多。整个这个讨论就是“很多”的体现 :wink:

嗯,我以为我找到了一些线索……我的测试“污染”了问题用户 george21,他的信任等级是 TL0。于是我把他改成了 TL1,然后它就工作了。好的!也许就是这个原因!然后我又把 george21 改回 TL0……结果他不再被“污染”了——即使作为 TL0 用户,他也能成功调用 API。

现在我再次运行导入脚本,果然,george21 在导入脚本中抛出了 500 错误。当我在 Insomnia 中尝试时,也失败了。于是我把 george21 重新设回 TL1……是的,他现在可以执行 HTTP 调用了。

所以,我似乎能够复现以下现象:

  1. 如果进行一系列 API 调用(?),这 somehow 会导致后续某个 TL0 用户的 API 调用失败。
  2. 将 TL0 用户改为 TL1 后,API 调用就能通过。
  3. 奇怪的是,将同一个用户再次改回 TL0 后,API 调用仍然能通过。
  4. 再次运行脚本没问题,直到它又一次因另一个 TL0 用户而失败。

请注意:

  1. 据我所知,TL0 的所有最低要求等都已提高(即我已尝试移除所有会阻止 TL0 用户发帖的限制),而且
  2. 即使这是 TL0 用户某种内部速率限制的问题,API 也不应该抛出 500 错误并在错误日志中记录 SQL 错误。因此,我们现在可以确定,其中肯定存在某个 bug。

是啊,嗯,我知道,而且我已经基于给出的示例解释过为什么我不自己写导入脚本,来回说了四次了。:wink: 因此我改变了方法

与此同时,我将继续在这里贡献,以帮助发现和修复这个 bug。今天它影响了我的导入脚本,明天它可能会影响你网站上某个需要调用 API 的重要脚本……

1 个赞