-
Notifications
You must be signed in to change notification settings - Fork 956
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
Running out of memory due to code reloading feature in v4.0.6 #776
Comments
We are doing exactly what rails server does in dev mode. Can you produce an example app that reproduces the issue? |
I can try to create a sample app that can represent our environment. It is possible that even the mechanism that a rails server reloads may have the same problem for us. It may just not be so obvious since we don't reload so many times as opposed to when the delayed job worker does it every 5 seconds. I will get back to you as soon as a I can. |
I managed to find some more detailed info as to why this is happening. It appears we have had this symptom for a long time, but it never got visibly exposed until delayed job started reloading. This obviously would only exist in a dev environment since that is the only environment where we do not cache classes. Our application has lots of engines that have initializers in the initializer 'blorgh.initializer' do
ActionDispatch::Reloader.to_prepare do
require 'blorgh/mixins/enhance'
Blorgh::Article.send(:include, Blorgh::Mixins::Enhance)
end
end With this, I could easily detect when our mixins are being reinserted into the classes. So despite the fact that you are doing exactly what the rails server does in dev mode, Rails will not reload until something has changed. Unfortunately, with the delayed job reloader, it doesn't care if something has changed or not, but it will call reload every 5 seconds when the worker is launched. Because we have Reloader do blocks, it executes them regardless and causes more memory to be used by reinserting all our mixins. In our case, the increase is significant every reload. I created a very simple application with one engine that has a reloader block like above and 10 models it is including the mixin into. Each delayed job reload caused the memory to go up by 1MB. The same was true for triggering a Rails reload as well, but I had to make a model change first in order to do so, otherwise each refresh on the page was fine. If appears that there needs to be some sort of additional check before reloading similar to how rails determines to reload. |
Rails does not detect changes. It reloads for every request. The difference is if you aren't doing anything on the server it never reloads. I suggest you change that to initializer 'blorgh.initializer' do
ActionDispatch::Reloader.to_prepare do
require 'blorgh/mixins/enhance'
Blorgh::Article.send(:include, Blorgh::Mixins::Enhance) unless Blorgh::Article.included_modules.include?(Blorgh::Mixins::Enhance)
end
end |
Maybe the difference is with the different versions of Rails. We use 3.2.21. This condition was called by the reloader middleware here and was used to determine if the reloader callbacks should be executed or not. This would change between true and false depending on whether or not something changed and would reload accordingly. |
It looks that that has moved around a lot. I'll take a look at what we might be able to do for conditional reloading but in the meantime my suggestion stands. |
Thank you and I greatly appreciate your input. I definitely agree we should not include modules if they are already included, so I will discuss with our platform team and the other devs to possibly include checking modules as you suggested. For the meantime, we will just have to lock ourselves to version 4.0.4. By the way, I did a quick preliminary test on a modified version of the def self.reload_app?
defined?(ActionDispatch::Reloader) &&
Rails.application.config.cache_classes == false &&
Rails.application.reloaders.map(&:updated?).any?
end Anyway, thank you again. I hope some of my information to you is useful. |
Hey guys. @mic-kul submitted a PR on ASoftCo/leaky-gems#4. Can you confirm delayed_job has a memory leak? If it has I'd appreciate if you also confirm versions and environments affected. |
@sergey-alekseev DJ does not have a memory leak. We simply use Rails code reloading and when users have non-reload safe code things go poorly. The same thing would happen running a simple rails server in dev mode. |
@sergey-alekseev . It is true that it is not a memory leak. In development mode (or rather when The only difference between what Rails and DJ does, is that Rails will try to only reload if it detects that something has changed as I mentioned earlier in this discussion. Temporarily, we just turned on FYI: in Rails 4, there are checks for duplicating routes, so we don't get the increased memory as it just raises an exception in Rails when that happens and kills the DJ rake task. |
We are using the same level of conditional that Rails is |
Actually, the rake task does not invoke that same code. Both rails middleware and DJ will call |
But Rails middleware will run a block to update the @validated variable, where in DJ it does not. https://github.com/collectiveidea/delayed_job/blob/v4.0.6/lib/delayed/worker.rb#L296-L298 |
Since DJ is just calling the class method |
Is it possible to disable this reloading via some kind of flag? We have problems in our app with Mail Interceptor because of that: collectiveidea/delayed_job_active_record#120 |
An option to disable this would be appreciated. We are running a pretty complex app with many external deps and DJ will completely eat up all the mem in dev mode in less than 15 minutes. Unfortunately we cannot control how all the external pieces do their initializers for their engines. Also DJ uses an incredible amount of CPU by reloading the app every 5 sec. |
This is really appreciated because ActiveJob already reloads code as needed on every execution. ActiveJob::Execution def execute(job_data) #:nodoc:
ActiveJob::Callbacks.run_callbacks(:execute) do
job = deserialize(job_data)
job.perform_now
end
end ActiveJob::Railtie initializer "active_job.set_reloader_hook" do |app|
ActiveSupport.on_load(:active_job) do
ActiveJob::Callbacks.singleton_class.set_callback(:execute, :around, prepend: true) do |_, inner|
app.reloader.wrap do
inner.call
end
end
end
end ActiveSupport::Reloader # Run the supplied block as a work unit, reloading code as needed
def self.wrap
executor.wrap do
super
end
end |
My workaround is to place below snippet to initializer. # ActiveJob will reload codes if necessary. DelayedJob consumes CPU and memory for reloading on every 5 secs.
# TODO: https://github.com/collectiveidea/delayed_job/issues/776#issuecomment-307161178
Delayed::Worker.instance_exec do
def self.reload_app?
false
end
end |
Part of the problem here is that delayed_job reloads the code more often than it is needed. By default this is every 5 seconds (if you have The code only needs to be reloaded if there are any jobs to run. Unfortunately, there is no shared API method in I've hacked delayed_job to do this check for my company's usage since we only use If anyone is interested you can see the diff here: master...financeit:v.4.0.6_leak_fix |
@thisduck you might like our solution: #1115 (comment) |
4.0.6 introduced the ability to reload the app in an attempt to behave like a Rails web application when the configuration
cache_classes == false
.Unfortunately, with our very large application, the worker which executes roughly every 5 seconds keeps reloading the app and memory usage increases pretty quickly.
The memory footprint of of our background rake task grew from about 300MB to 1.5G in about an hour when the
cache_classes = false
and no jobs were even being processed. Setting it totrue
alleviates this problem, but it affects our daily dev environment.The text was updated successfully, but these errors were encountered: