debug icon indicating copy to clipboard operation
debug copied to clipboard

Open to extensions?

Open st0012 opened this issue 4 years ago • 17 comments

I'm thinking about building a rdbg-rails gem to provide Rails integration. This includes:

  • rails commands, for example:
    • rails configs - list all Rails config options
    • rails initializers - list all initializers
    • rails instrumentation /sql.active_record/ [on|off] - similar to trace [on|off] but print ActiveSupport` instrumentation.
    • This relies on debugger to provide APIs for register new commands
  • Apply Rails' backtrace_cleaner to backtrace and flow control commands to skip certain frames.
    • This also relies on debugger to provide related APIs

So my question is, is the debugger open to these extension APIs?

st0012 avatar Jul 09 '21 04:07 st0012

What kind of extension do you want to make?

ko1 avatar Jul 09 '21 05:07 ko1

  1. Allow registering new commands
  2. Support frame filtering in debugger and allow configuring the pattern

st0012 avatar Jul 09 '21 05:07 st0012

For 1, now I'm negative because the debugger's structure is complex and is not stable yet.

For 2, we can introduce some configurations.

ko1 avatar Jul 09 '21 05:07 ko1

For 1, "alias" functionality is acceptable, to call Ruby's method and so on.

ko1 avatar Jul 09 '21 07:07 ko1

For 1, the API should provide access to the scope of ThreadClient, so the extension can also format and colorize the output.

For 2, we can introduce some configurations.

Ok cool, I'll make a PR to experiment this idea 😄

st0012 avatar Jul 09 '21 09:07 st0012

For 1, the API should provide access to the scope of ThreadClient, so the extension can also format and colorize the output.

Actually this is not necessary. I think being able to register the command to session is good enough.

st0012 avatar Jul 10 '21 05:07 st0012

Could you give me a more concrete example? and we can discuss on it.

ko1 avatar Jul 10 '21 07:07 ko1

@ko1 I'm thinking something like https://github.com/ruby/debug/pull/158

st0012 avatar Jul 10 '21 07:07 st0012

I want to ask what kind of extension do you want to make. Not the extension design. We can provide a command to eval Ruby code in session context, like direct_eval <expr>. Combine with alias foo <debug command> https://github.com/ruby/debug/issues/151#issuecomment-876994542 we can make new command:

$ alias foo direct_eval some_method

ko1 avatar Jul 10 '21 15:07 ko1

In general, design extension API is too difficult and we need to consider user's examples. It is what we Ruby interpreter developers do usually.

ko1 avatar Jul 10 '21 15:07 ko1

An example is to get Rails application's config:

(rdbg) rails config active_job

The simplest implementation would be:

Rails.configuration.send(arg)

st0012 avatar Jul 10 '21 15:07 st0012

how about that? alias rails direct_eval rails_command_helper(__args__)

where __args__ are expanded to the given parameters. if rails foo bar is called, __args__ will be "foo bar" and rails_command_helper will parse it.

(NOTE: the names direct_eval and __args__ are not considered carefully now)

Disadvantage of this approach is, we can not define complement logic.

ko1 avatar Jul 10 '21 16:07 ko1

How can the extension insert the rails_command_helper method to either Session or ThreadClient?

st0012 avatar Jul 11 '21 05:07 st0012

eval rails_command_helper command run the method on ThreadClient. direct_eval rails_command_helper is on Session.

ko1 avatar Jul 11 '21 07:07 ko1

@ko1 does that mean the extension gem needs to define rails_command_helper at the top level? can we support namespacing with modules?

for example, alias rails direct_eval RDBG::Rails.execute_command(arguments)

this will allow extensions to organize commands more easily:

module RDBG
  module Rails
    class << self
      def execute_command(arguments)
        # ...
      end
    end
  end

  module Foo
    class << self
      def execute_command(arguments)
        # ...
      end
    end
  end
end

st0012 avatar Aug 13 '21 15:08 st0012

I now want to revisit this issue. I think the key here is to allow extensions to register their command logic in a ractor-safe way.

So using proc like:

DEBUGGER__.register_command("rails") { the_logic }

won't be acceptable, as we won't know if the block is isolated or not.

But how about classes? Will this work?

class MyCommand
  def self.process_command(args)
    # ...
  end
end

DEBUGGER__.register_command("cmd", MyCommand)

Or if we take modules and extend/include the methods instead?

# in the debugger
module DEBUGGER__
  module ExtensionContainer
  end

  def self.regsiter_command(module)
    ExtensionContainer.extend module
  end

  class Session
    def process_command line
      # ....
      else
        if ExtensionContainer.respond_to?(cmd)
          ExtensionContainer.send(cmd, arg)
          return :retry
        else
          @tc << [:eval, :pp, line]
        end
      end
  end
end

# in extension
module RailsExtension
  def rails(input)
    # logic
  end
end

DEBUGGER__.register_command(RailsExtension)

Of course, with this approach we'll need to worry about the name collision issue. Not just the main command method but also how extensions name their helper methods.

st0012 avatar Jan 15 '22 21:01 st0012

Update: the current plan is to introduce Ractor support (no due date) first so we can define better interfaces for extensions.

st0012 avatar Jan 17 '22 12:01 st0012