Class: RackRabbit::Server

Inherits:
Object
  • Object
show all
Defined in:
lib/rack-rabbit/server.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options) ⇒ Server

Returns a new instance of Server.



23
24
25
26
27
28
29
30
# File 'lib/rack-rabbit/server.rb', line 23

def initialize(options)
  @config       = Config.new(options)
  @logger       = config.logger
  @server_pid   = $$
  @worker_pids  = []
  @killed_pids  = []
  @signals      = Signals.new
end

Instance Attribute Details

#appObject (readonly)




15
16
17
# File 'lib/rack-rabbit/server.rb', line 15

def app
  @app
end

#configObject (readonly)




15
16
17
# File 'lib/rack-rabbit/server.rb', line 15

def config
  @config
end

#killed_pidsObject (readonly)




15
16
17
# File 'lib/rack-rabbit/server.rb', line 15

def killed_pids
  @killed_pids
end

#loggerObject (readonly)




15
16
17
# File 'lib/rack-rabbit/server.rb', line 15

def logger
  @logger
end

#server_pidObject (readonly)




15
16
17
# File 'lib/rack-rabbit/server.rb', line 15

def server_pid
  @server_pid
end

#signalsObject (readonly)




15
16
17
# File 'lib/rack-rabbit/server.rb', line 15

def signals
  @signals
end

#worker_pidsObject (readonly)




15
16
17
# File 'lib/rack-rabbit/server.rb', line 15

def worker_pids
  @worker_pids
end

Instance Method Details

#check_pidObject



207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/rack-rabbit/server.rb', line 207

def check_pid
  pidfile = config.pidfile
  if pidfile
    case pid_status(pidfile)
    when :running, :not_owned
      logger.fatal "A server is already running. Check #{pidfile}"
      exit(1)
    when :dead
      File.delete(pidfile)
    end
  end
end

#daemonizeObject

DAEMONIZING, PID MANAGEMENT, and OUTPUT REDIRECTION



170
171
172
173
174
175
176
177
# File 'lib/rack-rabbit/server.rb', line 170

def daemonize
  exit if fork
  Process.setsid
  exit if fork
  Dir.chdir "/"
  redirect_output
  logger.master_pid = $$ if logger.respond_to?(:master_pid)  # inform logger of new master_pid so it can continue to distinguish between "SERVER" and "worker" in log preamble
end

#kill_all_workers(sig) ⇒ Object



132
133
134
# File 'lib/rack-rabbit/server.rb', line 132

def kill_all_workers(sig)
  kill_worker(sig, worker_pids.last) until worker_pids.empty?
end

#kill_random_worker(sig) ⇒ Object



128
129
130
# File 'lib/rack-rabbit/server.rb', line 128

def kill_random_worker(sig)
  kill_worker(sig, worker_pids.sample) # choose a random wpid
end

#kill_worker(sig, wpid) ⇒ Object



136
137
138
139
140
# File 'lib/rack-rabbit/server.rb', line 136

def kill_worker(sig, wpid)
  worker_pids.delete(wpid)
  killed_pids.push(wpid)
  Process.kill(sig, wpid)
end

#load_appObject

RACK APP HANDLING



250
251
252
253
254
255
256
257
258
# File 'lib/rack-rabbit/server.rb', line 250

def load_app
  inner_app, _options = RackRabbit.load_rack_app(config.rack_file)
  @app = Rack::Builder.new do
    use RackRabbit::Middleware::ProgramName
    run inner_app
  end.to_app
  logger.info "LOADED #{inner_app.name if inner_app.respond_to?(:name)} FROM #{config.rack_file}"
  @app
end

#maintain_worker_countObject



106
107
108
109
110
111
112
113
114
115
# File 'lib/rack-rabbit/server.rb', line 106

def maintain_worker_count
  unless shutting_down?
    diff = worker_pids.length - config.workers
    if diff > 0
      diff.times { kill_random_worker(:QUIT) }
    elsif diff < 0
      (-diff).times { spawn_worker }
    end
  end
end

#manage_workersObject




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/rack-rabbit/server.rb', line 65

def manage_workers
  while true

    maintain_worker_count

    sig = signals.pop   # BLOCKS until there is a signal
    case sig

    when :INT  then shutdown(:INT)
    when :QUIT then shutdown(:QUIT)
    when :TERM then shutdown(:TERM)

    when :HUP
      reload

    when :CHLD
      reap_workers

    when :TTIN
      config.workers [config.max_workers, config.workers + 1].min

    when :TTOU
      config.workers [config.min_workers, config.workers - 1].max

    else
      raise RuntimeError, "unknown signal #{sig}"

    end

  end
