Class: Volt::ForkingServer
Instance Method Summary collapse
-
#boot_error(error) ⇒ Object
called from the child when the boot failes.
- #call(env) ⇒ Object
-
#call_on_child(env_base, env_other) ⇒ Object
When passing an object, Drb will not marshal it if any of its subobjects are not marshalable.
-
#initialize(server) ⇒ ForkingServer
constructor
A new instance of ForkingServer.
- #reload(changed_files) ⇒ Object
- #start_change_listener ⇒ Object
-
#start_child ⇒ Object
Start child forks off a child process and sets up a DRb connection to the child.
- #stop_change_listener ⇒ Object
- #stop_child ⇒ Object
-
#watch_for_parent_exit ⇒ Object
In the even the parent gets killed without at_exit running, we watch the pipe and close if the pipe gets closed.
Constructor Details
#initialize(server) ⇒ ForkingServer
Returns a new instance of ForkingServer.
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
# File 'lib/volt/server/forking_server.rb', line 18 def initialize(server) # A read write lock for accessing and creating the lock @child_lock = ReadWriteLock.new # Trap exit at_exit do # Only run on parent if @child_id puts 'Exiting...' @exiting = true stop_child end end @server = server start_child end |
Instance Method Details
#boot_error(error) ⇒ Object
called from the child when the boot failes. Sets up an error page rack app to show the user the error and handle reloading requests.
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/volt/server/forking_server.rb', line 99 def boot_error(error) msg = error.inspect if error.respond_to?(:backtrace) msg << "\n" + error.backtrace.join("\n") end Volt.logger.error(msg) # Only require when needed require 'cgi' @rack_app = Proc.new do path = File.join(File.dirname(__FILE__), "forking_server/boot_error.html.erb") html = File.read(path) error_page = ERB.new(html, nil, '-').result(binding) [500, {"Content-Type" => "text/html"}, error_page] end @dispatcher = ErrorDispatcher.new end |
#call(env) ⇒ Object
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 |
# File 'lib/volt/server/forking_server.rb', line 185 def call(env) @child_lock.with_read_lock do if @exiting [500, {}, 'Server Exiting'] else env_base = {} env_other = {} env.each_pair do |key, value| if [String, TrueClass, FalseClass, Array].include?(value.class) env_base.merge!(key => value) else env_other.merge!(key => value) end end status, headers, body_str = @server_proxy.call_on_child(env_base, env_other) [status, headers, StringIO.new(body_str)] end end end |
#call_on_child(env_base, env_other) ⇒ Object
When passing an object, Drb will not marshal it if any of its subobjects are not marshalable. So we split the marshable and not marshalbe objects then re-merge them so we get real copies of most values (which are needed in some cases) Then we merge them back into a new hash.
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 |
# File 'lib/volt/server/forking_server.rb', line 154 def call_on_child(env_base, env_other) env = env_base # TODO: this requires quite a few trips, there's probably a faster way # to handle this. env_other.each_pair do |key, value| env[key] = value end status, headers, body = @rack_app.call(env) # Extract the body to pass as a string. We need to do this # because after the call, the objects will be GC'ed, so we want # them to be able to be marshaled to be send over DRb. if body.respond_to?(:to_str) body_str = body else extracted_body = [] # Read the body.each do |str| extracted_body << str end body.close if body.respond_to?(:close) body_str = extracted_body.join end [status, headers, body_str] end |
#reload(changed_files) ⇒ Object
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 |
# File 'lib/volt/server/forking_server.rb', line 209 def reload(changed_files) # only reload the server code if a non-view file was changed server_code_changed = changed_files.any? { |path| File.extname(path) == '.rb' } msg = 'file changed, reloading' msg << ' server and' if server_code_changed msg << ' client...' Volt.logger.log_with_color(msg, :light_blue) begin SocketConnectionHandler.(nil, 'reload') rescue => e Volt.logger.error('Reload dispatch error: ') Volt.logger.error(e) end if server_code_changed @child_lock.with_write_lock do stop_child start_child end end end |
#start_change_listener ⇒ Object
234 235 236 237 238 239 240 241 242 243 |
# File 'lib/volt/server/forking_server.rb', line 234 def start_change_listener # Setup the listeners for file changes @listener = Listen.to("#{@server.app_path}/") do |modified, added, removed| Thread.new do # Run the reload in a new thread reload(modified + added + removed) end end @listener.start end |
#start_child ⇒ Object
Start child forks off a child process and sets up a DRb connection to the child. #start_child should be called from within the write lock.
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/volt/server/forking_server.rb', line 39 def start_child # Aquire the write lock, so we prevent anyone from using the child until # its setup or recreated. unless @drb_object # Get the id of the parent process, so we can wait for exit in the child # so the child can exit if the parent closes. @parent_id = Process.pid @reader, @writer = IO.pipe if @child_id = fork # running as parent @writer.close # Read the url from the child uri = @reader.gets.strip # Setup a drb object to the child DRb.start_service @drb_object = DRbObject.new_with_uri(uri) @server_proxy = @drb_object[0] @dispatcher_proxy = @drb_object[1] SocketConnectionHandler.dispatcher = @dispatcher_proxy start_change_listener else # Running as child @reader.close begin volt_app = @server.boot_volt @rack_app = volt_app.middleware # Set the drb object locally @dispatcher = Dispatcher.new(volt_app) rescue Exception => error boot_error(error) end drb_object = DRb.start_service('drbunix:', [self, @dispatcher]) @writer.puts(drb_object.uri) watch_for_parent_exit begin DRb.thread.join rescue Interrupt => e # Ignore interrupt exit end end end end |
#stop_change_listener ⇒ Object
245 246 247 |
# File 'lib/volt/server/forking_server.rb', line 245 def stop_change_listener @listener.stop end |
#stop_child ⇒ Object
120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
# File 'lib/volt/server/forking_server.rb', line 120 def stop_child # clear the drb object and kill the child process. if @drb_object begin @drb_object = nil DRb.stop_service @reader.close stop_change_listener Process.kill(9, @child_id) rescue => e puts "Stop Child Error: #{e.inspect}" end end end |
#watch_for_parent_exit ⇒ Object
In the even the parent gets killed without at_exit running, we watch the pipe and close if the pipe gets closed.
137 138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/volt/server/forking_server.rb', line 137 def watch_for_parent_exit Thread.new do loop do if @writer.closed? puts 'Parent process died' exit end sleep 3 end end end |