-
Notifications
You must be signed in to change notification settings - Fork 299
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Zeitwerk throws a LoadError of cannot load such file for turbo-rails-1.0.0/app/channels/turbo/streams when RAILS_ENV=production is set while using Sidekiq #285
Comments
The scope of this issue has changed since its creation. It's now an issue with
Perhaps this has to do with lazy loading modules in development vs pre-loading modules in production? At least it happens in both environments now. Question is whether this is a turbo-rails, sidekiq, Rails 7 or a user issue. From my end all I did was execute a default Rails 7 mailer through Active Job using |
Having the same issue without zeitwerk |
I'll have a look, do you have a minimal app where it can be reproduced? Let me shed some light about why is a directory being required. You know that if a module acting as a namespace does not have a file defining it, Zeitwerk autovivifies it, like classic did. In order to do that when the module is first referenced by the user, Zeitwerk defines an autoload on the first directory it finds implicitly defining the module. Then, a thin This is telling us that the autoload is being triggered, but for some reason the internal library registry does not identify it as being managed by the loader. I have never seen this before, we need to understand. Let me also say that since Zeitwerk has that |
The example app at https://github.com/nickjj/docker-rails-example can be used to reproduce it. As long as you already have Docker installed you can run these steps to get up and running: git clone https://github.com/nickjj/docker-rails-example hellorails
cd hellorails
cp .env.example .env
cp docker-compose.override.yml.example docker-compose.override.yml
docker-compose build To reproduce the error you can goto Once that's been adjusted you can run You can also check the main app (in case you want to create a mailer that gets sent in a controller action) by visiting http://localhost:8000. This may look like a lot of steps to reproduce it but without Docker you would be responsible for setting up Ruby, Redis, Sidekiq, Foreman, etc. If you already have that available then the same config change should do the trick on your end. If you have any additional questions please let me know. Happy to include as much information as you request. Edit: It's worth pointing out this issue persists with Zeitwerk |
I can confirm the same error. I'm not sure if it helps, but I've noticed that most of my Sidekiq jobs complete without issue. The ones that fail seem to all be Mailer jobs. |
I had this exact problem, and resolved it by using |
This appears to have resolved the issue on my development machine. |
This was reported as a potential fixed based on: hotwired/turbo-rails#285 (comment) The root cause isn't known at the time of making this commit but this will at least let us run Active Jobs without errors for now.
Nice find @skinnyfit, I can also confirm that using I've patched the example Docker Rails app for this at nickjj/docker-rails-example@ed7e99c. It still feels like maybe the root cause isn't addressed tho since this wasn't an issue previously? I'll keep this issue open until we're greeted with an official answer or it gets closed from a maintainer of turbo-rails. |
Confirming @skinnyfit solution solves in production. Thank you so much! |
Hey, I see the missing In my machine, with a just cloned repo, the error is different:
This is telling us, at least, that the issue is not related to I'll write back when I get something more. |
OK, I am closer to the root cause, and I have been able to reproduce with a minimal Ruby script that does not depend on Rails. The command With that loading order, the Why wasn't this happening before? Because in Rails 7 So, the problem is loading require 'fileutils'
require 'tmpdir'
# UNCOMMENT THE FOLLOWING LINE TO REPRODUCE.
# require 'zeitwerk'
require 'bundler/inline'
gemfile do
source 'https://rubygems.org'
gem 'zeitwerk'
end
Dir.mktmpdir do |dir|
Dir.chdir(dir)
FileUtils.mkdir_p('lib/admin')
File.write('lib/foo.rb', 'Foo = 1')
loader = Zeitwerk::Loader.new
loader.push_dir('lib')
loader.log!
loader.setup
p Foo
p Admin
end If you run the script as is, you'll see:
If you uncomment the
Both (1) and (2) are telling us the decorator is not invoked. In the case of (1), we see it because the traces are missing. If the application had callbacks, for example, they would not be called. (2) is what this issue detected, but it could have been (1) as well. Next step now is to understand who hijacks the |
Bundler does it here. Basically, Bundler assumes no one else is decorating Bundler is special, however, maybe this needs to be addressed with documentation. I'll open an issue in Bundler to discuss. But I think we can close here: You have to use |
Thank you for the excellent breakdown, thorough explanation and coming up with a good repeatable test case. I'll close this issue based on everything you wrote. |
Followup. As discussed, in a project managed by Bundler you need to However, being pragmatic, I have patched Rails (rails/rails@9b61af8) to delay loading Zeitwerk just a tad, so that a mere |
Issue opened rubygems/rubygems#5263 to at least have a conversation about it with the Bundler team. It's a very edge incompatibility, you need to load Zeitwerk first and then Bundler for it to happen. That's rare, because a project managed by Bundler typically runs executables and loads libraries managed by Bundler. In particular, Bundler loads before Zeitwerk. |
I encountered the same issue while working on my own gem. loader = Zeitwerk::Loader.for_gem
loader.setup as usual. But when testing it in my own Rails app, with great surprise I got the error above although I was using gem 'rails_api_logger', path: '../rails_api_logger' I pushed my branch and changed the Gemfile to gem 'rails_api_logger', github: 'renuo/rails_api_logger' and the problem disappeared. Of course, this is anyway very inconvenient, because I cannot point to the local path of my gem, but it might help tackle the issue. |
@coorasse which Rails version? |
Rails version: 7.0.2.3 |
@coorasse Awesome. Would it be possible to share a minimal application that reproduces the issue? |
Here you are: https://github.com/coorasse/reproduce_zeitwerk_issue |
Hey, the branch If I point to |
Right, that branch got deleted today. I now pushed a new version where it points to the local path directly and gives me the error when running
|
ExplanationOK, I see what happens. Bundler resets Why does the local dependency incur in this situation? Because Bundler evaluates its gemspec file. I guess it does so to figure out version and dependencies. The gemspec loads the gem entry point, the entry point loads Zeitwerk, and later Bundler resets WorkaroundA workaround would be to set the version in its own file and load just the version file in the gemspec, which is a common practice anyway: # rails_api_logger.gemspec
require_relative "lib/rails_api_logger/version" Extra BallLet me also suggest to ignore the generators directory: loader.ignore("{__dir__}/generators") since its files are not meant to be autoloaded and do not follow conventions anyway. |
Thanks! This makes totally sense! I wonder if:
|
Hey!
Excluding from eager loading is not enough. You have to ignore the directory. If you exclude, you are still telling the loader that it is managing the directory, that the directory is a namespace, and that it should define a constant On the other hand, ignoring means: you are not managing this at all, ignore it for all purposes. This is what you want.
It is unfortunate and difficult to understand, yes. Very rare to hit it also, though. I can't think of a reasonable way to warn that does not imply monkey-patching Bundler, which I think would be a bad idea. Someone else is removing my methods! What we really need is this to be fixed upstream.
This is a good question. Take into account that Zeitwerk knows nothing about Rails, However, it could be the case that extra directories in I'll think about it. |
I have inspected a sample of gems that load with Zeitwerk:
This, and our thread, tell me that checking for these extras is worthwhile. I have implemented this feature in fxn/zeitwerk@f1c1b59. You get a warning if there are extra non-ignored Ruby files or directories under This issue was already closed, and after this it is double closed 😀. |
@coorasse I have reported your use case in rubygems/rubygems#5263 (comment). Researching this, I realized that when Bundler evaluates the local gemspec of a If the gem loaded from the system has a different version than the one in |
This has shipped with 2.6.0. |
Hi,
Edit: The scope of this issue has changed to affect every Active Job running through Sidekiq and it also affects all environments. The first reply to this issue has more details. I left the original issue here for extra context which is still worth reading.
I recently updated an example Rails app to use Rails 7 (from Rails 6). After doing that Sidekiq will no longer start successfully when
RAILS_ENV=production
is set but it starts successfully withRAILS_ENV=development
.This gem comes up as a potential culprit but it's too soon to say if it's a bug with this gem. I haven't seen anything similar to this type of error previously (I wasn't using zeitwerk with Rails 6.x).
Here's the turbo-rails related stack trace:
The application is running within Docker and it's open source at https://github.com/nickjj/docker-rails-example which should provide a reproduceable test case based on the latest
main
branch. There's a quick getting started guide for the app at https://github.com/nickjj/docker-rails-example#running-this-app which ultimately comes down to running a few commands.If I
exec
into theworker
container I'm able tostat /usr/local/bundle/gems/turbo-rails-1.0.0/app/channels/turbo/streams
which returns back that the directory does exist as seen below:What's weird is the error mentions
cannot load such file
but that path it not a file, it's a directory on disk.What's also interesting is the
web
,worker
andcable
containers that are a part of the project all use an identical Dockerfile and the only difference is the command that gets run (such as puma or sidekiq). Both theweb
andcable
containers come up fine. It's onlyworker
that fails.Running the same stat command in the
web
andcable
containers produce the same output.I'm tracking the issue in my repo at nickjj/docker-rails-example#30.
I'm not sure where else to post this. I'm also not sure how autoloading works under the hood. If paths are loaded alphabetically I'm taking a guess here that other gem paths were successfully loaded and it failed when it got to turbo-rails?
The text was updated successfully, but these errors were encountered: