Class: Spring::Application

Inherits:
Object
  • Object
show all
Defined in:
lib/spring/application.rb

Constant Summary collapse

WATCH_INTERVAL =
0.2

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(manager, watcher = Spring.application_watcher) ⇒ Application

Returns a new instance of Application.



17
18
19
20
21
22
23
24
25
# File 'lib/spring/application.rb', line 17

def initialize(manager, watcher = Spring.application_watcher)
  @manager = manager
  @watcher = watcher
  @setup   = Set.new

  @stdout = IO.new(STDOUT.fileno)
  @stderr = IO.new(STDERR.fileno)
  @stdin  = IO.new(STDIN.fileno)
end

Instance Attribute Details

#managerObject (readonly)

Returns the value of attribute manager.



15
16
17
# File 'lib/spring/application.rb', line 15

def manager
  @manager
end

#watcherObject (readonly)

Returns the value of attribute watcher.



15
16
17
# File 'lib/spring/application.rb', line 15

def watcher
  @watcher
end

Instance Method Details

#redirect_output(socket) ⇒ Object



97
98
99
100
101
102
103
104
105
106
107
# File 'lib/spring/application.rb', line 97

def redirect_output(socket)
  STDOUT.reopen socket.recv_io
  STDERR.reopen socket.recv_io
  STDIN.reopen  socket.recv_io

  yield
ensure
  STDOUT.reopen @stdout
  STDERR.reopen @stderr
  STDIN.reopen  @stdin
end

#runObject



46
47
48
49
50
51
52
53
54
55
# File 'lib/spring/application.rb', line 46

def run
  loop do
    watch_application

    client = manager.recv_io
    client.autoclose = false # prevent GC closing the FD

    serve UNIXSocket.for_fd(client.fileno)
  end
end

#serve(client) ⇒ Object



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/spring/application.rb', line 63

def serve(client)
  redirect_output(client) do
    args_length = client.gets.to_i
    args        = args_length.times.map { client.read(client.gets.to_i) }
    command     = Spring.command(args.shift)

    setup command

    ActionDispatch::Reloader.cleanup!
    ActionDispatch::Reloader.prepare!

    Process.wait(fork { command.call(args) })
  end
ensure
  client.puts
  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.

FIXME: The watcher.add_files will reset the watcher, which may mean that

previous changes to already-loaded files are missed.


87
88
89
90
91
92
93
94
95
# File 'lib/spring/application.rb', line 87

def setup(command)
  return if @setup.include?(command.class)
  @setup << command.class

  if command.respond_to?(:setup)
    command.setup
    watcher.add_files $LOADED_FEATURES # loaded features may have changed
  end
end

#startObject



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/spring/application.rb', line 27

def start
  require "./config/application"

  # The test environment has config.cache_classes = true set by default.
  # However, we don't want this to prevent us from performing class reloading,
  # so this gets around that.
  Rails::Application.initializer :initialize_dependency_mechanism, group: :all do
    ActiveSupport::Dependencies.mechanism = :load
  end

  require "./config/environment"

  watcher.add_files $LOADED_FEATURES
  watcher.add_files ["Gemfile", "Gemfile.lock"].map { |f| "#{Rails.root}/#{f}" }
  watcher.add_globs Rails.application.paths["config/initializers"].map { |p| "#{Rails.root}/#{p}/*.rb" }

  run
end

#watch_applicationObject



57
58
59
60
61
# File 'lib/spring/application.rb', line 57

def watch_application
  until IO.select([manager], [], [], WATCH_INTERVAL)
    exit if watcher.stale?
  end
end