end

#pid_status(pidfile) ⇒ Object



220
221
222
223
224
225
226
227
228
229
230
# File 'lib/rack-rabbit/server.rb', line 220

def pid_status(pidfile)
  return :exited unless File.exists?(pidfile)
  pid = ::File.read(pidfile).to_i
  return :dead if pid == 0
  Process.kill(0, pid)
  :running
rescue Errno::ESRCH
  :dead
rescue Errno::EPERM
  :not_owned
end

#reap_workersObject



142
143
144
145
146
147
148
149
150
# File 'lib/rack-rabbit/server.rb', line 142

def reap_workers
  while true
    wpid = Process.waitpid(-1, Process::WNOHANG)
    return if wpid.nil?
    worker_pids.delete(wpid)
    killed_pids.delete(wpid)
  end
  rescue Errno::ECHILD
end

#redirect_outputObject



179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/rack-rabbit/server.rb', line 179

def redirect_output
  if logfile = config.logfile
    logfile = File.expand_path(logfile)
    FileUtils.mkdir_p(File.dirname(logfile), :mode => 0755)
    FileUtils.touch logfile
    File.chmod(0644, logfile)
    $stderr.reopen(logfile, 'a')
    $stdout.reopen($stderr)
    $stdout.sync = $stderr.sync = true
  else
    $stderr.reopen('/dev/null', 'a')
    $stdout.reopen($stderr)
  end
end

#reloadObject




99
100
101
102
103
104
# File 'lib/rack-rabbit/server.rb', line 99

def reload
  logger.info "RELOADING"
  config.reload
  load_app if config.preload_app
  kill_all_workers(:QUIT)  # they will respawn automatically
end

#runObject




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
# File 'lib/rack-rabbit/server.rb', line 34

def run

  check_pid

  if config.daemonize
    daemonize
  elsif config.logfile
    redirect_output
  end

  write_pid

  logger.info "RUNNING #{config.app_id} (#{config.rack_file}) #{'DAEMONIZED' if config.daemonize}"
  logger.info "  rabbit   : #{config.rabbit}"
  logger.info "  exchange : #{config.exchange} (#{config.exchange_type})" if config.exchange
  logger.info "  queue    : #{config.queue}"                              if config.queue
  logger.info "  route    : #{config.routing_key}"                        if config.routing_key
  logger.info "  workers  : #{config.workers}"
  logger.info "  preload  : true"              if config.preload_app
  logger.info "  logfile  : #{config.logfile}" unless config.logfile.nil?
  logger.info "  pidfile  : #{config.pidfile}" unless config.pidfile.nil?

  load_app if config.preload_app

  trap_server_signals
  manage_workers

end

#shutdown(sig) ⇒ Object




154
155
156
157
158
159
160
# File 'lib/rack-rabbit/server.rb', line 154

def shutdown(sig)
  @shutting_down = true
  kill_all_workers(sig)
  Process.waitall
  logger.info "#{RackRabbit.friendly_signal(sig)} server"
  exit
end

#shutting_down?Boolean

Returns:

  • (Boolean)


162
163
164
# File 'lib/rack-rabbit/server.rb', line 162

def shutting_down?
  @shutting_down
end

#spawn_workerObject



117
118
119
120
121
122
123
124
125
126
# File 'lib/rack-rabbit/server.rb', line 117

def spawn_worker
  config.before_fork(self)
  worker_pids << fork do
    signals.close
    load_app unless config.preload_app
    worker = Worker.new(config, app)
    config.after_fork(self, worker)
    worker.run
  end
end

#trap_server_signalsObject

SIGNAL HANDLING



236
237
238
239
240
241
242
243
244
# File 'lib/rack-rabbit/server.rb', line 236

def trap_server_signals

  [:HUP, :INT, :QUIT, :TERM, :CHLD, :TTIN, :TTOU].each do |sig|
    trap(sig) do
      signals.push(sig)
    end
  end

end

#write_pidObject



194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/rack-rabbit/server.rb', line 194

def write_pid
  pidfile = config.pidfile
  if pidfile
    begin
      File.open(pidfile, ::File::CREAT | ::File::EXCL | ::File::WRONLY){|f| f.write("#{Process.pid}") }
      at_exit { File.delete(pidfile) if File.exists?(pidfile) }
    rescue Errno::EEXIST
      check_pid
      retry
    end
  end
end