Skip to content

Commit 514dc0f

Browse files
committed
refactor
1 parent 69fc4ab commit 514dc0f

File tree

6 files changed

+308
-297
lines changed

6 files changed

+308
-297
lines changed

lib/spring/application.rb

+7-286
Original file line numberDiff line numberDiff line change
@@ -2,294 +2,15 @@
22
require "set"
33
require "pty"
44
require "spring/platform"
5+
require "spring/application/base"
6+
require "spring/application/pool_strategy"
7+
require "spring/application/fork_strategy"
58

69
module Spring
7-
class Application
8-
if Spring.fork?
9-
require 'spring/application/fork_strategy'
10-
include ForkStrategy
11-
else
12-
require 'spring/application/pool_strategy'
13-
include PoolStrategy
14-
end
15-
attr_reader :manager, :watcher, :spring_env, :original_env
16-
17-
def initialize(manager, original_env)
18-
@manager = manager
19-
@original_env = original_env
20-
@spring_env = Env.new
21-
@mutex = Mutex.new
22-
@waiting = Set.new
23-
@preloaded = false
24-
@state = :initialized
25-
@interrupt = IO.pipe
26-
end
27-
28-
def state(val)
29-
return if exiting?
30-
log "#{@state} -> #{val}"
31-
@state = val
32-
end
33-
34-
def state!(val)
35-
state val
36-
@interrupt.last.write "."
37-
end
38-
39-
def app_env
40-
ENV['RAILS_ENV']
41-
end
42-
43-
def app_name
44-
spring_env.app_name
45-
end
46-
47-
def log(message)
48-
spring_env.log "[application:#{app_env}] #{message}"
49-
end
50-
51-
def preloaded?
52-
@preloaded
53-
end
54-
55-
def preload_failed?
56-
@preloaded == :failure
57-
end
58-
59-
def exiting?
60-
@state == :exiting
61-
end
62-
63-
def terminating?
64-
@state == :terminating
65-
end
66-
67-
def watcher_stale?
68-
@state == :watcher_stale
69-
end
70-
71-
def initialized?
72-
@state == :initialized
73-
end
74-
75-
def start_watcher
76-
@watcher = Spring.watcher
77-
@watcher.on_stale { state! :watcher_stale }
78-
@watcher.start
79-
end
80-
81-
def preload
82-
log "preloading app"
83-
84-
begin
85-
require "spring/commands"
86-
ensure
87-
start_watcher
88-
end
89-
90-
require Spring.application_root_path.join("config", "application")
91-
92-
# config/environments/test.rb will have config.cache_classes = true. However
93-
# we want it to be false so that we can reload files. This is a hack to
94-
# override the effect of config.cache_classes = true. We can then actually
95-
# set config.cache_classes = false after loading the environment.
96-
Rails::Application.initializer :initialize_dependency_mechanism, group: :all do
97-
ActiveSupport::Dependencies.mechanism = :load
98-
end
99-
100-
require Spring.application_root_path.join("config", "environment")
101-
102-
@original_cache_classes = Rails.application.config.cache_classes
103-
Rails.application.config.cache_classes = false
104-
105-
disconnect_database
106-
107-
@preloaded = :success
108-
rescue Exception => e
109-
@preloaded = :failure
110-
watcher.add e.backtrace.map { |line| line[/^(.*)\:\d+/, 1] }
111-
raise e unless initialized?
112-
ensure
113-
watcher.add loaded_application_features
114-
watcher.add Spring.gemfile, "#{Spring.gemfile}.lock"
115-
116-
if defined?(Rails) && Rails.application
117-
watcher.add Rails.application.paths["config/initializers"]
118-
watcher.add Rails.application.paths["config/database"]
119-
if secrets_path = Rails.application.paths["config/secrets"]
120-
watcher.add secrets_path
121-
end
122-
end
123-
end
124-
125-
def run
126-
state :running
127-
manager.puts Process.pid
128-
129-
loop do
130-
IO.select [manager, @interrupt.first]
131-
132-
if terminating? || watcher_stale? || preload_failed?
133-
exit
134-
else
135-
serve manager.recv_io(UNIXSocket)
136-
end
137-
end
138-
end
139-
140-
def serve(client)
141-
app_started = [false]
142-
log "got client"
143-
manager.puts
144-
145-
stdout, stderr, stdin = streams = 3.times.map { client.recv_io }
146-
[STDOUT, STDERR, STDIN].zip(streams).each { |a, b| a.reopen(b) }
147-
148-
preload unless preloaded?
149-
150-
args, env = JSON.load(client.read(client.gets.to_i)).values_at("args", "env")
151-
command = Spring.command(args.shift)
152-
153-
connect_database
154-
setup command
155-
156-
if Rails.application.reloaders.any?(&:updated?)
157-
ActionDispatch::Reloader.cleanup!
158-
ActionDispatch::Reloader.prepare!
159-
end
160-
161-
start_app(client, streams, app_started) {
162-
IGNORE_SIGNALS.each { |sig| trap(sig, "DEFAULT") }
163-
trap("TERM", "DEFAULT")
164-
165-
STDERR.puts "Running via Spring preloader in process #{Process.pid}" unless Spring.quiet
166-
167-
ARGV.replace(args)
168-
$0 = command.exec_name
169-
170-
# Delete all env vars which are unchanged from before spring started
171-
original_env.each { |k, v| ENV.delete k if ENV[k] == v }
172-
173-
# Load in the current env vars, except those which *were* changed when spring started
174-
env.each { |k, v| ENV[k] ||= v }
175-
176-
# requiring is faster, so if config.cache_classes was true in
177-
# the environment's config file, then we can respect that from
178-
# here on as we no longer need constant reloading.
179-
if @original_cache_classes
180-
ActiveSupport::Dependencies.mechanism = :require
181-
Rails.application.config.cache_classes = true
182-
end
183-
184-
connect_database
185-
srand
186-
187-
invoke_after_fork_callbacks
188-
shush_backtraces
189-
190-
command.call
191-
}
192-
rescue Exception => e
193-
Kernel.exit if exiting? && e.is_a?(SystemExit)
194-
195-
log "exception: #{e}"
196-
manager.puts unless app_started[0]
197-
198-
if streams && !e.is_a?(SystemExit)
199-
print_exception(stderr, e)
200-
streams.each(&:close)
201-
end
202-
203-
client.puts(1) if app_started[0]
204-
client.close
205-
end
206-
207-
def terminate
208-
if exiting?
209-
# Ensure that we do not ignore subsequent termination attempts
210-
log "forced exit"
211-
@waiting.each { |pid| Process.kill("TERM", pid) }
212-
Kernel.exit
213-
else
214-
state! :terminating
215-
end
216-
end
217-
218-
def exit
219-
state :exiting
220-
manager.shutdown(:RDWR)
221-
exit_if_finished
222-
sleep
223-
end
224-
225-
def exit_if_finished
226-
@mutex.synchronize {
227-
Kernel.exit if exiting? && @waiting.empty?
228-
}
229-
end
230-
231-
# The command might need to require some files in the
232-
# main process so that they are cached. For example a test command wants to
233-
# load the helper file once and have it cached.
234-
def setup(command)
235-
if command.setup
236-
watcher.add loaded_application_features # loaded features may have changed
237-
end
238-
end
239-
240-
def invoke_after_fork_callbacks
241-
Spring.after_fork_callbacks.each do |callback|
242-
callback.call
243-
end
244-
end
245-
246-
def loaded_application_features
247-
root = Spring.application_root_path.to_s
248-
$LOADED_FEATURES.select { |f| f.start_with?(root) }
249-
end
250-
251-
def disconnect_database
252-
ActiveRecord::Base.remove_connection if active_record_configured?
253-
end
254-
255-
def connect_database
256-
ActiveRecord::Base.establish_connection if active_record_configured?
257-
end
258-
259-
# This feels very naughty
260-
def shush_backtraces
261-
Kernel.module_eval do
262-
old_raise = Kernel.method(:raise)
263-
remove_method :raise
264-
define_method :raise do |*args|
265-
begin
266-
old_raise.call(*args)
267-
ensure
268-
if $!
269-
lib = File.expand_path("..", __FILE__)
270-
$!.backtrace.reject! { |line| line.start_with?(lib) }
271-
end
272-
end
273-
end
274-
private :raise
275-
end
276-
end
277-
278-
def print_exception(stream, error)
279-
first, rest = error.backtrace.first, error.backtrace.drop(1)
280-
stream.puts("#{first}: #{error} (#{error.class})")
281-
rest.each { |line| stream.puts("\tfrom #{line}") }
282-
end
283-
284-
def reset_streams
285-
[STDOUT, STDERR].each { |stream| stream.reopen(spring_env.log_file) }
286-
STDIN.reopen("/dev/null")
287-
end
288-
289-
private
290-
291-
def active_record_configured?
292-
defined?(ActiveRecord::Base) && ActiveRecord::Base.configurations.any?
10+
module Application
11+
def self.create(*args)
12+
strategy = Spring.fork? ? ForkStrategy : PoolStrategy
13+
strategy.new(*args)
29314
end
29415
end
29516
end

0 commit comments

Comments
 (0)