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
Guard against hitting const_missing from within const_missing #415
Guard against hitting const_missing from within const_missing #415
Conversation
If MiqAeServiceModelBase is not yet defined, we could hit const_missing recursively, leading to a stack level too deep. Fixes ManageIQ/manageiq#19618
Checked commit jrafanie@3f4ea2c with ruby 2.5.5, rubocop 0.69.0, haml-lint 0.20.0, and yamllint 1.10.0 |
Pull Request Test Coverage Report for Build 3654
💛 - Coveralls |
Thanks @jrafanie, this is a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gmcculloug it's green 😉 |
What, @gmcculloug, you're gonna let him get away with no tests? |
@@ -1,5 +1,7 @@ | |||
module MiqAeMethodService | |||
def self.const_missing(name) | |||
super unless defined?(MiqAeServiceModelBase) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gmcculloug should this have been:
return super unless defined?(MiqAeServiceModelBase)
As is it now, it's possible to super
twice.
or possibly more surgical:
return super if name.to_s.ends_with?("MiqAeServiceModelBase")
cc @d-m-u
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1st one - no changes
2nd one:
NoMethodError: undefined method `create_service_model_from_name' for MiqAeMethodService::MiqAeServiceModelBase:Class
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Error caught: [NoMethodError] undefined method `create_service_model_from_name' for MiqAeMethodService::MiqAeServiceModelBase:Class
/home/skateman/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/bundler/gems/manageiq-automation_engine-358772c1409b/lib/miq_automation_engine/engine/miq_ae_method_service.rb:5:in `const_missing'
/home/skateman/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/bundler/gems/manageiq-automation_engine-358772c1409b/lib/miq_automation_engine/engine/miq_ae_method_service/miq_ae_service_model_base.rb:12:in `<class:MiqAeServiceModelBase>'
/home/skateman/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/bundler/gems/manageiq-automation_engine-358772c1409b/lib/miq_automation_engine/engine/miq_ae_method_service/miq_ae_service_model_base.rb:4:in `<module:MiqAeMethodService>'
/home/skateman/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/bundler/gems/manageiq-automation_engine-358772c1409b/lib/miq_automation_engine/engine/miq_ae_method_service/miq_ae_service_model_base.rb:3:in `<top (required)>'
/home/skateman/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/activesupport-5.1.7/lib/active_support/dependencies.rb:477:in `load'
/home/skateman/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/activesupport-5.1.7/lib/active_support/dependencies.rb:477:in `block in load_file'
/home/skateman/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/activesupport-5.1.7/lib/active_support/dependencies.rb:662:in `new_constants_in'
/home/skateman/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/activesupport-5.1.7/lib/active_support/dependencies.rb:476:in `load_file'
/home/skateman/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/activesupport-5.1.7/lib/active_support/dependencies.rb:374:in `block in require_or_load'
/home/skateman/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/activesupport-5.1.7/lib/active_support/dependencies.rb:36:in `block in load_interlock'
/home/skateman/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/activesupport-5.1.7/lib/active_support/dependencies/interlock.rb:12:in `block in loading'
/home/skateman/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/activesupport-5.1.7/lib/active_support/concurrency/share_lock.rb:149:in `exclusive'
/home/skateman/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/activesupport-5.1.7/lib/active_support/dependencies/interlock.rb:11:in `loading'
/home/skateman/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/activesupport-5.1.7/lib/active_support/dependencies.rb:36:in `load_interlock'
/home/skateman/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/activesupport-5.1.7/lib/active_support/dependencies.rb:357:in `require_or_load'
/home/skateman/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/activesupport-5.1.7/lib/active_support/dependencies.rb:511:in `load_missing_constant'
/home/skateman/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/activesupport-5.1.7/lib/active_support/dependencies.rb:202:in `const_missing'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, it's failing when reloading MiqAeServiceModelBase when trying to autoload the include MiqAeMethodService::MiqAeServiceObjectCommon
line, so the constant exists but the method doesn't yet.
Maybe something like this? @skateman
diff --git a/lib/miq_automation_engine/engine/miq_ae_method_service.rb b/lib/miq_automation_engine/engine/miq_ae_method_service.rb
index b0f668a..73eccd1 100644
--- a/lib/miq_automation_engine/engine/miq_ae_method_service.rb
+++ b/lib/miq_automation_engine/engine/miq_ae_method_service.rb
@@ -1,8 +1,10 @@
module MiqAeMethodService
def self.const_missing(name)
- super unless defined?(MiqAeServiceModelBase)
-
- MiqAeServiceModelBase.create_service_model_from_name(name) || super
+ if !defined?(MiqAeServiceModelBase) || !MiqAeServiceModelBase.respond_to?(:create_service_model_from_name)
+ super
+ else
+ MiqAeServiceModelBase.create_service_model_from_name(name) || super
+ end
end
end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yuck by the way
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or a more positive way to write the code above
diff --git a/lib/miq_automation_engine/engine/miq_ae_method_service.rb b/lib/miq_automation_engine/engine/miq_ae_method_service.rb
index b0f668a..50f8e71 100644
--- a/lib/miq_automation_engine/engine/miq_ae_method_service.rb
+++ b/lib/miq_automation_engine/engine/miq_ae_method_service.rb
@@ -1,8 +1,10 @@
module MiqAeMethodService
def self.const_missing(name)
- super unless defined?(MiqAeServiceModelBase)
-
- MiqAeServiceModelBase.create_service_model_from_name(name) || super
+ if defined?(MiqAeServiceModelBase) && MiqAeServiceModelBase.respond_to?(:create_service_model_from_name)
+ MiqAeServiceModelBase.create_service_model_from_name(name) || super
+ else
+ super
+ end
end
end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, same problem 😕 let's get you a reproducer:
- you need to have a catalog with a service dialog assigned that contains a dynamic field
- you try to order the catalog and you see that the automation works
- you force a code reload, ideally in the backend or the API
- you try to order the catalog again and the automation won't return anything
Harpreet's MBU DB is a good starting point, there are multiple services there which you can use to test, will send you a link.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Files defining constants to be autoloaded should never be required
...
Just follow the flow and use constant autoloading always, never mix autoloading and require. As a last resort, if some file absolutely needs to load a certain file use require_dependency to play nice with constant autoloading. This option is rarely needed in practice, though.
Of course, using require in autoloaded files to load ordinary 3rd party libraries is fine, and Rails is able to distinguish their constants, they are not marked as autoloaded.
I'm thinking it's because automation_engine is not rails code reloading friendly. I think it needs to autoload constants or use require_dependency
so rails can track the constants loaded. See below, we're using require_relative
instead of autoload/require_dependency
so rails doesn't know about the constants. 🤷♂
I don't think this ever worked with code reloading cc @gmcculloug
lib/miq_automation_engine/engine/miq_ae_engine.rb: require_relative "miq_ae_engine/#{File.basename(file)}"
lib/miq_automation_engine/engine/miq_ae_engine/miq_ae_object.rb:require_relative 'miq_ae_state_machine'
lib/miq_automation_engine/engine/miq_ae_engine/miq_ae_workspace_runtime.rb:require_relative 'miq_ae_state_info'
lib/miq_automation_engine/engine/miq_ae_method_service.rb: require_relative "miq_ae_method_service/#{File.basename(file)}"
lib/miq_automation_engine/engine/miq_ae_method_service/miq_ae_service.rb:require_relative './miq_ae_service_model_legacy'
lib/miq_automation_engine/engine/miq_ae_method_service/miq_ae_service.rb:require_relative './miq_ae_service_vmdb'
lib/miq_automation_engine/engine/miq_ae_method_service/miq_ae_service.rb:require_relative './miq_ae_service_rbac'
lib/miq_automation_engine/engine/miq_ae_method_service/miq_ae_service_model_base.rb:require_relative 'miq_ae_service_object_common'
lib/miq_automation_engine/engine/miq_ae_method_service/miq_ae_service_model_base.rb:require_relative 'miq_ae_service_rbac'
lib/miq_automation_engine/engine/miq_ae_method_service/miq_ae_service_object.rb:require_relative './miq_ae_service_object_common'
lib/miq_automation_engine/service_models/miq_ae_service_cloud_tenant.rb: require_relative "mixins/miq_ae_external_url_mixin"
lib/miq_automation_engine/service_models/miq_ae_service_cloud_volume.rb: require_relative "mixins/miq_ae_service_ems_operations_mixin"
lib/miq_automation_engine/service_models/miq_ae_service_ems_cluster.rb: require_relative "mixins/miq_ae_service_ems_operations_mixin"
lib/miq_automation_engine/service_models/miq_ae_service_ems_folder.rb: require_relative "mixins/miq_ae_service_ems_operations_mixin"
lib/miq_automation_engine/service_models/miq_ae_service_ext_management_system.rb: require_relative "mixins/miq_ae_service_inflector_mixin"
lib/miq_automation_engine/service_models/miq_ae_service_ext_management_system.rb: require_relative "mixins/miq_ae_service_custom_attribute_mixin"
lib/miq_automation_engine/service_models/miq_ae_service_ext_management_system.rb: require_relative "mixins/miq_ae_external_url_mixin"
lib/miq_automation_engine/service_models/miq_ae_service_generic_object.rb: require_relative "mixins/miq_ae_service_remove_from_vmdb_mixin"
lib/miq_automation_engine/service_models/miq_ae_service_generic_object.rb: require_relative "mixins/miq_ae_external_url_mixin"
lib/miq_automation_engine/service_models/miq_ae_service_host.rb: require_relative "mixins/miq_ae_service_custom_attribute_mixin"
lib/miq_automation_engine/service_models/miq_ae_service_host.rb: require_relative "mixins/miq_ae_service_ems_operations_mixin"
lib/miq_automation_engine/service_models/miq_ae_service_host.rb: require_relative "mixins/miq_ae_service_remove_from_vmdb_mixin"
lib/miq_automation_engine/service_models/miq_ae_service_load_balancer.rb: require_relative "mixins/miq_ae_service_retirement_mixin"
lib/miq_automation_engine/service_models/miq_ae_service_load_balancer.rb: require_relative "mixins/miq_ae_service_remove_from_vmdb_mixin"
lib/miq_automation_engine/service_models/miq_ae_service_manageiq-providers-vmware-infra_manager-host_esx.rb: require_relative "mixins/miq_ae_service_retirement_mixin"
lib/miq_automation_engine/service_models/miq_ae_service_miq_group.rb: require_relative "mixins/miq_ae_service_custom_attribute_mixin"
lib/miq_automation_engine/service_models/miq_ae_service_miq_group.rb: require_relative "mixins/miq_ae_external_url_mixin"
lib/miq_automation_engine/service_models/miq_ae_service_miq_provision.rb: require_relative "mixins/miq_ae_service_miq_provision_mixin"
lib/miq_automation_engine/service_models/miq_ae_service_miq_provision_request.rb: require_relative "mixins/miq_ae_service_miq_provision_mixin"
lib/miq_automation_engine/service_models/miq_ae_service_miq_request.rb: require_relative "mixins/miq_ae_service_miq_request_mixin"
lib/miq_automation_engine/service_models/miq_ae_service_miq_request_task.rb: require_relative "mixins/miq_ae_service_miq_request_mixin"
lib/miq_automation_engine/service_models/miq_ae_service_orchestration_stack.rb: require_relative "mixins/miq_ae_service_retirement_mixin"
lib/miq_automation_engine/service_models/miq_ae_service_orchestration_stack.rb: require_relative "mixins/miq_ae_service_remove_from_vmdb_mixin"
lib/miq_automation_engine/service_models/miq_ae_service_service.rb: require_relative "mixins/miq_ae_service_retirement_mixin"
lib/miq_automation_engine/service_models/miq_ae_service_service.rb: require_relative "mixins/miq_ae_service_custom_attribute_mixin"
lib/miq_automation_engine/service_models/miq_ae_service_service.rb: require_relative "mixins/miq_ae_service_remove_from_vmdb_mixin"
lib/miq_automation_engine/service_models/miq_ae_service_service.rb: require_relative "mixins/miq_ae_external_url_mixin"
lib/miq_automation_engine/service_models/miq_ae_service_service_ansible_tower.rb: require_relative "mixins/miq_ae_service_service_ansible_tower_mixin"
lib/miq_automation_engine/service_models/miq_ae_service_service_orchestration.rb: require_relative "mixins/miq_ae_service_service_orchestration_mixin"
lib/miq_automation_engine/service_models/miq_ae_service_service_template_ansible_tower.rb: require_relative "mixins/miq_ae_service_service_ansible_tower_mixin"
lib/miq_automation_engine/service_models/miq_ae_service_service_template_orchestration.rb: require_relative "mixins/miq_ae_service_service_orchestration_mixin"
lib/miq_automation_engine/service_models/miq_ae_service_service_template_provision_request.rb: require_relative "mixins/miq_ae_service_miq_provision_mixin"
lib/miq_automation_engine/service_models/miq_ae_service_tenant.rb: require_relative "mixins/miq_ae_external_url_mixin"
lib/miq_automation_engine/service_models/miq_ae_service_user.rb: require_relative "mixins/miq_ae_service_custom_attribute_mixin"
lib/miq_automation_engine/service_models/miq_ae_service_user.rb: require_relative "mixins/miq_ae_external_url_mixin"
lib/miq_automation_engine/service_models/miq_ae_service_vm.rb: require_relative "mixins/miq_ae_external_url_mixin"
lib/miq_automation_engine/service_models/miq_ae_service_vm_cloud_reconfigure_request.rb: require_relative "mixins/miq_ae_service_miq_provision_quota_mixin"
lib/miq_automation_engine/service_models/miq_ae_service_vm_migrate_request.rb: require_relative "mixins/miq_ae_service_miq_provision_quota_mixin"
lib/miq_automation_engine/service_models/miq_ae_service_vm_or_template.rb: require_relative "mixins/miq_ae_service_ems_operations_mixin"
lib/miq_automation_engine/service_models/miq_ae_service_vm_or_template.rb: require_relative "mixins/miq_ae_service_retirement_mixin"
lib/miq_automation_engine/service_models/miq_ae_service_vm_or_template.rb: require_relative "mixins/miq_ae_service_inflector_mixin"
lib/miq_automation_engine/service_models/miq_ae_service_vm_or_template.rb: require_relative "mixins/miq_ae_service_custom_attribute_mixin"
lib/miq_automation_engine/service_models/miq_ae_service_vm_or_template.rb: require_relative "mixins/miq_ae_service_remove_from_vmdb_mixin"
lib/miq_automation_engine/service_models/miq_ae_service_vm_reconfigure_request.rb: require_relative "mixins/miq_ae_service_miq_provision_quota_mixin"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jrafanie So your recommendation is to update these 50+ require_relative
references to use require_dependency
? Is that the only update required? Is there any other potential benefit other than supporting reload when running in dev mode?
If MiqAeServiceModelBase is not yet defined, we could hit const_missing
recursively, leading to a stack level too deep.
Fixes ManageIQ/manageiq#19618