Skip to content

Commit e3abaec

Browse files
committed
Automatically name forked processes based on callers
1 parent 5e5afe9 commit e3abaec

File tree

2 files changed

+91
-0
lines changed

2 files changed

+91
-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
@@ -542,6 +542,25 @@ def database_supports_indexing?(model)
542542
end
543543
end
544544

545+
# Patch fork to name processes based on the caller's file path. This is useful for figuring out what is creating more
546+
# child processes from the runtime server, so that we can optimize and more easily debug orphaned processes
547+
# @requires_ancestor: Kernel
548+
module ForkHook
549+
#: (*untyped) -> Integer?
550+
def _fork(*args)
551+
pid = super
552+
553+
if pid == 0
554+
fork_caller = caller_locations(1, 1)&.first
555+
Process.setproctitle("ruby-lsp-rails: #{fork_caller.path}") if fork_caller
556+
end
557+
558+
pid
559+
end
560+
561+
Process.singleton_class.prepend(self)
562+
end
563+
545564
if ARGV.first == "start"
546565
RubyLsp::Rails::Server.new(capabilities: JSON.parse(ARGV[1], symbolize_names: true)).start
547566
end

test/ruby_lsp_rails/server_test.rb

Lines changed: 72 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,76 @@ 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+
# ps_output = `ps -p #{Process.pid} -o comm=`.strip
277+
addon_path = File.expand_path("my_addon.rb")
278+
File.write(addon_path, <<~RUBY)
279+
class MyServerAddon < RubyLsp::Rails::ServerAddon
280+
def name
281+
"MyAddon"
282+
end
283+
284+
def execute(request, params)
285+
file = "process_name.txt"
286+
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+
pid = fork { File.write(file, `ps -p \#{Process.pid} -o comm=`.strip) }
290+
Process.wait(pid)
291+
292+
send_message({ process_name: File.read(file) })
293+
File.delete(file)
294+
end
295+
end
296+
RUBY
297+
298+
begin
299+
@server.execute("server_addon/register", server_addon_path: addon_path)
300+
@server.execute("server_addon/delegate", server_addon_name: "MyAddon", request_name: "dsl")
301+
assert_equal(response, { process_name: "ruby-lsp-rails: #{addon_path}" })
302+
ensure
303+
FileUtils.rm(addon_path)
304+
end
305+
end
306+
307+
test "forked processes with no block are named based on caller" do
308+
skip("Fork is not supported on Windows") if Gem.win_platform?
309+
310+
addon_path = File.expand_path("my_other_addon.rb")
311+
File.write(addon_path, <<~RUBY)
312+
class MyOtherServerAddon < RubyLsp::Rails::ServerAddon
313+
def name
314+
"MyOtherAddon"
315+
end
316+
317+
def execute(request, params)
318+
file = "other_process_name.txt"
319+
pid = fork
320+
321+
if pid
322+
Process.wait(pid)
323+
send_message({ process_name: File.read(file) })
324+
File.delete(file)
325+
else
326+
File.write(file, `ps -p \#{Process.pid} -o comm=`.strip)
327+
# Exit from the child process or else we're stuck in the infinite loop of the server
328+
exit!
329+
end
330+
end
331+
end
332+
RUBY
333+
334+
begin
335+
@server.execute("server_addon/register", server_addon_path: addon_path)
336+
@server.execute("server_addon/delegate", server_addon_name: "MyOtherAddon", request_name: "dsl")
337+
assert_equal(response, { process_name: "ruby-lsp-rails: #{addon_path}" })
338+
ensure
339+
FileUtils.rm(addon_path)
340+
end
341+
end
342+
271343
private
272344

273345
def response

0 commit comments

Comments
 (0)