Skip to content

Commit

Permalink
Add Spring.spawn_on_env
Browse files Browse the repository at this point in the history
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
  • Loading branch information
gmcgibbon committed Dec 14, 2023
1 parent 8b285d5 commit d414b28
Show file tree
Hide file tree
Showing 6 changed files with 36 additions and 12 deletions.
5 changes: 5 additions & 0 deletions lib/spring/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 9 additions & 3 deletions lib/spring/application/boot.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
9 changes: 6 additions & 3 deletions lib/spring/application_manager.rb
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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] : []),
Expand Down
6 changes: 5 additions & 1 deletion lib/spring/client/run.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
4 changes: 4 additions & 0 deletions lib/spring/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 7 additions & 5 deletions lib/spring/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down

0 comments on commit d414b28

Please sign in to comment.