From d414b280264745840cffea07f16fd65ea076bb7f Mon Sep 17 00:00:00 2001 From: Gannon McGibbon Date: Tue, 21 Nov 2023 18:53:52 -0600 Subject: [PATCH] Add Spring.spawn_on_env Currently, the only way Spring can create multiple applications for a given environment variable value is by changing the `SPRING_APPLICATION_ID`. This has the tradeoff of not being visible in `bin/spring status` calls, and being treated as a separate application, when it actually isn't. You might want to run an application with and without certain features enabled in the same rails env, and with this patch you can do that and have Spring treat these as the same app. Configuration must be set in config/spring_client.rb --- lib/spring/application.rb | 5 +++++ lib/spring/application/boot.rb | 12 +++++++++--- lib/spring/application_manager.rb | 9 ++++++--- lib/spring/client/run.rb | 6 +++++- lib/spring/configuration.rb | 4 ++++ lib/spring/server.rb | 12 +++++++----- 6 files changed, 36 insertions(+), 12 deletions(-) diff --git a/lib/spring/application.rb b/lib/spring/application.rb index 97836399..6fdbbb3a 100644 --- a/lib/spring/application.rb +++ b/lib/spring/application.rb @@ -28,6 +28,11 @@ def state!(val) @interrupt.last.write "." end + def spawn_env + env = JSON.load(ENV["SPRING_SPAWN_ENV"].dup).map { |key, value| "#{key}=#{value}" } + env.join(", ") if env.any? + end + def app_env ENV['RAILS_ENV'] end diff --git a/lib/spring/application/boot.rb b/lib/spring/application/boot.rb index 8510b886..f48eed8e 100644 --- a/lib/spring/application/boot.rb +++ b/lib/spring/application/boot.rb @@ -11,9 +11,15 @@ Signal.trap("TERM") { app.terminate } -Spring::ProcessTitleUpdater.run { |distance| - "spring app | #{app.app_name} | started #{distance} ago | #{app.app_env} mode" -} +Spring::ProcessTitleUpdater.run do |distance| + attributes = [ + app.app_name, + "started #{distance} ago", + "#{app.app_env} mode", + app.spawn_env, + ].compact + "spring app | #{attributes.join(" | ")}" +end app.eager_preload if ENV.delete("SPRING_PRELOAD") == "1" app.run diff --git a/lib/spring/application_manager.rb b/lib/spring/application_manager.rb index c759fba7..34c14dac 100644 --- a/lib/spring/application_manager.rb +++ b/lib/spring/application_manager.rb @@ -1,9 +1,10 @@ module Spring class ApplicationManager - attr_reader :pid, :child, :app_env, :spring_env, :status + attr_reader :pid, :child, :app_env, :spawn_env, :spring_env, :status - def initialize(app_env, spring_env) + def initialize(app_env, spawn_env, spring_env) @app_env = app_env + @spawn_env = spawn_env @spring_env = spring_env @mutex = Mutex.new @state = :running @@ -100,7 +101,9 @@ def start_child(preload = false) "RAILS_ENV" => app_env, "RACK_ENV" => app_env, "SPRING_ORIGINAL_ENV" => JSON.dump(Spring::ORIGINAL_ENV), - "SPRING_PRELOAD" => preload ? "1" : "0" + "SPRING_PRELOAD" => preload ? "1" : "0", + "SPRING_SPAWN_ENV" => JSON.dump(spawn_env), + **spawn_env, }, "ruby", *(bundler_dir != RbConfig::CONFIG["rubylibdir"] ? ["-I", bundler_dir] : []), diff --git a/lib/spring/client/run.rb b/lib/spring/client/run.rb index c5fe1dd5..398a8367 100644 --- a/lib/spring/client/run.rb +++ b/lib/spring/client/run.rb @@ -132,7 +132,7 @@ def verify_server_version def connect_to_application(client) server.send_io client - send_json server, "args" => args, "default_rails_env" => default_rails_env + send_json server, "args" => args, "default_rails_env" => default_rails_env, "spawn_env" => spawn_env if IO.select([server], [], [], CONNECT_TIMEOUT) server.gets or raise CommandNotFound @@ -232,6 +232,10 @@ def send_json(socket, data) def default_rails_env ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development' end + + def spawn_env + ENV.slice(*Spring.spawn_on_env) + end end end end diff --git a/lib/spring/configuration.rb b/lib/spring/configuration.rb index cbd3d5e3..cf7a2118 100644 --- a/lib/spring/configuration.rb +++ b/lib/spring/configuration.rb @@ -32,6 +32,10 @@ def after_fork(&block) after_fork_callbacks << block end + def spawn_on_env + @spawn_on_env ||= [] + end + def verify_environment application_root_path end diff --git a/lib/spring/server.rb b/lib/spring/server.rb index c3ab20e6..dbf43f4d 100644 --- a/lib/spring/server.rb +++ b/lib/spring/server.rb @@ -19,7 +19,9 @@ def self.boot(options = {}) def initialize(options = {}) @foreground = options.fetch(:foreground, false) @env = options[:env] || default_env - @applications = Hash.new { |h, k| h[k] = ApplicationManager.new(k, env) } + @applications = Hash.new do |hash, key| + hash[key] = ApplicationManager.new(*key, env) + end @pidfile = env.pidfile_path.open('a') @mutex = Mutex.new end @@ -57,12 +59,12 @@ def serve(client) app_client = client.recv_io command = JSON.load(client.read(client.gets.to_i)) - args, default_rails_env = command.values_at('args', 'default_rails_env') + args, default_rails_env, spawn_env = command.values_at('args', 'default_rails_env', 'spawn_env') if Spring.command?(args.first) log "running command #{args.first}" client.puts - client.puts @applications[rails_env_for(args, default_rails_env)].run(app_client) + client.puts @applications[rails_env_for(args, default_rails_env, spawn_env)].run(app_client) else log "command not found #{args.first}" client.close @@ -73,8 +75,8 @@ def serve(client) redirect_output end - def rails_env_for(args, default_rails_env) - Spring.command(args.first).env(args.drop(1)) || default_rails_env + def rails_env_for(args, default_rails_env, spawn_env) + [Spring.command(args.first).env(args.drop(1)) || default_rails_env, spawn_env] end # Boot the server into the process group of the current session.