Sidekiq 在 Docker 开发环境中启用 Zeitwerk 后无法启动

我们最近从 2.4.0.beta4 升级到了 2.4.0.beta5,但 Sidekiq 无法启动,并出现以下堆栈跟踪:

> $ bundle exec sidekiq -C config/sidekiq.yml
> uninitialized constant SiteSetting::SiteSettingExtension
> /discourse/app/models/site_setting.rb:5:in `<class:SiteSetting>'
> /discourse/app/models/site_setting.rb:3:in `<top (required)>'
> /discourse/vendor/bundle/ruby/2.5.0/gems/zeitwerk-2.1.10/lib/zeitwerk/kernel.rb:23:in `require'
> /discourse/vendor/bundle/ruby/2.5.0/gems/zeitwerk-2.1.10/lib/zeitwerk/kernel.rb:23:in `require'
> /discourse/vendor/bundle/ruby/2.5.0/gems/activesupport-6.0.0/lib/active_support/dependencies/interlock.rb:14:in `block in loading'
> /discourse/vendor/bundle/ruby/2.5.0/gems/activesupport-6.0.0/lib/active_support/concurrency/share_lock.rb:151:in `exclusive'
> /discourse/vendor/bundle/ruby/2.5.0/gems/activesupport-6.0.0/lib/active_support/dependencies/interlock.rb:13:in `loading'
> /discourse/config/initializers/004-message_bus.rb:120:in `<top (required)>'
> /discourse/vendor/bundle/ruby/2.5.0/gems/railties-6.0.0/lib/rails/engine.rb:667:in `block in load_config_initializer'
> /discourse/vendor/bundle/ruby/2.5.0/gems/activesupport-6.0.0/lib/active_support/notifications.rb:182:in `instrument'
> /discourse/vendor/bundle/ruby/2.5.0/gems/railties-6.0.0/lib/rails/engine.rb:666:in `load_config_initializer'
> /discourse/vendor/bundle/ruby/2.5.0/gems/railties-6.0.0/lib/rails/engine.rb:624:in `block (2 levels) in <class:Engine>'
> /discourse/vendor/bundle/ruby/2.5.0/gems/railties-6.0.0/lib/rails/engine.rb:623:in `each'
> /discourse/vendor/bundle/ruby/2.5.0/gems/railties-6.0.0/lib/rails/engine.rb:623:in `block in <class:Engine>'
> /discourse/vendor/bundle/ruby/2.5.0/gems/railties-6.0.0/lib/rails/initializable.rb:32:in `instance_exec'
> /discourse/vendor/bundle/ruby/2.5.0/gems/railties-6.0.0/lib/rails/initializable.rb:32:in `run'
> /discourse/vendor/bundle/ruby/2.5.0/gems/railties-6.0.0/lib/rails/initializable.rb:61:in `block in run_initializers'
> /usr/local/lib/ruby/2.5.0/tsort.rb:228:in `block in tsort_each'
> /usr/local/lib/ruby/2.5.0/tsort.rb:350:in `block (2 levels) in each_strongly_connected_component'
> /usr/local/lib/ruby/2.5.0/tsort.rb:422:in `block (2 levels) in each_strongly_connected_component_from'
> /usr/local/lib/ruby/2.5.0/tsort.rb:431:in `each_strongly_connected_component_from'
> /usr/local/lib/ruby/2.5.0/tsort.rb:421:in `block in each_strongly_connected_component_from'
> /discourse/vendor/bundle/ruby/2.5.0/gems/railties-6.0.0/lib/rails/initializable.rb:50:in `each'
> /discourse/vendor/bundle/ruby/2.5.0/gems/railties-6.0.0/lib/rails/initializable.rb:50:in `tsort_each_child'
> /usr/local/lib/ruby/2.5.0/tsort.rb:415:in `call'
> /usr/local/lib/ruby/2.5.0/tsort.rb:415:in `each_strongly_connected_component_from'
> /usr/local/lib/ruby/2.5.0/tsort.rb:349:in `block in each_strongly_connected_component'
> /usr/local/lib/ruby/2.5.0/tsort.rb:347:in `each'
> /usr/local/lib/ruby/2.5.0/tsort.rb:347:in `call'
> /usr/local/lib/ruby/2.5.0/tsort.rb:347:in `each_strongly_connected_component'
> /usr/local/lib/ruby/2.5.0/tsort.rb:226:in `tsort_each'
> /usr/local/lib/ruby/2.5.0/tsort.rb:205:in `tsort_each'
> /discourse/vendor/bundle/ruby/2.5.0/gems/railties-6.0.0/lib/rails/initializable.rb:60:in `run_initializers'
> /discourse/vendor/bundle/ruby/2.5.0/gems/railties-6.0.0/lib/rails/application.rb:363:in `initialize!'
> /discourse/vendor/bundle/ruby/2.5.0/gems/railties-6.0.0/lib/rails/railtie.rb:190:in `public_send'
> /discourse/vendor/bundle/ruby/2.5.0/gems/railties-6.0.0/lib/rails/railtie.rb:190:in `method_missing'
> /discourse/config/environment.rb:7:in `<top (required)>'
> /discourse/vendor/bundle/ruby/2.5.0/gems/sidekiq-5.2.7/lib/sidekiq/cli.rb:288:in `boot_system'
> /discourse/vendor/bundle/ruby/2.5.0/gems/sidekiq-5.2.7/lib/sidekiq/cli.rb:46:in `run'
> /discourse/vendor/bundle/ruby/2.5.0/gems/sidekiq-5.2.7/bin/sidekiq:12:in `<top (required)>'
> /discourse/vendor/bundle/ruby/2.5.0/bin/sidekiq:23:in `load'
> /discourse/vendor/bundle/ruby/2.5.0/bin/sidekiq:23:in `<top (required)>'
> /usr/local/lib/ruby/site_ruby/2.5.0/bundler/cli/exec.rb:74:in `load'
> /usr/local/lib/ruby/site_ruby/2.5.0/bundler/cli/exec.rb:74:in `kernel_load'
> /usr/local/lib/ruby/site_ruby/2.5.0/bundler/cli/exec.rb:28:in `run'
> /usr/local/lib/ruby/site_ruby/2.5.0/bundler/cli.rb:463:in `exec'
> /usr/local/lib/ruby/site_ruby/2.5.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'
> /usr/local/lib/ruby/site_ruby/2.5.0/bundler/vendor/thor/lib/thor/invocation.rb:126:in `invoke_command'
> /usr/local/lib/ruby/site_ruby/2.5.0/bundler/vendor/thor/lib/thor.rb:387:in `dispatch'
> /usr/local/lib/ruby/site_ruby/2.5.0/bundler/cli.rb:27:in `dispatch'
> /usr/local/lib/ruby/site_ruby/2.5.0/bundler/vendor/thor/lib/thor/base.rb:466:in `start'
> /usr/local/lib/ruby/site_ruby/2.5.0/bundler/cli.rb:18:in `start'
> /usr/local/bin/bundle:30:in `block in <main>'
> /usr/local/lib/ruby/site_ruby/2.5.0/bundler/friendly_errors.rb:124:in `with_friendly_errors'
> /usr/local/bin/bundle:22:in `<main>'
```\n
请问接下来应该从哪里着手排查?谢谢!

这不是按照我们的官方指南进行的安装,因此我们能提供的帮助有限。

我们目前在 OpenShift 上运行 Discourse,因此这肯定不是标准安装。我会查阅官方指南,看看在当前部署中是否有遗漏之处,谢谢。

实际上,即使执行以下 Docker 命令,在我的通过 VirtualBox 运行的 Discourse Vagrant 机器中也会触发相同的堆栈跟踪:

vagrant ssh -c '(cd ~/discourse && sudo bin/docker/sidekiq)'

你确定吗?官方指南 中没有任何关于手动启动 Sidekiq 的内容,而且里面也没有 /home/discourse 目录。

你是在生产环境中运行开发版安装吗?:exploding_head:

我使用 Docker 命令来测试和开发 Discourse 插件。

我目前使用的另一个脚本是:

# 清除临时 Discourse 缓存并启动开发环境的 Rails
vagrant ssh -c '(rm -rf ~/discourse/tmp/cache)'
vagrant ssh -c '(cd ~/discourse && sudo bin/docker/rails s)'

当然不是:smile:

@Falco 我认为这与以下内容有关:

你怎么看?

哦,可能是这样。你没说明这是你的开发设置而非开发环境,这确实造成了困扰。

所以你的意思是,我们目前的 Docker 开发设置已经出问题了?

是的,看起来就是这样。

回退那个提交后,Sidekiq 又能正常工作了。

看来这归你管了 @kris.kotlarek

好的,我知道这里发生了什么,这是我的错。由于在使用 Zeitwerk 自动加载器时不再需要,我移除了许多 require_dependency

不过,在 application.rb 中我们有这段代码:

if !Sidekiq.server?
  config.autoload_paths += Dir["#{config.root}/lib"]
end

这意味着 Sidekiq 不会在 lib 目录中查找依赖项,我们需要在特定文件中显式声明所需的依赖。

我可以为 Sidekiq 使用的文件恢复 require_dependency,或者移除 application.rb 中的这个保护条件。

我想我们之前使用显式 require 是为了节省 worker 的内存,所以可能应该遵循这个方向。我会把 require_dependency 加回来。

@sam 你怎么看?

我们应该移除这个保护机制,我不喜欢它,因为你无法确定你的进程是否会变成 Sidekiq。在我们的部署中:unicorn master → fork → sidekiq worker。在 fork 时,application.rb 已经被解析过了。

@kris.kotlarek 感谢您的修复。Sidekiq 现在与 Discourse 官方插件一起正常工作了 :+1:

但我大部分第三方插件中的作业无法运行 :cry:。很可能是由于以下提交未应用到它们的 Jobs:: 类中。:arrow_down:


故障的第三方插件

你说得对,现在所有 Jobs::OnceoffJobs::BaseJobs::Scheduled 都需要使用 ::

我已修复 discourse-whos-online,并为其他插件创建了拉取请求:

@kris.kotlarek 非常感谢 :partying_face: :+1: 。我看了你关于 zeitwork 自动加载器的帖子。是否有计划启用开发插件的自动重载功能,以加快插件开发速度?

仍然有一些地方包含 requirerequire_dependency,但许多已被移除。

我认为现在插件代码应该可以自动重新加载而不会出现问题。我们需要稍作测试以确保,但我持乐观态度 :slight_smile:

看起来我们需要先根据文档启用它。我在开发环境中尝试过,但没有重新加载任何插件更改 :cry:

这太棒了。我原本就抱有一些希望,而你的话让我更加充满期待。插件开发者们一定会喜欢这个功能。