diff --git a/lib/ruby_lsp/ruby_lsp_rails/server.rb b/lib/ruby_lsp/ruby_lsp_rails/server.rb index 1fc3b9f4..a2820d47 100644 --- a/lib/ruby_lsp/ruby_lsp_rails/server.rb +++ b/lib/ruby_lsp/ruby_lsp_rails/server.rb @@ -529,6 +529,23 @@ def database_supports_indexing?(model) end end +# Patch fork to name processes based on the caller's file path. This is useful for figuring out what is creating more +# child processes from the runtime server, so that we can optimize and more easily debug orphaned processes +# @requires_ancestor: Kernel +module ForkPatch + #: () { () -> void } -> Integer? + def fork(&block) + super do + fork_caller = caller_locations(3, 1)&.first + $0 = "ruby-lsp-rails: #{fork_caller&.path}" + block.call + end + end + + Kernel.prepend(self) + Process.singleton_class.prepend(self) +end + if ARGV.first == "start" RubyLsp::Rails::Server.new(capabilities: JSON.parse(ARGV[1], symbolize_names: true)).start end diff --git a/test/ruby_lsp_rails/server_test.rb b/test/ruby_lsp_rails/server_test.rb index ecd2e02b..81093e2e 100644 --- a/test/ruby_lsp_rails/server_test.rb +++ b/test/ruby_lsp_rails/server_test.rb @@ -268,6 +268,38 @@ def print_it! $> = original_stdout end + test "forked processes are named based on caller" do + skip("Fork is not supported on Windows") if Gem.win_platform? + + File.write("my_addon.rb", <<~RUBY) + class MyServerAddon < RubyLsp::Rails::ServerAddon + def name + "MyAddon" + end + + def execute(request, params) + pid = fork do + # We can't directly send a message in theses tests because we're using a StringIO as stdout instead of the + # actual pipe, which means that the child process doesn't have access to the same object + File.write("process_name.txt", $0) + end + + Process.wait(pid) + send_message({ process_name: File.read("process_name.txt") }) + File.delete("process_name.txt") + end + end + RUBY + + addon_path = File.expand_path("my_addon.rb") + @server.execute("server_addon/register", server_addon_path: addon_path) + @server.execute("server_addon/delegate", server_addon_name: "MyAddon", request_name: "dsl") + + assert_equal(response, { process_name: "ruby-lsp-rails: #{addon_path}" }) + ensure + FileUtils.rm("my_addon.rb") + end + private def response