|
1 | 1 | module Spring |
2 | | - class ApplicationManager |
3 | | - attr_reader :pid, :child, :app_env, :spring_env, :status |
4 | | - |
5 | | - def initialize(app_env) |
6 | | - @app_env = app_env |
7 | | - @spring_env = Env.new |
8 | | - @mutex = Mutex.new |
9 | | - @state = :running |
10 | | - end |
11 | | - |
12 | | - def log(message) |
13 | | - spring_env.log "[application_manager:#{app_env}] #{message}" |
14 | | - end |
15 | | - |
16 | | - # We're not using @mutex.synchronize to avoid the weird "<internal:prelude>:10" |
17 | | - # line which messes with backtraces in e.g. rspec |
18 | | - def synchronize |
19 | | - @mutex.lock |
20 | | - yield |
21 | | - ensure |
22 | | - @mutex.unlock |
23 | | - end |
24 | | - |
25 | | - def start |
26 | | - start_child |
27 | | - end |
28 | | - |
29 | | - def restart |
30 | | - return if @state == :stopping |
31 | | - start_child(true) |
32 | | - end |
33 | | - |
34 | | - def alive? |
35 | | - @pid |
36 | | - end |
37 | | - |
38 | | - def with_child |
39 | | - synchronize do |
40 | | - if alive? |
41 | | - begin |
42 | | - yield |
43 | | - rescue Errno::ECONNRESET, Errno::EPIPE |
44 | | - # The child has died but has not been collected by the wait thread yet, |
45 | | - # so start a new child and try again. |
46 | | - log "child dead; starting" |
47 | | - start |
48 | | - yield |
49 | | - end |
50 | | - else |
51 | | - log "child not running; starting" |
52 | | - start |
53 | | - yield |
54 | | - end |
55 | | - end |
56 | | - end |
57 | | - |
58 | | - # Returns the pid of the process running the command, or nil if the application process died. |
59 | | - def run(client) |
60 | | - with_child do |
61 | | - child.send_io client |
62 | | - child.gets or raise Errno::EPIPE |
63 | | - end |
64 | | - |
65 | | - pid = child.gets.to_i |
66 | | - |
67 | | - unless pid.zero? |
68 | | - log "got worker pid #{pid}" |
69 | | - pid |
70 | | - end |
71 | | - rescue Errno::ECONNRESET, Errno::EPIPE => e |
72 | | - log "#{e} while reading from child; returning no pid" |
73 | | - nil |
74 | | - ensure |
75 | | - client.close |
76 | | - end |
77 | | - |
78 | | - def stop |
79 | | - log "stopping" |
80 | | - @state = :stopping |
81 | | - |
82 | | - if pid |
83 | | - Process.kill('TERM', pid) |
84 | | - Process.wait(pid) |
85 | | - end |
86 | | - rescue Errno::ESRCH, Errno::ECHILD |
87 | | - # Don't care |
88 | | - end |
89 | | - |
90 | | - private |
91 | | - |
92 | | - def start_child(preload = false) |
93 | | - @child, child_socket = UNIXSocket.pair |
94 | | - |
95 | | - Bundler.with_clean_env do |
96 | | - @pid = Process.spawn( |
97 | | - { |
98 | | - "RAILS_ENV" => app_env, |
99 | | - "RACK_ENV" => app_env, |
100 | | - "SPRING_ORIGINAL_ENV" => JSON.dump(Spring::ORIGINAL_ENV), |
101 | | - "SPRING_PRELOAD" => preload ? "1" : "0" |
102 | | - }, |
103 | | - "ruby", |
104 | | - "-I", File.expand_path("../..", $LOADED_FEATURES.grep(/bundler\/setup\.rb$/).first), |
105 | | - "-I", File.expand_path("../..", __FILE__), |
106 | | - "-e", "require 'spring/application/boot'", |
107 | | - 3 => child_socket |
108 | | - ) |
109 | | - end |
110 | | - |
111 | | - start_wait_thread(pid, child) if child.gets |
112 | | - child_socket.close |
113 | | - end |
114 | | - |
115 | | - def start_wait_thread(pid, child) |
116 | | - Process.detach(pid) |
117 | | - |
118 | | - Thread.new { |
119 | | - # The recv can raise an ECONNRESET, killing the thread, but that's ok |
120 | | - # as if it does we're no longer interested in the child |
121 | | - loop do |
122 | | - IO.select([child]) |
123 | | - break if child.recv(1, Socket::MSG_PEEK).empty? |
124 | | - sleep 0.01 |
125 | | - end |
126 | | - |
127 | | - log "child #{pid} shutdown" |
128 | | - |
129 | | - synchronize { |
130 | | - if @pid == pid |
131 | | - @pid = nil |
132 | | - restart |
133 | | - end |
134 | | - } |
135 | | - } |
136 | | - end |
| 2 | + module ApplicationManager |
137 | 3 | end |
138 | 4 | end |
| 5 | + |
| 6 | +require 'spring/application_manager/fork_strategy' |
| 7 | +require 'spring/application_manager/pool_strategy' |
0 commit comments