Class: Spring::Application

Inherits:
Object
  • Object
show all
Includes:
ApplicationImpl
Defined in:
lib/spring-jruby/application.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from ApplicationImpl

#before_command, #eager_preload, #fork_child, #notify_manager_ready, #receive_streams, #reopen_streams, #reset_streams, #screen_attached?, #screen_move_to_bottom, #wait, #with_pty

Constructor Details

#initialize(manager, original_env) ⇒ Application

Returns a new instance of Application.



11
12
13
14
15
16
17
18
19
20
# File 'lib/spring-jruby/application.rb', line 11

def initialize(manager, original_env)
  @manager      = manager
  @original_env = original_env
  @spring_env   = Env.new
  @mutex        = Mutex.new
  @waiting      = Set.new
  @preloaded    = false
  @state        = :initialized
  @interrupt    = IO.pipe
end

Instance Attribute Details

#managerObject (readonly)

Returns the value of attribute manager.



9
10
11
# File 'lib/spring-jruby/application.rb', line 9

def manager
  @manager
end

#original_envObject (readonly)

Returns the value of attribute original_env.



9
10
11
# File 'lib/spring-jruby/application.rb', line 9

def original_env
  @original_env
end

#spring_envObject (readonly)

Returns the value of attribute spring_env.



9
10
11
# File 'lib/spring-jruby/application.rb', line 9

def spring_env
  @spring_env
end

#watcherObject (readonly)

Returns the value of attribute watcher.



9
10
11
# File 'lib/spring-jruby/application.rb', line 9

def watcher
  @watcher
end

Instance Method Details

#app_envObject



33
34
35
# File 'lib/spring-jruby/application.rb', line 33

def app_env
  ENV['RAILS_ENV']
end

#app_nameObject



37
38
39
# File 'lib/spring-jruby/application.rb', line 37

def app_name
  spring_env.app_name
end

#connect_databaseObject



248
249
250
# File 'lib/spring-jruby/application.rb', line 248

def connect_database
  ActiveRecord::Base.establish_connection if active_record_configured?
end

#disconnect_databaseObject



244
245
246
# File 'lib/spring-jruby/application.rb', line 244

def disconnect_database
  ActiveRecord::Base.remove_connection if active_record_configured?
end

#exitObject



211
212
213
214
215
216
# File 'lib/spring-jruby/application.rb', line 211

def exit
  state :exiting
  manager.shutdown(:RDWR)
  exit_if_finished
  sleep
end

#exit_if_finishedObject



218
219
220
221
222
# File 'lib/spring-jruby/application.rb', line 218

def exit_if_finished
  @mutex.synchronize {
    Kernel.exit if exiting? && @waiting.empty?
  }
end

#exiting?Boolean

Returns:



53
54
55
# File 'lib/spring-jruby/application.rb', line 53

def exiting?
  @state == :exiting
end

#initialized?Boolean

Returns:



65
66
67
# File 'lib/spring-jruby/application.rb', line 65

def initialized?
  @state == :initialized
end

#invoke_after_fork_callbacksObject



233
234
235
236
237
# File 'lib/spring-jruby/application.rb', line 233

def invoke_after_fork_callbacks
  Spring.after_fork_callbacks.each do |callback|
    callback.call
  end
end

#loaded_application_featuresObject



239
240
241
242
# File 'lib/spring-jruby/application.rb', line 239

def loaded_application_features
  root = Spring.application_root_path.to_s
  $LOADED_FEATURES.select { |f| f.start_with?(root) }
end

#log(message) ⇒ Object



41
42
43
# File 'lib/spring-jruby/application.rb', line 41

def log(message)
  spring_env.log "[application:#{app_env}] #{message}"
end

#preloadObject



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/spring-jruby/application.rb', line 75

def preload
  log "preloading app"

  begin
    require "spring-jruby/commands"
  ensure
    start_watcher
  end

  require Spring.application_root_path.join("config", "application")

  # config/environments/test.rb will have config.cache_classes = true. However
  # we want it to be false so that we can reload files. This is a hack to
  # override the effect of config.cache_classes = true. We can then actually
  # set config.cache_classes = false after loading the environment.
  Rails::Application.initializer :initialize_dependency_mechanism, group: :all do
    ActiveSupport::Dependencies.mechanism = :load
  end

  require Spring.application_root_path.join("config", "environment")

  @original_cache_classes = Rails.application.config.cache_classes
  Rails.application.config.cache_classes = false

  disconnect_database

  @preloaded = :success
rescue Exception => e
  @preloaded = :failure
  watcher.add e.backtrace.map { |line| line.match(/^(.*)\:\d+\:in /)[1] }
  raise e unless initialized?
ensure
  watcher.add loaded_application_features
  watcher.add Spring.gemfile, "#{Spring.gemfile}.lock"

  if defined?(Rails) && Rails.application
    watcher.add Rails.application.paths["config/initializers"]
    watcher.add Rails.application.paths["config/database"]
    if secrets_path = Rails.application.paths["config/secrets"]
      watcher.add secrets_path
    end
  end
