Skip to content

Commit 27a3728

Browse files
authored
Reduce file watching overhead from Rails (#555)
I noticed that we were spending a lot of time doing file IO operations coming from the listen gem. After some investigation, these appear to be coming from Rails' own file watching mechanisms, which spawn threads that will check if views are being modified to trigger rendering or reloading in the background. We currently don't have any functionality that depends on views being rendered. Additionally, the LSP already watches files, so even if we wanted to trigger view rendering, the language server would be in a better position to dictate which views to reload. This PR shuts down view file watching, which eliminates a good chunk of overhead that we were seeing in the Tapioca add-on. ### Approach I initially tried setting `config.file_watcher` to a no-op implementation, but that doesn't work. The reason is because, by the time we start running `server.rb`, Rails has already registered the file watchers and its callbacks, so changing the config won't have any real effect. The approach I took is to clear the file system hooks array from the Action View path registry. Those hooks are the ones that build the file watchers, spawning listen threads. By clearing that array, I can see the overhead disappear in my profiles.
1 parent 8d37218 commit 27a3728

File tree

1 file changed

+13
-3
lines changed

1 file changed

+13
-3
lines changed

lib/ruby_lsp/ruby_lsp_rails/server.rb

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@
44
require "json"
55
require "open3"
66

7-
# NOTE: We should avoid printing to stderr since it causes problems. We never read the standard error pipe from the
8-
# client, so it will become full and eventually hang or crash. Instead, return a response with an `error` key.
9-
107
module RubyLsp
118
module Rails
129
module Common
@@ -125,6 +122,7 @@ def initialize(stdout: $stdout, override_default_output_device: true)
125122

126123
def start
127124
load_routes
125+
clear_file_system_resolver_hooks
128126
send_result({ message: "ok", root: ::Rails.root.to_s })
129127

130128
while @running
@@ -302,6 +300,18 @@ def load_routes
302300
routes_reloader.execute_unless_loaded if routes_reloader&.respond_to?(:execute_unless_loaded)
303301
end
304302
end
303+
304+
# File system resolver hooks spawn file watcher threads which introduce unnecessary overhead since the LSP already
305+
# watches files. Since the Rails application is already booted by the time we reach this script, we can't no-op
306+
# the file watcher implementation. Instead, we clear the hooks to prevent the registered file watchers from being
307+
# instantiated
308+
def clear_file_system_resolver_hooks
309+
return unless defined?(::ActionView::PathRegistry)
310+
311+
with_notification_error_handling("clear_file_system_resolver_hooks") do
312+
::ActionView::PathRegistry.file_system_resolver_hooks.clear
313+
end
314+
end
305315
end
306316
end
307317
end

0 commit comments

Comments
 (0)