Skip to content

Commit cfcc46b

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

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: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,22 @@ 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+
fork_caller = caller_locations(1, 1)&.first
539+
540+
pid = super
541+
Process.setproctitle("ruby-lsp-rails: #{fork_caller.path}") if fork_caller && pid == 0
542+
pid
543+
end
544+
545+
Process.singleton_class.prepend(self)
546+
end
547+
532548
if ARGV.first == "start"
533549
RubyLsp::Rails::Server.new(capabilities: JSON.parse(ARGV[1], symbolize_names: true)).start
534550
end

test/ruby_lsp_rails/server_test.rb

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

273348
def response

0 commit comments

Comments
 (0)