oracle / truffleruby

A high performance implementation of the Ruby programming language, built on GraalVM.
https://www.graalvm.org/ruby/
Other
2.98k stars 179 forks source link

Monkey patching not working #3570

Open n-rodriguez opened 1 month ago

n-rodriguez commented 1 month ago

Hi there!

I'm trying to run a Rails application with TruffleRuby 24.0.1 and it almost works expect one thing : a monkey patch is not applied to the codebase :/

The patch :

# frozen_string_literal: true

# Add queries count to template rendering instrumentation
# See: https://github.com/rails/rails/pull/51457

# return if Settings.java_ruby?

require 'active_record/railties/controller_runtime'
require 'active_record/runtime_registry'

module Concerto
  module CoreExt
    module RailsPatch
      module ControllerRuntimePatch
        module ClassMethods
          def log_process_action(payload)
            messages, db_runtime = super, payload[:db_runtime]

            if db_runtime
              queries_count = payload[:queries_count] || 0
              cached_queries_count = payload[:cached_queries_count] || 0
              messages << ("ActiveRecord: %.1fms (%d %s, %d cached)" % [db_runtime.to_f, queries_count,
                                                                        "query".pluralize(queries_count), cached_queries_count])
            end

            messages
          end
        end
        module InstanceMethods
          private

            def cleanup_view_runtime
              if logger && logger.info?
                db_rt_before_render = ActiveRecord::RuntimeRegistry.reset_runtimes
                self.db_runtime = (db_runtime || 0) + db_rt_before_render
                runtime = super
                queries_rt = ActiveRecord::RuntimeRegistry.sql_runtime - ActiveRecord::RuntimeRegistry.async_sql_runtime
                db_rt_after_render = ActiveRecord::RuntimeRegistry.reset_runtimes
                self.db_runtime += db_rt_after_render
                runtime - queries_rt
              else
                super
              end
            end

            def append_info_to_payload(payload)
              super

              payload[:db_runtime] = (db_runtime || 0) + ActiveRecord::RuntimeRegistry.reset_runtimes
              payload[:queries_count] = ActiveRecord::RuntimeRegistry.reset_queries_count
              payload[:cached_queries_count] = ActiveRecord::RuntimeRegistry.reset_cached_queries_count
            end
        end
      end

      module ActiveRecordPatch
        def queries_count
          ActiveSupport::IsolatedExecutionState[:active_record_queries_count] ||= 0
        end

        def queries_count=(count)
          ActiveSupport::IsolatedExecutionState[:active_record_queries_count] = count
        end

        def cached_queries_count
          ActiveSupport::IsolatedExecutionState[:active_record_cached_queries_count] ||= 0
        end

        def cached_queries_count=(count)
          ActiveSupport::IsolatedExecutionState[:active_record_cached_queries_count] = count
        end

        def reset
          reset_runtimes
          reset_queries_count
          reset_cached_queries_count
        end

        def reset_runtimes
          rt, self.sql_runtime = sql_runtime, 0.0
          self.async_sql_runtime = 0.0
          rt
        end

        def reset_queries_count
          qc = queries_count
          self.queries_count = 0
          qc
        end

        def reset_cached_queries_count
          qc = cached_queries_count
          self.cached_queries_count = 0
          qc
        end
      end
    end
  end
end

ActiveRecord::Railties::ControllerRuntime::ClassMethods.prepend(Concerto::CoreExt::RailsPatch::ControllerRuntimePatch::ClassMethods)
ActiveRecord::Railties::ControllerRuntime.prepend(Concerto::CoreExt::RailsPatch::ControllerRuntimePatch::InstanceMethods)
ActiveRecord::RuntimeRegistry.include(Concerto::CoreExt::RailsPatch::ActiveRecordPatch)

ActiveSupport::Notifications.monotonic_subscribe("sql.active_record") do |name, start, finish, id, payload| # rubocop:disable Lint/UnusedBlockArgument,Style/StringLiterals
  unless ["SCHEMA", "TRANSACTION"].include?(payload[:name]) # rubocop:disable Style/StringLiterals,Style/WordArray
    ActiveRecord::RuntimeRegistry.queries_count += 1
    ActiveRecord::RuntimeRegistry.cached_queries_count += 1 if payload[:cached]
  end
end

This monkey patch is extracted from this PR : https://github.com/rails/rails/pull/51457

The error is :

