Skip to content

Commit f2b1ff9

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

File tree

2 files changed

+99
-0
lines changed

2 files changed

+99
-0
lines changed

lib/ruby_lsp/ruby_lsp_rails/server.rb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,25 @@ 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 ForkHook
536+
#: (*untyped) -> Integer?
537+
def _fork(*args)
538+
pid = super
539+
540+
if pid == 0
541+
fork_caller = caller_locations(1, 1)&.first
542+
Process.setproctitle("ruby-lsp-rails: #{fork_caller.path}") if fork_caller
543+
end
544+
545+
pid
546+
end
547+
548+
Process.singleton_class.prepend(self)
549+
end
550+
532551
if ARGV.first == "start"
533552
RubyLsp::Rails::Server.new(capabilities: JSON.parse(ARGV[1], symbolize_names: true)).start
534553
end

test/ruby_lsp_rails/server_test.rb

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ class ServerTest < ActiveSupport::TestCase
88
setup do
99
@stdout = StringIO.new
1010
@stderr = StringIO.new
11+
RubyLsp::Rails::ServerAddon.instance_variable_set(:@server_addon_classes, [])
12+
RubyLsp::Rails::ServerAddon.instance_variable_set(:@server_addons, {})
1113
@server = RubyLsp::Rails::Server.new(stdout: @stdout, stderr: @stderr, override_default_output_device: false)
1214
end
1315

@@ -268,6 +270,84 @@ def print_it!
268270
$> = original_stdout
269271
end
270272

273+
test "forked processes are named based on caller" do
274+
skip("Fork is not supported on Windows") if Gem.win_platform?
275+
276+
addon_path = File.expand_path("my_addon.rb")
277+
File.write(addon_path, <<~RUBY)
278+
class MyServerAddon < RubyLsp::Rails::ServerAddon
279+
def name
280+
"MyAddon"
281+
end
282+
283+
def execute(request, params)
284+
parent_process_title = `ps -p \#{Process.pid} -o args=`.lines.last.strip
285+
file = "process_name.txt"
286+
pid = fork do
287+
# We can't directly send a message in these tests because we're using a StringIO as stdout instead of the
288+
# actual pipe, which means that the child process doesn't have access to the same object
289+
process_title = `ps -p \#{Process.pid} -o args=`.lines.last.strip
290+
File.write(file, process_title)
291+
end
292+
293+
Process.wait(pid)
294+
295+
parent_process_title_changed = `ps -p \#{Process.pid} -o args=`.lines.last.strip != parent_process_title
296+
send_message({ process_name: File.read(file), changed_parent_title: parent_process_title_changed })
297+
File.delete(file)
298+
end
299+
end
300+
RUBY
301+
302+
begin
303+
@server.execute("server_addon/register", server_addon_path: addon_path)
304+
@server.execute("server_addon/delegate", server_addon_name: "MyAddon", request_name: "dsl")
305+
assert_equal(response, { process_name: "ruby-lsp-rails: #{addon_path}", changed_parent_title: false })
306+
ensure
307+
FileUtils.rm(addon_path)
308+
end
309+
end
310+
311+
test "forked processes with no block are named based on caller" do
312+
skip("Fork is not supported on Windows") if Gem.win_platform?
313+
314+
addon_path = File.expand_path("my_other_addon.rb")
315+
File.write(addon_path, <<~RUBY)
316+
class MyOtherServerAddon < RubyLsp::Rails::ServerAddon
317+
def name
318+
"MyOtherAddon"
319+
end
320+
321+
def execute(request, params)
322+
parent_process_title = `ps -p \#{Process.pid} -o args=`.lines.last.strip
323+
file = "other_process_name.txt"
324+
pid = fork
325+
326+
if pid
327+
Process.wait(pid)
328+
parent_process_title_changed = `ps -p \#{Process.pid} -o args=`.lines.last.strip != parent_process_title
329+
send_message({ process_name: File.read(file), changed_parent_title: parent_process_title_changed })
330+
File.delete(file)
331+
else
332+
process_title = `ps -p \#{Process.pid} -o args=`.lines.last.strip
333+
File.write(file, process_title)
334+
335+
# Exit from the child process or else we're stuck in the infinite loop of the server
336+
exit!
337+
end
338+
end
339+
end
340+
RUBY
341+
342+
begin
343+
@server.execute("server_addon/register", server_addon_path: addon_path)
344+
@server.execute("server_addon/delegate", server_addon_name: "MyOtherAddon", request_name: "dsl")
345+
assert_equal(response, { process_name: "ruby-lsp-rails: #{addon_path}", changed_parent_title: false })
346+
ensure
347+
FileUtils.rm(addon_path)
348+
end
349+
end
350+
271351
private
272352

273353
def response

0 commit comments

Comments
 (0)