Skip to content

Commit

Permalink
Reduce file watching overhead from Rails (#555)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
vinistock authored Jan 15, 2025
1 parent 8d37218 commit 27a3728
Showing 1 changed file with 13 additions and 3 deletions.
16 changes: 13 additions & 3 deletions lib/ruby_lsp/ruby_lsp_rails/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@
require "json"
require "open3"

# NOTE: We should avoid printing to stderr since it causes problems. We never read the standard error pipe from the
# client, so it will become full and eventually hang or crash. Instead, return a response with an `error` key.

module RubyLsp
module Rails
module Common
Expand Down Expand Up @@ -125,6 +122,7 @@ def initialize(stdout: $stdout, override_default_output_device: true)

def start
load_routes
clear_file_system_resolver_hooks
send_result({ message: "ok", root: ::Rails.root.to_s })

while @running
Expand Down Expand Up @@ -302,6 +300,18 @@ def load_routes
routes_reloader.execute_unless_loaded if routes_reloader&.respond_to?(:execute_unless_loaded)
end
end

# File system resolver hooks spawn file watcher threads which introduce unnecessary overhead since the LSP already
# watches files. Since the Rails application is already booted by the time we reach this script, we can't no-op
# the file watcher implementation. Instead, we clear the hooks to prevent the registered file watchers from being
# instantiated
def clear_file_system_resolver_hooks
return unless defined?(::ActionView::PathRegistry)

with_notification_error_handling("clear_file_system_resolver_hooks") do
::ActionView::PathRegistry.file_system_resolver_hooks.clear
end
end
end
end
end
Expand Down

0 comments on commit 27a3728

Please sign in to comment.