Class: Volt::ForkingServer

Inherits:
Object show all
Defined in:
lib/volt/server/forking_server.rb

Instance Method Summary collapse

Constructor Details

#initialize(server) ⇒ ForkingServer

Returns a new instance of ForkingServer.



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/volt/server/forking_server.rb', line 9

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

#call(env) ⇒ Object



120
121
122
123
124
125
126
127
128
129
130
# File 'lib/volt/server/forking_server.rb', line 120

def call(env)
  @child_lock.with_read_lock do
    if @exiting
      [500, {}, 'Server Exiting']
    else
      status, headers, body_str = @server_proxy.call_on_child(env)

      [status, headers, StringIO.new(body_str)]
    end
  end
end

#call_on_child(env) ⇒ Object



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/volt/server/forking_server.rb', line 97

def call_on_child(env)
  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



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
# File 'lib/volt/server/forking_server.rb', line 147

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'
  if server_code_changed
    msg << ' server and'
  end
  msg << ' client...'

  Volt.logger.log_with_color(msg, :light_blue)

  begin
    SocketConnectionHandler.send_message_all(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_listenerObject



174
175
176
177
178
179
180
181
182
183
# File 'lib/volt/server/forking_server.rb', line 174

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_childObject

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.



30
31
32
33
34
35
36
37
38
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
# File 'lib/volt/server/forking_server.rb', line 30

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

      @server.boot_volt
      @rack_app = @server.new_server

      # Set the drb object locally
      @dispatcher = Dispatcher.new
      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_listenerObject



185
186
187
# File 'lib/volt/server/forking_server.rb', line 185

def stop_change_listener
  @listener.stop
end

#stop_childObject



132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/volt/server/forking_server.rb', line 132

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_exitObject

In the even the parent gets killed without at_exit running, we watch the pipe and close if the pipe gets closed.



84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/volt/server/forking_server.rb', line 84

def watch_for_parent_exit
  Thread.new do
    loop do
      if @writer.closed?
        puts "Parent process died"
        exit
      end

      sleep 3
    end
  end
end