Exiting
/Users/nicolas/PROJECTS/CONCERTO/concerto/lib/concerto/core_ext/rails_patch.rb:107:in `block in <top (required)>': undefined method `queries_count' for ActiveRecord::RuntimeRegistry:Module (NoMethodError)
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/activesupport-7.1.3.2/lib/active_support/notifications/fanout.rb:137:in `block in finish'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/activesupport-7.1.3.2/lib/active_support/notifications/fanout.rb:26:in `block in iterate_guarding_exceptions'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/activesupport-7.1.3.2/lib/active_support/notifications/fanout.rb:25:in `each'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/activesupport-7.1.3.2/lib/active_support/notifications/fanout.rb:25:in `iterate_guarding_exceptions'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/activesupport-7.1.3.2/lib/active_support/notifications/fanout.rb:125:in `each'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/activesupport-7.1.3.2/lib/active_support/notifications/fanout.rb:136:in `finish'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/activesupport-7.1.3.2/lib/active_support/notifications/fanout.rb:262:in `block in finish_with_values'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/activesupport-7.1.3.2/lib/active_support/notifications/fanout.rb:26:in `block in iterate_guarding_exceptions'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/activesupport-7.1.3.2/lib/active_support/notifications/fanout.rb:25:in `each'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/activesupport-7.1.3.2/lib/active_support/notifications/fanout.rb:25:in `iterate_guarding_exceptions'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/activesupport-7.1.3.2/lib/active_support/notifications/fanout.rb:261:in `finish_with_values'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/activesupport-7.1.3.2/lib/active_support/notifications/fanout.rb:254:in `finish'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/activesupport-7.1.3.2/lib/active_support/notifications/instrumenter.rb:64:in `instrument'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/activerecord-7.1.3.2/lib/active_record/connection_adapters/abstract_adapter.rb:1143:in `log'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/activerecord-7.1.3.2/lib/active_record/connection_adapters/postgresql_adapter.rb:892:in `exec_no_cache'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/activerecord-7.1.3.2/lib/active_record/connection_adapters/postgresql_adapter.rb:872:in `execute_and_clear'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/activerecord-7.1.3.2/lib/active_record/connection_adapters/postgresql/database_statements.rb:64:in `internal_exec_query'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/activerecord-7.1.3.2/lib/active_record/connection_adapters/abstract/database_statements.rb:630:in `select'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/activerecord-7.1.3.2/lib/active_record/connection_adapters/abstract/database_statements.rb:71:in `select_all'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/activerecord-7.1.3.2/lib/active_record/connection_adapters/abstract/query_cache.rb:115:in `select_all'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/activerecord-7.1.3.2/lib/active_record/relation.rb:994:in `block (2 levels) in exec_main_query'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/activerecord-7.1.3.2/lib/active_record/relation/finder_methods.rb:467:in `apply_join_dependency'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/activerecord-7.1.3.2/lib/active_record/relation.rb:988:in `block in exec_main_query'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/activerecord-7.1.3.2/lib/active_record/relation.rb:1018:in `skip_query_cache_if_necessary'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/activerecord-7.1.3.2/lib/active_record/relation.rb:984:in `exec_main_query'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/activerecord-7.1.3.2/lib/active_record/relation.rb:962:in `block in exec_queries'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/activerecord-7.1.3.2/lib/active_record/relation.rb:1018:in `skip_query_cache_if_necessary'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/activerecord-7.1.3.2/lib/active_record/relation.rb:956:in `exec_queries'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/activerecord-7.1.3.2/lib/active_record/relation.rb:742:in `load'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/activerecord-7.1.3.2/lib/active_record/relation.rb:264:in `records'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/activerecord-7.1.3.2/lib/active_record/relation/delegation.rb:100:in `-'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/app/models/region.rb:108:in `for_french_site'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/vendor/engines/concerto_france/config/routes.rb:54:in `block in <top (required)>'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/actionpack-7.1.3.2/lib/action_dispatch/routing/mapper.rb:636:in `instance_exec'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/actionpack-7.1.3.2/lib/action_dispatch/routing/mapper.rb:636:in `block in with_default_scope'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/actionpack-7.1.3.2/lib/action_dispatch/routing/mapper.rb:882:in `scope'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/actionpack-7.1.3.2/lib/action_dispatch/routing/mapper.rb:635:in `with_default_scope'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/actionpack-7.1.3.2/lib/action_dispatch/routing/route_set.rb:446:in `eval_block'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/actionpack-7.1.3.2/lib/action_dispatch/routing/route_set.rb:430:in `draw'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/vendor/engines/concerto_france/config/routes.rb:4:in `<top (required)>'
    from <internal:core> core/kernel.rb:378:in `load'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/railties-7.1.3.2/lib/rails/application/routes_reloader.rb:50:in `block in load_paths'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/railties-7.1.3.2/lib/rails/application/routes_reloader.rb:50:in `each'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/railties-7.1.3.2/lib/rails/application/routes_reloader.rb:50:in `load_paths'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/railties-7.1.3.2/lib/rails/application/routes_reloader.rb:24:in `reload!'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/railties-7.1.3.2/lib/rails/application/routes_reloader.rb:38:in `block in updater'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/activesupport-7.1.3.2/lib/active_support/file_update_checker.rb:85:in `execute'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/railties-7.1.3.2/lib/rails/application/routes_reloader.rb:13:in `execute'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/railties-7.1.3.2/lib/rails/application/finisher.rb:161:in `block in <module:Finisher>'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/railties-7.1.3.2/lib/rails/initializable.rb:32:in `instance_exec'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/railties-7.1.3.2/lib/rails/initializable.rb:32:in `run'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/railties-7.1.3.2/lib/rails/initializable.rb:61:in `block in run_initializers'
    from /Users/nicolas/.asdf/installs/ruby/truffleruby-24.0.1/lib/mri/tsort.rb:228:in `block in tsort_each'
    from /Users/nicolas/.asdf/installs/ruby/truffleruby-24.0.1/lib/mri/tsort.rb:350:in `block (2 levels) in each_strongly_connected_component'
    from /Users/nicolas/.asdf/installs/ruby/truffleruby-24.0.1/lib/mri/tsort.rb:431:in `each_strongly_connected_component_from'
    from /Users/nicolas/.asdf/installs/ruby/truffleruby-24.0.1/lib/mri/tsort.rb:349:in `block in each_strongly_connected_component'
    from /Users/nicolas/.asdf/installs/ruby/truffleruby-24.0.1/lib/mri/tsort.rb:347:in `each'
    from /Users/nicolas/.asdf/installs/ruby/truffleruby-24.0.1/lib/mri/tsort.rb:347:in `each_strongly_connected_component'
    from /Users/nicolas/.asdf/installs/ruby/truffleruby-24.0.1/lib/mri/tsort.rb:226:in `tsort_each'
    from /Users/nicolas/.asdf/installs/ruby/truffleruby-24.0.1/lib/mri/tsort.rb:205:in `tsort_each'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/railties-7.1.3.2/lib/rails/initializable.rb:60:in `run_initializers'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/railties-7.1.3.2/lib/rails/application.rb:426:in `initialize!'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/config/environment.rb:7:in `<top (required)>'
    from <internal:core> core/kernel.rb:292:in `require_relative'
    from config.ru:5:in `<top (required)>'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/rack-3.0.11/lib/rack/builder.rb:103:in `new_from_string'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/rack-3.0.11/lib/rack/builder.rb:94:in `load_file'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/rack-3.0.11/lib/rack/builder.rb:64:in `parse_file'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/rackup-2.1.0/lib/rackup/server.rb:354:in `build_app_and_options_from_config'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/rackup-2.1.0/lib/rackup/server.rb:263:in `app'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/rackup-2.1.0/lib/rackup/server.rb:424:in `wrapped_app'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/railties-7.1.3.2/lib/rails/commands/server/server_command.rb:76:in `log_to_stdout'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/railties-7.1.3.2/lib/rails/commands/server/server_command.rb:36:in `start'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/railties-7.1.3.2/lib/rails/commands/server/server_command.rb:145:in `block in perform'
    from <internal:core> core/kernel.rb:512:in `tap'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/railties-7.1.3.2/lib/rails/commands/server/server_command.rb:136:in `perform'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/thor-1.3.1/lib/thor/command.rb:28:in `run'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/thor-1.3.1/lib/thor/invocation.rb:127:in `invoke_command'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/railties-7.1.3.2/lib/rails/command/base.rb:178:in `invoke_command'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/thor-1.3.1/lib/thor.rb:527:in `dispatch'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/railties-7.1.3.2/lib/rails/command/base.rb:73:in `perform'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/railties-7.1.3.2/lib/rails/command.rb:71:in `block in invoke'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/railties-7.1.3.2/lib/rails/command.rb:149:in `with_argv'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/railties-7.1.3.2/lib/rails/command.rb:69:in `invoke'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/railties-7.1.3.2/lib/rails/commands.rb:18:in `<top (required)>'
    from <internal:core> core/kernel.rb:229:in `gem_original_require'
    from <internal:/Users/nicolas/.asdf/installs/ruby/truffleruby-24.0.1/lib/mri/rubygems/core_ext/kernel_require.rb>:37:in `require'
    from /Users/nicolas/PROJECTS/CONCERTO/concerto/.bundle/truffleruby/3.2.2.24.0.0.2/gems/bootsnap-1.18.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
    from bin/rails:4:in `<main>'

This works perfectly with Ruby 3.3.1.

Thank you!

andrykonchin commented 1 month ago

Thank you for the report, we'll look into it.

andrykonchin commented 1 month ago

It seems the issue is that methods of a module ActiveRecordPatch (that is included into RuntimeRegistry) aren't accessible as class methods. The RuntimeRegistry module extends itself so instance methods become class methods as well. But it seems it doesn't work on TruffleRuby.

A simplified example:

module A
  extend self
end

module B
  def foo
    :foo
  end
end

A.include B
puts A.foo

It works on CRuby but raises exception on TruffleRuby:

undefined method `foo' for A:Module (NoMethodError)