From 781c6151d0061e765a1a40293d1a524c7c26f1f1 Mon Sep 17 00:00:00 2001 From: Kaan Ozkan Date: Fri, 6 Mar 2026 15:44:22 -0500 Subject: [PATCH] Run reload in a background thread to keep the server responsive On large Rails apps (e.g., Bourgeois), reload takes 10-20 seconds. During that time, the server can't process any other requests (model info, route info, associations), causing the editor to freeze. Run reload in a background thread and respond immediately. If another reload arrives while one is in progress, mark that a follow-up reload is needed. When the current reload finishes, it checks the flag and reloads once more to pick up changes that arrived mid-reload. This means N rapid reloads result in at most 2 actual reloads. Related: https://github.com/Shopify/team-ruby-dx/issues/1734 --- lib/ruby_lsp/ruby_lsp_rails/server.rb | 28 ++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/lib/ruby_lsp/ruby_lsp_rails/server.rb b/lib/ruby_lsp/ruby_lsp_rails/server.rb index e27341c5..1e5bc534 100644 --- a/lib/ruby_lsp/ruby_lsp_rails/server.rb +++ b/lib/ruby_lsp/ruby_lsp_rails/server.rb @@ -320,15 +320,25 @@ def execute(request, params) send_result(run_migrations) end when "reload" - with_progress("rails-reload", "Reloading Ruby LSP Rails instance") do - begin - ::Rails.application.reloader.reload! - send_result({ success: true }) - rescue StandardError => e - log_message( - "Request reload failed with #{e.class}:\n#{e.full_message(highlight: false)}", - ) - send_result({ success: false }) + # Respond immediately so callers are not blocked while the app reloads. + # Reload runs in a background thread to keep the server responsive to + # other requests (model, route, association, etc.). + # If a reload is already in progress, we mark that another reload is needed. + # When the current reload finishes, it checks the flag and reloads once more + # to pick up any changes that arrived mid-reload. This means N rapid reloads + # result in at most 2 actual reloads (first + one catch-up). + send_result({ success: true }) + if @reload_thread&.alive? + @reload_needed = true + else + @reload_thread = Thread.new do + loop do + @reload_needed = false + with_notification_error_handling("reload") do + ::Rails.application.reloader.reload! + end + break unless @reload_needed + end end end when "route_location"