end

#preload_failed?Boolean

Returns:



49
50
51
# File 'lib/spring-jruby/application.rb', line 49

def preload_failed?
  @preloaded == :failure
end

#preloaded?Boolean

Returns:



45
46
47
# File 'lib/spring-jruby/application.rb', line 45

def preloaded?
  @preloaded
end


271
272
273
274
275
# File 'lib/spring-jruby/application.rb', line 271

def print_exception(stream, error)
  first, rest = error.backtrace.first, error.backtrace.drop(1)
  stream.puts("#{first}: #{error} (#{error.class})")
  rest.each { |line| stream.puts("\tfrom #{line}") }
end

#runObject



119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/spring-jruby/application.rb', line 119

def run
  state :running
  notify_manager_ready

  loop do
    IO.select [manager, @interrupt.first]

    if terminating? || watcher_stale? || preload_failed?
      exit
    else
      serve IOWrapper.recv_io(manager, UNIXSocket).to_io
    end
  end
end

#serve(client) ⇒ Object



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# File 'lib/spring-jruby/application.rb', line 134

def serve(client)
  child_started = [false]
  log "got client"
  manager.puts

  stdout, stderr, stdin = streams = receive_streams(client)
  reopen_streams(streams)

  preload unless preloaded?

  args, env = JSON.load(client.read(client.gets.to_i)).values_at("args", "env")
  command   = Spring.command(args.shift)

  connect_database
  setup command

  if Rails.application.reloaders.any?(&:updated?)
    ActionDispatch::Reloader.cleanup!
    ActionDispatch::Reloader.prepare!
  end

  fork_child(client, streams, child_started) {
    IGNORE_SIGNALS.each { |sig| trap(sig, "DEFAULT") }
    trap("TERM", "DEFAULT")

    ARGV.replace(args)
    $0 = command.exec_name

    # Delete all env vars which are unchanged from before spring started
    original_env.each { |k, v| ENV.delete k if ENV[k] == v }

    # Load in the current env vars, except those which *were* changed when spring started
    env.each { |k, v| ENV[k] ||= v }

    # requiring is faster, so if config.cache_classes was true in
    # the environment's config file, then we can respect that from
    # here on as we no longer need constant reloading.
    if @original_cache_classes
      ActiveSupport::Dependencies.mechanism = :require
      Rails.application.config.cache_classes = true
    end

    connect_database
    srand

    invoke_after_fork_callbacks
    shush_backtraces

    before_command
    command.call
  }
rescue Exception => e
  Kernel.exit if exiting? && e.is_a?(SystemExit)

  log "exception: #{e}"
  manager.puts unless child_started[0]

  if streams && !e.is_a?(SystemExit)
    print_exception(stderr || STDERR, e)
    streams.each(&:close)
  end

  client.puts(1) if child_started[0]
  client.close
end

#setup(command) ⇒ Object

The command might need to require some files in the main process so that they are cached. For example a test command wants to load the helper file once and have it cached.



227
228
229
230
231
# File 'lib/spring-jruby/application.rb', line 227

def setup(command)
  if command.setup
    watcher.add loaded_application_features # loaded features may have changed
  end
end

#shush_backtracesObject

This feels very naughty



253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
# File 'lib/spring-jruby/application.rb', line 253

def shush_backtraces
  Kernel.module_eval do
    old_raise = Kernel.method(:raise)
    remove_method :raise
    define_method :raise do |*args|
      begin
        old_raise.call(*args)
      ensure
        if $!
          lib = File.expand_path("..", __FILE__)
          $!.backtrace.reject! { |line| line.start_with?(lib) }
        end
      end
    end
    private :raise
  end
end

#start_watcherObject



69
70
71
72
73
# File 'lib/spring-jruby/application.rb', line 69

def start_watcher
  @watcher = Spring.watcher
  @watcher.on_stale { state! :watcher_stale }
  @watcher.start
end

#state(val) ⇒ Object



22
23
24
25
26
# File 'lib/spring-jruby/application.rb', line 22

def state(val)
  return if exiting?
  log "#{@state} -> #{val}"
  @state = val
end

#state!(val) ⇒ Object



28
29
30
31
# File 'lib/spring-jruby/application.rb', line 28

def state!(val)
  state val
  @interrupt.last.write "."
end

#terminateObject



200
201
202
203
204
205
206
207
208
209
# File 'lib/spring-jruby/application.rb', line 200

def terminate
  if exiting?
    # Ensure that we do not ignore subsequent termination attempts
    log "forced exit"
    @waiting.each { |pid| Process.kill("TERM", pid) }
    Kernel.exit
  else
    state! :terminating
  end
end

#terminating?Boolean

Returns:



57
58
59
# File 'lib/spring-jruby/application.rb', line 57

def terminating?
  @state == :terminating
end

#watcher_stale?Boolean

Returns:



61
62
63
# File 'lib/spring-jruby/application.rb', line 61

def watcher_stale?
  @state == :watcher_stale
end