Skip to content

Commit f729ba0

Browse files
committed
Automatically name forked processes based on callers
1 parent 724663e commit f729ba0

File tree

2 files changed

+58
-0
lines changed

2 files changed

+58
-0
lines changed

lib/ruby_lsp/ruby_lsp_rails/server.rb

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,32 @@ def database_supports_indexing?(model)
529529
end
530530
end
531531

532+
# Patch fork to name processes based on the caller's file path. This is useful for figuring out what is creating more
533+
# child processes from the runtime server, so that we can optimize and more easily debug orphaned processes
534+
# @requires_ancestor: Kernel
535+
module ForkPatch
536+
#: () ?{ () -> void } -> Integer?
537+
def fork(&block)
538+
fork_caller = caller_locations(1, 1)&.first
539+
540+
# If a block is given, set the child process name and call the original block
541+
if block
542+
return super do
543+
$0 = "ruby-lsp-rails: #{fork_caller&.path}"
544+
block.call
545+
end
546+
end
547+
548+
# If no block is given, then only set the child process name (when `pid` is `nil`)
549+
pid = super
550+
$0 = "ruby-lsp-rails: #{fork_caller&.path}" unless pid
551+
pid
552+
end
553+
554+
Kernel.prepend(self)
555+
Process.singleton_class.prepend(self)
556+
end
557+
532558
if ARGV.first == "start"
533559
RubyLsp::Rails::Server.new(capabilities: JSON.parse(ARGV[1], symbolize_names: true)).start
534560
end

test/ruby_lsp_rails/server_test.rb

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,38 @@ def print_it!
268268
$> = original_stdout
269269
end
270270

271+
test "forked processes are named based on caller" do
272+
skip("Fork is not supported on Windows") if Gem.win_platform?
273+
274+
File.write("my_addon.rb", <<~RUBY)
275+
class MyServerAddon < RubyLsp::Rails::ServerAddon
276+
def name
277+
"MyAddon"
278+
end
279+
280+
def execute(request, params)
281+
pid = fork do
282+
# We can't directly send a message in theses tests because we're using a StringIO as stdout instead of the
283+
# actual pipe, which means that the child process doesn't have access to the same object
284+
File.write("process_name.txt", $0)
285+
end
286+
287+
Process.wait(pid)
288+
send_message({ process_name: File.read("process_name.txt") })
289+
File.delete("process_name.txt")
290+
end
291+
end
292+
RUBY
293+
294+
addon_path = File.expand_path("my_addon.rb")
295+
@server.execute("server_addon/register", server_addon_path: addon_path)
296+
@server.execute("server_addon/delegate", server_addon_name: "MyAddon", request_name: "dsl")
297+
298+
assert_equal(response, { process_name: "ruby-lsp-rails: #{addon_path}" })
299+
ensure
300+
FileUtils.rm("my_addon.rb")
301+
end
302+
271303
private
272304

273305
def response

0 commit comments

Comments
 (0)