unicorn-simon 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. checksums.yaml +7 -0
  2. data/.CHANGELOG.old +25 -0
  3. data/.document +28 -0
  4. data/.gitattributes +5 -0
  5. data/.gitignore +25 -0
  6. data/.mailmap +26 -0
  7. data/.manifest +156 -0
  8. data/.olddoc.yml +18 -0
  9. data/Application_Timeouts +77 -0
  10. data/CONTRIBUTORS +35 -0
  11. data/COPYING +674 -0
  12. data/DESIGN +95 -0
  13. data/Documentation/.gitignore +5 -0
  14. data/Documentation/GNUmakefile +30 -0
  15. data/Documentation/unicorn.1.txt +187 -0
  16. data/Documentation/unicorn_rails.1.txt +175 -0
  17. data/FAQ +70 -0
  18. data/GIT-VERSION-FILE +1 -0
  19. data/GIT-VERSION-GEN +39 -0
  20. data/GNUmakefile +253 -0
  21. data/HACKING +120 -0
  22. data/ISSUES +90 -0
  23. data/KNOWN_ISSUES +79 -0
  24. data/LATEST +30 -0
  25. data/LICENSE +67 -0
  26. data/Links +56 -0
  27. data/NEWS +2465 -0
  28. data/PHILOSOPHY +139 -0
  29. data/README +138 -0
  30. data/Rakefile +16 -0
  31. data/SIGNALS +123 -0
  32. data/Sandbox +104 -0
  33. data/TODO +3 -0
  34. data/TUNING +119 -0
  35. data/archive/.gitignore +3 -0
  36. data/archive/slrnpull.conf +4 -0
  37. data/bin/unicorn +126 -0
  38. data/bin/unicorn_rails +209 -0
  39. data/examples/big_app_gc.rb +2 -0
  40. data/examples/echo.ru +27 -0
  41. data/examples/init.sh +102 -0
  42. data/examples/logger_mp_safe.rb +25 -0
  43. data/examples/logrotate.conf +44 -0
  44. data/examples/nginx.conf +155 -0
  45. data/examples/unicorn.conf.minimal.rb +13 -0
  46. data/examples/unicorn.conf.rb +110 -0
  47. data/examples/unicorn.socket +11 -0
  48. data/examples/unicorn@.service +33 -0
  49. data/ext/unicorn_http/CFLAGS +13 -0
  50. data/ext/unicorn_http/c_util.h +124 -0
  51. data/ext/unicorn_http/common_field_optimization.h +111 -0
  52. data/ext/unicorn_http/ext_help.h +62 -0
  53. data/ext/unicorn_http/extconf.rb +11 -0
  54. data/ext/unicorn_http/global_variables.h +97 -0
  55. data/ext/unicorn_http/httpdate.c +78 -0
  56. data/ext/unicorn_http/unicorn_http.c +4274 -0
  57. data/ext/unicorn_http/unicorn_http.rl +980 -0
  58. data/ext/unicorn_http/unicorn_http_common.rl +76 -0
  59. data/lib/unicorn/app/old_rails/static.rb +59 -0
  60. data/lib/unicorn/app/old_rails.rb +35 -0
  61. data/lib/unicorn/cgi_wrapper.rb +147 -0
  62. data/lib/unicorn/configurator.rb +664 -0
  63. data/lib/unicorn/const.rb +21 -0
  64. data/lib/unicorn/http_request.rb +122 -0
  65. data/lib/unicorn/http_response.rb +60 -0
  66. data/lib/unicorn/http_server.rb +824 -0
  67. data/lib/unicorn/launcher.rb +62 -0
  68. data/lib/unicorn/oob_gc.rb +82 -0
  69. data/lib/unicorn/preread_input.rb +33 -0
  70. data/lib/unicorn/socket_helper.rb +195 -0
  71. data/lib/unicorn/stream_input.rb +146 -0
  72. data/lib/unicorn/tee_input.rb +133 -0
  73. data/lib/unicorn/tmpio.rb +27 -0
  74. data/lib/unicorn/util.rb +90 -0
  75. data/lib/unicorn/version.rb +1 -0
  76. data/lib/unicorn/worker.rb +140 -0
  77. data/lib/unicorn.rb +123 -0
  78. data/man/man1/unicorn.1 +221 -0
  79. data/man/man1/unicorn_rails.1 +212 -0
  80. data/setup.rb +1586 -0
  81. data/t/.gitignore +4 -0
  82. data/t/GNUmakefile +74 -0
  83. data/t/README +42 -0
  84. data/t/bin/content-md5-put +36 -0
  85. data/t/bin/sha1sum.rb +17 -0
  86. data/t/bin/unused_listen +40 -0
  87. data/t/broken-app.ru +12 -0
  88. data/t/detach.ru +11 -0
  89. data/t/env.ru +3 -0
  90. data/t/fails-rack-lint.ru +5 -0
  91. data/t/heartbeat-timeout.ru +12 -0
  92. data/t/hijack.ru +43 -0
  93. data/t/listener_names.ru +4 -0
  94. data/t/my-tap-lib.sh +201 -0
  95. data/t/oob_gc.ru +20 -0
  96. data/t/oob_gc_path.ru +20 -0
  97. data/t/pid.ru +3 -0
  98. data/t/preread_input.ru +17 -0
  99. data/t/rack-input-tests.ru +21 -0
  100. data/t/t0000-http-basic.sh +50 -0
  101. data/t/t0001-reload-bad-config.sh +53 -0
  102. data/t/t0002-config-conflict.sh +49 -0
  103. data/t/t0002-parser-error.sh +94 -0
  104. data/t/t0003-working_directory.sh +51 -0
  105. data/t/t0004-heartbeat-timeout.sh +69 -0
  106. data/t/t0004-working_directory_broken.sh +24 -0
  107. data/t/t0005-working_directory_app.rb.sh +40 -0
  108. data/t/t0006-reopen-logs.sh +83 -0
  109. data/t/t0006.ru +13 -0
  110. data/t/t0007-working_directory_no_embed_cli.sh +44 -0
  111. data/t/t0008-back_out_of_upgrade.sh +110 -0
  112. data/t/t0009-broken-app.sh +56 -0
  113. data/t/t0009-winch_ttin.sh +59 -0
  114. data/t/t0010-reap-logging.sh +55 -0
  115. data/t/t0011-active-unix-socket.sh +79 -0
  116. data/t/t0012-reload-empty-config.sh +85 -0
  117. data/t/t0013-rewindable-input-false.sh +24 -0
  118. data/t/t0013.ru +12 -0
  119. data/t/t0014-rewindable-input-true.sh +24 -0
  120. data/t/t0014.ru +12 -0
  121. data/t/t0015-configurator-internals.sh +25 -0
  122. data/t/t0018-write-on-close.sh +23 -0
  123. data/t/t0019-max_header_len.sh +49 -0
  124. data/t/t0020-at_exit-handler.sh +49 -0
  125. data/t/t0021-process_detach.sh +29 -0
  126. data/t/t0022-listener_names-preload_app.sh +32 -0
  127. data/t/t0100-rack-input-tests.sh +124 -0
  128. data/t/t0116-client_body_buffer_size.sh +80 -0
  129. data/t/t0116.ru +16 -0
  130. data/t/t0200-rack-hijack.sh +30 -0
  131. data/t/t0300-no-default-middleware.sh +20 -0
  132. data/t/t9000-preread-input.sh +48 -0
  133. data/t/t9001-oob_gc.sh +47 -0
  134. data/t/t9002-oob_gc-path.sh +75 -0
  135. data/t/test-lib.sh +128 -0
  136. data/t/write-on-close.ru +11 -0
  137. data/test/aggregate.rb +15 -0
  138. data/test/benchmark/README +50 -0
  139. data/test/benchmark/dd.ru +18 -0
  140. data/test/benchmark/stack.ru +8 -0
  141. data/test/exec/README +5 -0
  142. data/test/exec/test_exec.rb +1099 -0
  143. data/test/test_helper.rb +298 -0
  144. data/test/unit/test_configurator.rb +175 -0
  145. data/test/unit/test_droplet.rb +28 -0
  146. data/test/unit/test_http_parser.rb +886 -0
  147. data/test/unit/test_http_parser_ng.rb +633 -0
  148. data/test/unit/test_request.rb +182 -0
  149. data/test/unit/test_response.rb +111 -0
  150. data/test/unit/test_server.rb +268 -0
  151. data/test/unit/test_signals.rb +188 -0
  152. data/test/unit/test_socket_helper.rb +197 -0
  153. data/test/unit/test_stream_input.rb +203 -0
  154. data/test/unit/test_tee_input.rb +304 -0
  155. data/test/unit/test_upload.rb +306 -0
  156. data/test/unit/test_util.rb +105 -0
  157. data/unicorn.gemspec +50 -0
  158. metadata +310 -0
@@ -0,0 +1,824 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ # This is the process manager of Unicorn. This manages worker
4
+ # processes which in turn handle the I/O and application process.
5
+ # Listener sockets are started in the master process and shared with
6
+ # forked worker children.
7
+ #
8
+ # Users do not need to know the internals of this class, but reading the
9
+ # {source}[https://bogomips.org/unicorn.git/tree/lib/unicorn/http_server.rb]
10
+ # is education for programmers wishing to learn how unicorn works.
11
+ # See Unicorn::Configurator for information on how to configure unicorn.
12
+ class Unicorn::HttpServer
13
+ # :stopdoc:
14
+ attr_accessor :app, :timeout, :worker_processes,
15
+ :before_fork, :after_fork, :before_exec,
16
+ :listener_opts, :preload_app,
17
+ :orig_app, :config, :ready_pipe, :user
18
+
19
+ attr_reader :pid, :logger
20
+ include Unicorn::SocketHelper
21
+ include Unicorn::HttpResponse
22
+
23
+ # all bound listener sockets
24
+ # note: this is public used by raindrops, but not recommended for use
25
+ # in new projects
26
+ LISTENERS = []
27
+
28
+ # listeners we have yet to bind
29
+ NEW_LISTENERS = []
30
+
31
+ # :startdoc:
32
+ # We populate this at startup so we can figure out how to reexecute
33
+ # and upgrade the currently running instance of Unicorn
34
+ # This Hash is considered a stable interface and changing its contents
35
+ # will allow you to switch between different installations of Unicorn
36
+ # or even different installations of the same applications without
37
+ # downtime. Keys of this constant Hash are described as follows:
38
+ #
39
+ # * 0 - the path to the unicorn executable
40
+ # * :argv - a deep copy of the ARGV array the executable originally saw
41
+ # * :cwd - the working directory of the application, this is where
42
+ # you originally started Unicorn.
43
+ #
44
+ # To change your unicorn executable to a different path without downtime,
45
+ # you can set the following in your Unicorn config file, HUP and then
46
+ # continue with the traditional USR2 + QUIT upgrade steps:
47
+ #
48
+ # Unicorn::HttpServer::START_CTX[0] = "/home/bofh/2.3.0/bin/unicorn"
49
+ START_CTX = {
50
+ :argv => ARGV.map(&:dup),
51
+ 0 => $0.dup,
52
+ }
53
+ # We favor ENV['PWD'] since it is (usually) symlink aware for Capistrano
54
+ # and like systems
55
+ START_CTX[:cwd] = begin
56
+ a = File.stat(pwd = ENV['PWD'])
57
+ b = File.stat(Dir.pwd)
58
+ a.ino == b.ino && a.dev == b.dev ? pwd : Dir.pwd
59
+ rescue
60
+ Dir.pwd
61
+ end
62
+ # :stopdoc:
63
+
64
+ # Creates a working server on host:port (strange things happen if
65
+ # port isn't a Number). Use HttpServer::run to start the server and
66
+ # HttpServer.run.join to join the thread that's processing
67
+ # incoming requests on the socket.
68
+ def initialize(app, options = {})
69
+ @app = app
70
+ @request = Unicorn::HttpRequest.new
71
+ @reexec_pid = 0
72
+ options = options.dup
73
+ @ready_pipe = options.delete(:ready_pipe)
74
+ @init_listeners = options[:listeners] ? options[:listeners].dup : []
75
+ options[:use_defaults] = true
76
+ self.config = Unicorn::Configurator.new(options)
77
+ self.listener_opts = {}
78
+
79
+ # We use @self_pipe differently in the master and worker processes:
80
+ #
81
+ # * The master process never closes or reinitializes this once
82
+ # initialized. Signal handlers in the master process will write to
83
+ # it to wake up the master from IO.select in exactly the same manner
84
+ # djb describes in http://cr.yp.to/docs/selfpipe.html
85
+ #
86
+ # * The workers immediately close the pipe they inherit. See the
87
+ # Unicorn::Worker class for the pipe workers use.
88
+ @self_pipe = []
89
+ @workers = {} # hash maps PIDs to Workers
90
+ @sig_queue = [] # signal queue used for self-piping
91
+
92
+ # we try inheriting listeners first, so we bind them later.
93
+ # we don't write the pid file until we've bound listeners in case
94
+ # unicorn was started twice by mistake. Even though our #pid= method
95
+ # checks for stale/existing pid files, race conditions are still
96
+ # possible (and difficult/non-portable to avoid) and can be likely
97
+ # to clobber the pid if the second start was in quick succession
98
+ # after the first, so we rely on the listener binding to fail in
99
+ # that case. Some tests (in and outside of this source tree) and
100
+ # monitoring tools may also rely on pid files existing before we
101
+ # attempt to connect to the listener(s)
102
+ config.commit!(self, :skip => [:listeners, :pid])
103
+ @orig_app = app
104
+ # list of signals we care about and trap in master.
105
+ @queue_sigs = [
106
+ :WINCH, :QUIT, :INT, :TERM, :USR1, :USR2, :HUP, :TTIN, :TTOU ]
107
+ end
108
+
109
+ # Runs the thing. Returns self so you can run join on it
110
+ def start
111
+ inherit_listeners!
112
+ # this pipe is used to wake us up from select(2) in #join when signals
113
+ # are trapped. See trap_deferred.
114
+ @self_pipe.replace(Unicorn.pipe)
115
+ @master_pid = $$
116
+
117
+ # setup signal handlers before writing pid file in case people get
118
+ # trigger happy and send signals as soon as the pid file exists.
119
+ # Note that signals don't actually get handled until the #join method
120
+ @queue_sigs.each { |sig| trap(sig) { @sig_queue << sig; awaken_master } }
121
+ trap(:CHLD) { awaken_master }
122
+
123
+ # write pid early for Mongrel compatibility if we're not inheriting sockets
124
+ # This is needed for compatibility some Monit setups at least.
125
+ # This unfortunately has the side effect of clobbering valid PID if
126
+ # we upgrade and the upgrade breaks during preload_app==true && build_app!
127
+ self.pid = config[:pid]
128
+
129
+ build_app! if preload_app
130
+ bind_new_listeners!
131
+
132
+ spawn_missing_workers
133
+ self
134
+ end
135
+
136
+ # replaces current listener set with +listeners+. This will
137
+ # close the socket if it will not exist in the new listener set
138
+ def listeners=(listeners)
139
+ cur_names, dead_names = [], []
140
+ listener_names.each do |name|
141
+ if ?/ == name[0]
142
+ # mark unlinked sockets as dead so we can rebind them
143
+ (File.socket?(name) ? cur_names : dead_names) << name
144
+ else
145
+ cur_names << name
146
+ end
147
+ end
148
+ set_names = listener_names(listeners)
149
+ dead_names.concat(cur_names - set_names).uniq!
150
+
151
+ LISTENERS.delete_if do |io|
152
+ if dead_names.include?(sock_name(io))
153
+ (io.close rescue nil).nil? # true
154
+ else
155
+ set_server_sockopt(io, listener_opts[sock_name(io)])
156
+ false
157
+ end
158
+ end
159
+
160
+ (set_names - cur_names).each { |addr| listen(addr) }
161
+ end
162
+
163
+ def stdout_path=(path); redirect_io($stdout, path); end
164
+ def stderr_path=(path); redirect_io($stderr, path); end
165
+
166
+ def logger=(obj)
167
+ Unicorn::HttpRequest::DEFAULTS["rack.logger"] = @logger = obj
168
+ end
169
+
170
+ def clobber_pid(path)
171
+ unlink_pid_safe(@pid) if @pid
172
+ if path
173
+ fp = begin
174
+ tmp = "#{File.dirname(path)}/#{rand}.#$$"
175
+ File.open(tmp, File::RDWR|File::CREAT|File::EXCL, 0644)
176
+ rescue Errno::EEXIST
177
+ retry
178
+ end
179
+ fp.syswrite("#$$\n")
180
+ File.rename(fp.path, path)
181
+ fp.close
182
+ end
183
+ end
184
+
185
+ # sets the path for the PID file of the master process
186
+ def pid=(path)
187
+ if path
188
+ if x = valid_pid?(path)
189
+ return path if pid && path == pid && x == $$
190
+ if x == @reexec_pid && pid.end_with?('.oldbin')
191
+ logger.warn("will not set pid=#{path} while reexec-ed "\
192
+ "child is running PID:#{x}")
193
+ return
194
+ end
195
+ raise ArgumentError, "Already running on PID:#{x} " \
196
+ "(or pid=#{path} is stale)"
197
+ end
198
+ end
199
+
200
+ # rename the old pid if possible
201
+ if @pid && path
202
+ begin
203
+ File.rename(@pid, path)
204
+ rescue Errno::ENOENT, Errno::EXDEV
205
+ # a user may have accidentally removed the original,
206
+ # obviously cross-FS renames don't work, either.
207
+ clobber_pid(path)
208
+ end
209
+ else
210
+ clobber_pid(path)
211
+ end
212
+ @pid = path
213
+ end
214
+
215
+ # add a given address to the +listeners+ set, idempotently
216
+ # Allows workers to add a private, per-process listener via the
217
+ # after_fork hook. Very useful for debugging and testing.
218
+ # +:tries+ may be specified as an option for the number of times
219
+ # to retry, and +:delay+ may be specified as the time in seconds
220
+ # to delay between retries.
221
+ # A negative value for +:tries+ indicates the listen will be
222
+ # retried indefinitely, this is useful when workers belonging to
223
+ # different masters are spawned during a transparent upgrade.
224
+ def listen(address, opt = {}.merge(listener_opts[address] || {}))
225
+ address = config.expand_addr(address)
226
+ return if String === address && listener_names.include?(address)
227
+
228
+ delay = opt[:delay] || 0.5
229
+ tries = opt[:tries] || 5
230
+ begin
231
+ io = bind_listen(address, opt)
232
+ unless Kgio::TCPServer === io || Kgio::UNIXServer === io
233
+ io.autoclose = false
234
+ io = server_cast(io)
235
+ end
236
+ logger.info "listening on addr=#{sock_name(io)} fd=#{io.fileno}"
237
+ LISTENERS << io
238
+ io
239
+ rescue Errno::EADDRINUSE => err
240
+ logger.error "adding listener failed addr=#{address} (in use)"
241
+ raise err if tries == 0
242
+ tries -= 1
243
+ logger.error "retrying in #{delay} seconds " \
244
+ "(#{tries < 0 ? 'infinite' : tries} tries left)"
245
+ sleep(delay)
246
+ retry
247
+ rescue => err
248
+ logger.fatal "error adding listener addr=#{address}"
249
+ raise err
250
+ end
251
+ end
252
+
253
+ # monitors children and receives signals forever
254
+ # (or until a termination signal is sent). This handles signals
255
+ # one-at-a-time time and we'll happily drop signals in case somebody
256
+ # is signalling us too often.
257
+ def join
258
+ respawn = true
259
+ last_check = time_now
260
+
261
+ proc_name 'master'
262
+ logger.info "master process ready" # test_exec.rb relies on this message
263
+ if @ready_pipe
264
+ begin
265
+ @ready_pipe.syswrite($$.to_s)
266
+ rescue => e
267
+ logger.warn("grandparent died too soon?: #{e.message} (#{e.class})")
268
+ end
269
+ @ready_pipe = @ready_pipe.close rescue nil
270
+ end
271
+ begin
272
+ reap_all_workers
273
+ case @sig_queue.shift
274
+ when nil
275
+ # avoid murdering workers after our master process (or the
276
+ # machine) comes out of suspend/hibernation
277
+ if (last_check + @timeout) >= (last_check = time_now)
278
+ sleep_time = murder_lazy_workers
279
+ else
280
+ sleep_time = @timeout/2.0 + 1
281
+ @logger.debug("waiting #{sleep_time}s after suspend/hibernation")
282
+ end
283
+ maintain_worker_count if respawn
284
+ master_sleep(sleep_time)
285
+ when :QUIT # graceful shutdown
286
+ break
287
+ when :TERM, :INT # immediate shutdown
288
+ stop(false)
289
+ break
290
+ when :USR1 # rotate logs
291
+ logger.info "master reopening logs..."
292
+ Unicorn::Util.reopen_logs
293
+ logger.info "master done reopening logs"
294
+ soft_kill_each_worker(:USR1)
295
+ when :USR2 # exec binary, stay alive in case something went wrong
296
+ reexec
297
+ when :WINCH
298
+ if $stdin.tty?
299
+ logger.info "SIGWINCH ignored because we're not daemonized"
300
+ else
301
+ respawn = false
302
+ logger.info "gracefully stopping all workers"
303
+ soft_kill_each_worker(:QUIT)
304
+ self.worker_processes = 0
305
+ end
306
+ when :TTIN
307
+ respawn = true
308
+ self.worker_processes += 1
309
+ when :TTOU
310
+ self.worker_processes -= 1 if self.worker_processes > 0
311
+ when :HUP
312
+ respawn = true
313
+ if config.config_file
314
+ load_config!
315
+ else # exec binary and exit if there's no config file
316
+ logger.info "config_file not present, reexecuting binary"
317
+ reexec
318
+ end
319
+ end
320
+ rescue => e
321
+ Unicorn.log_error(@logger, "master loop error", e)
322
+ end while true
323
+ stop # gracefully shutdown all workers on our way out
324
+ logger.info "master complete"
325
+ unlink_pid_safe(pid) if pid
326
+ end
327
+
328
+ # Terminates all workers, but does not exit master process
329
+ def stop(graceful = true)
330
+ self.listeners = []
331
+ limit = time_now + timeout
332
+ until @workers.empty? || time_now > limit
333
+ if graceful
334
+ soft_kill_each_worker(:QUIT)
335
+ else
336
+ kill_each_worker(:TERM)
337
+ end
338
+ sleep(0.1)
339
+ reap_all_workers
340
+ end
341
+ kill_each_worker(:KILL)
342
+ end
343
+
344
+ def rewindable_input
345
+ Unicorn::HttpRequest.input_class.method_defined?(:rewind)
346
+ end
347
+
348
+ def rewindable_input=(bool)
349
+ Unicorn::HttpRequest.input_class = bool ?
350
+ Unicorn::TeeInput : Unicorn::StreamInput
351
+ end
352
+
353
+ def client_body_buffer_size
354
+ Unicorn::TeeInput.client_body_buffer_size
355
+ end
356
+
357
+ def client_body_buffer_size=(bytes)
358
+ Unicorn::TeeInput.client_body_buffer_size = bytes
359
+ end
360
+
361
+ def check_client_connection
362
+ Unicorn::HttpRequest.check_client_connection
363
+ end
364
+
365
+ def check_client_connection=(bool)
366
+ Unicorn::HttpRequest.check_client_connection = bool
367
+ end
368
+
369
+ private
370
+
371
+ # wait for a signal hander to wake us up and then consume the pipe
372
+ def master_sleep(sec)
373
+ @self_pipe[0].kgio_wait_readable(sec) or return
374
+ # 11 bytes is the maximum string length which can be embedded within
375
+ # the Ruby itself and not require a separate malloc (on 32-bit MRI 1.9+).
376
+ # Most reads are only one byte here and uncommon, so it's not worth a
377
+ # persistent buffer, either:
378
+ @self_pipe[0].kgio_tryread(11)
379
+ end
380
+
381
+ def awaken_master
382
+ return if $$ != @master_pid
383
+ @self_pipe[1].kgio_trywrite('.') # wakeup master process from select
384
+ end
385
+
386
+ # reaps all unreaped workers
387
+ def reap_all_workers
388
+ begin
389
+ wpid, status = Process.waitpid2(-1, Process::WNOHANG)
390
+ wpid or return
391
+ if @reexec_pid == wpid
392
+ logger.error "reaped #{status.inspect} exec()-ed"
393
+ @reexec_pid = 0
394
+ self.pid = pid.chomp('.oldbin') if pid
395
+ proc_name 'master'
396
+ else
397
+ worker = @workers.delete(wpid) and worker.close rescue nil
398
+ m = "reaped #{status.inspect} worker=#{worker.nr rescue 'unknown'}"
399
+ status.success? ? logger.info(m) : logger.error(m)
400
+ end
401
+ rescue Errno::ECHILD
402
+ break
403
+ end while true
404
+ end
405
+
406
+ # reexecutes the START_CTX with a new binary
407
+ def reexec
408
+ if @reexec_pid > 0
409
+ begin
410
+ Process.kill(0, @reexec_pid)
411
+ logger.error "reexec-ed child already running PID:#@reexec_pid"
412
+ return
413
+ rescue Errno::ESRCH
414
+ @reexec_pid = 0
415
+ end
416
+ end
417
+
418
+ if pid
419
+ old_pid = "#{pid}.oldbin"
420
+ begin
421
+ self.pid = old_pid # clear the path for a new pid file
422
+ rescue ArgumentError
423
+ logger.error "old PID:#{valid_pid?(old_pid)} running with " \
424
+ "existing pid=#{old_pid}, refusing rexec"
425
+ return
426
+ rescue => e
427
+ logger.error "error writing pid=#{old_pid} #{e.class} #{e.message}"
428
+ return
429
+ end
430
+ end
431
+
432
+ @reexec_pid = fork do
433
+ listener_fds = {}
434
+ LISTENERS.each do |sock|
435
+ sock.close_on_exec = false
436
+ listener_fds[sock.fileno] = sock
437
+ end
438
+ ENV['UNICORN_FD'] = listener_fds.keys.join(',')
439
+ Dir.chdir(START_CTX[:cwd])
440
+ cmd = [ START_CTX[0] ].concat(START_CTX[:argv])
441
+
442
+ # avoid leaking FDs we don't know about, but let before_exec
443
+ # unset FD_CLOEXEC, if anything else in the app eventually
444
+ # relies on FD inheritence.
445
+ (3..1024).each do |io|
446
+ next if listener_fds.include?(io)
447
+ io = IO.for_fd(io) rescue next
448
+ io.autoclose = false
449
+ io.close_on_exec = true
450
+ end
451
+
452
+ # exec(command, hash) works in at least 1.9.1+, but will only be
453
+ # required in 1.9.4/2.0.0 at earliest.
454
+ cmd << listener_fds
455
+ logger.info "executing #{cmd.inspect} (in #{Dir.pwd})"
456
+ before_exec.call(self)
457
+ exec(*cmd)
458
+ end
459
+ proc_name 'master (old)'
460
+ end
461
+
462
+ # forcibly terminate all workers that haven't checked in in timeout seconds. The timeout is implemented using an unlinked File
463
+ def murder_lazy_workers
464
+ next_sleep = @timeout - 1
465
+ now = time_now.to_i
466
+ @workers.dup.each_pair do |wpid, worker|
467
+ tick = worker.tick
468
+ 0 == tick and next # skip workers that haven't processed any clients
469
+ diff = now - tick
470
+ tmp = @timeout - diff
471
+ if tmp >= 0
472
+ next_sleep > tmp and next_sleep = tmp
473
+ next
474
+ end
475
+ next_sleep = 0
476
+ logger.error "worker=#{worker.nr} PID:#{wpid} timeout " \
477
+ "(#{diff}s > #{@timeout}s), killing"
478
+ kill_worker(:KILL, wpid) # take no prisoners for timeout violations
479
+ end
480
+ next_sleep <= 0 ? 1 : next_sleep
481
+ end
482
+
483
+ def after_fork_internal
484
+ @self_pipe.each(&:close).clear # this is master-only, now
485
+ @ready_pipe.close if @ready_pipe
486
+ Unicorn::Configurator::RACKUP.clear
487
+ @ready_pipe = @init_listeners = @before_exec = @before_fork = nil
488
+
489
+ # http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/36450
490
+ srand # remove in unicorn 6
491
+
492
+ # The OpenSSL PRNG is seeded with only the pid, and apps with frequently
493
+ # dying workers can recycle pids
494
+ OpenSSL::Random.seed(rand.to_s) if defined?(OpenSSL::Random)
495
+ end
496
+
497
+ def spawn_missing_workers
498
+ worker_nr = -1
499
+ until (worker_nr += 1) == @worker_processes
500
+ @workers.value?(worker_nr) and next
501
+ worker = Unicorn::Worker.new(worker_nr)
502
+ before_fork.call(self, worker)
503
+ if pid = fork
504
+ @workers[pid] = worker
505
+ worker.atfork_parent
506
+ else
507
+ after_fork_internal
508
+ worker_loop(worker)
509
+ exit
510
+ end
511
+ end
512
+ rescue => e
513
+ @logger.error(e) rescue nil
514
+ exit!
515
+ end
516
+
517
+ def maintain_worker_count
518
+ (off = @workers.size - worker_processes) == 0 and return
519
+ off < 0 and return spawn_missing_workers
520
+ @workers.each_value { |w| w.nr >= worker_processes and w.soft_kill(:QUIT) }
521
+ end
522
+
523
+ # if we get any error, try to write something back to the client
524
+ # assuming we haven't closed the socket, but don't get hung up
525
+ # if the socket is already closed or broken. We'll always ensure
526
+ # the socket is closed at the end of this function
527
+ def handle_error(client, e)
528
+ code = case e
529
+ when EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::ENOTCONN
530
+ # client disconnected on us and there's nothing we can do
531
+ when Unicorn::RequestURITooLongError
532
+ 414
533
+ when Unicorn::RequestEntityTooLargeError
534
+ 413
535
+ when Unicorn::HttpParserError # try to tell the client they're bad
536
+ 400
537
+ else
538
+ Unicorn.log_error(@logger, "app error", e)
539
+ 500
540
+ end
541
+ if code
542
+ client.kgio_trywrite(err_response(code, @request.response_start_sent))
543
+ end
544
+ client.close
545
+ rescue
546
+ end
547
+
548
+ def e100_response_write(client, env)
549
+ # We use String#freeze to avoid allocations under Ruby 2.1+
550
+ # Not many users hit this code path, so it's better to reduce the
551
+ # constant table sizes even for 1.9.3-2.0 users who'll hit extra
552
+ # allocations here.
553
+ client.write(@request.response_start_sent ?
554
+ "100 Continue\r\n\r\nHTTP/1.1 ".freeze :
555
+ "HTTP/1.1 100 Continue\r\n\r\n".freeze)
556
+ env.delete('HTTP_EXPECT'.freeze)
557
+ end
558
+
559
+ # once a client is accepted, it is processed in its entirety here
560
+ # in 3 easy steps: read request, call app, write app response
561
+ def process_client(client)
562
+ status, headers, body = @app.call(env = @request.read(client))
563
+
564
+ begin
565
+ return if @request.hijacked?
566
+
567
+ if 100 == status.to_i
568
+ e100_response_write(client, env)
569
+ status, headers, body = @app.call(env)
570
+ return if @request.hijacked?
571
+ end
572
+ @request.headers? or headers = nil
573
+ http_response_write(client, status, headers, body,
574
+ @request.response_start_sent)
575
+ ensure
576
+ body.respond_to?(:close) and body.close
577
+ end
578
+
579
+ unless client.closed? # rack.hijack may've close this for us
580
+ client.shutdown # in case of fork() in Rack app
581
+ client.close # flush and uncork socket immediately, no keepalive
582
+ end
583
+ rescue => e
584
+ handle_error(client, e)
585
+ end
586
+
587
+ def nuke_listeners!(readers)
588
+ # only called from the worker, ordering is important here
589
+ tmp = readers.dup
590
+ readers.replace([false]) # ensure worker does not continue ASAP
591
+ tmp.each { |io| io.close rescue nil } # break out of IO.select
592
+ end
593
+
594
+ # gets rid of stuff the worker has no business keeping track of
595
+ # to free some resources and drops all sig handlers.
596
+ # traps for USR1, USR2, and HUP may be set in the after_fork Proc
597
+ # by the user.
598
+ def init_worker_process(worker)
599
+ worker.atfork_child
600
+ # we'll re-trap :QUIT later for graceful shutdown iff we accept clients
601
+ exit_sigs = [ :QUIT, :TERM, :INT ]
602
+ exit_sigs.each { |sig| trap(sig) { exit!(0) } }
603
+ exit!(0) if (@sig_queue & exit_sigs)[0]
604
+ (@queue_sigs - exit_sigs).each { |sig| trap(sig, nil) }
605
+ trap(:CHLD, 'DEFAULT')
606
+ @sig_queue.clear
607
+ proc_name "worker[#{worker.nr}]"
608
+ START_CTX.clear
609
+ @workers.clear
610
+
611
+ after_fork.call(self, worker) # can drop perms and create listeners
612
+ LISTENERS.each { |sock| sock.close_on_exec = true }
613
+
614
+ worker.user(*user) if user.kind_of?(Array) && ! worker.switched
615
+ self.timeout /= 2.0 # halve it for select()
616
+ @config = nil
617
+ build_app! unless preload_app
618
+ @after_fork = @listener_opts = @orig_app = nil
619
+ readers = LISTENERS.dup
620
+ readers << worker
621
+ trap(:QUIT) { nuke_listeners!(readers) }
622
+ readers
623
+ end
624
+
625
+ def reopen_worker_logs(worker_nr)
626
+ logger.info "worker=#{worker_nr} reopening logs..."
627
+ Unicorn::Util.reopen_logs
628
+ logger.info "worker=#{worker_nr} done reopening logs"
629
+ rescue => e
630
+ logger.error(e) rescue nil
631
+ exit!(77) # EX_NOPERM in sysexits.h
632
+ end
633
+
634
+ # runs inside each forked worker, this sits around and waits
635
+ # for connections and doesn't die until the parent dies (or is
636
+ # given a INT, QUIT, or TERM signal)
637
+ def worker_loop(worker)
638
+ ppid = @master_pid
639
+ readers = init_worker_process(worker)
640
+ nr = 0 # this becomes negative if we need to reopen logs
641
+
642
+ # this only works immediately if the master sent us the signal
643
+ # (which is the normal case)
644
+ trap(:USR1) { nr = -65536 }
645
+
646
+ ready = readers.dup
647
+ @logger.info "worker=#{worker.nr} ready"
648
+
649
+ begin
650
+ nr < 0 and reopen_worker_logs(worker.nr)
651
+ nr = 0
652
+ worker.tick = time_now.to_i
653
+ tmp = ready.dup
654
+ while sock = tmp.shift
655
+ # Unicorn::Worker#kgio_tryaccept is not like accept(2) at all,
656
+ # but that will return false
657
+ if client = sock.kgio_tryaccept
658
+ process_client(client)
659
+ nr += 1
660
+ worker.tick = time_now.to_i
661
+ end
662
+ break if nr < 0
663
+ end
664
+
665
+ # make the following bet: if we accepted clients this round,
666
+ # we're probably reasonably busy, so avoid calling select()
667
+ # and do a speculative non-blocking accept() on ready listeners
668
+ # before we sleep again in select().
669
+ unless nr == 0
670
+ tmp = ready.dup
671
+ redo
672
+ end
673
+
674
+ ppid == Process.ppid or return
675
+
676
+ # timeout used so we can detect parent death:
677
+ worker.tick = time_now.to_i
678
+ ret = IO.select(readers, nil, nil, @timeout) and ready = ret[0]
679
+ rescue => e
680
+ redo if nr < 0 && readers[0]
681
+ Unicorn.log_error(@logger, "listen loop error", e) if readers[0]
682
+ end while readers[0]
683
+ end
684
+
685
+ # delivers a signal to a worker and fails gracefully if the worker
686
+ # is no longer running.
687
+ def kill_worker(signal, wpid)
688
+ Process.kill(signal, wpid)
689
+ rescue Errno::ESRCH
690
+ worker = @workers.delete(wpid) and worker.close rescue nil
691
+ end
692
+
693
+ # delivers a signal to each worker
694
+ def kill_each_worker(signal)
695
+ @workers.keys.each { |wpid| kill_worker(signal, wpid) }
696
+ end
697
+
698
+ def soft_kill_each_worker(signal)
699
+ @workers.each_value { |worker| worker.soft_kill(signal) }
700
+ end
701
+
702
+ # unlinks a PID file at given +path+ if it contains the current PID
703
+ # still potentially racy without locking the directory (which is
704
+ # non-portable and may interact badly with other programs), but the
705
+ # window for hitting the race condition is small
706
+ def unlink_pid_safe(path)
707
+ (File.read(path).to_i == $$ and File.unlink(path)) rescue nil
708
+ end
709
+
710
+ # returns a PID if a given path contains a non-stale PID file,
711
+ # nil otherwise.
712
+ def valid_pid?(path)
713
+ wpid = File.read(path).to_i
714
+ wpid <= 0 and return
715
+ Process.kill(0, wpid)
716
+ wpid
717
+ rescue Errno::EPERM
718
+ logger.info "pid=#{path} possibly stale, got EPERM signalling PID:#{wpid}"
719
+ nil
720
+ rescue Errno::ESRCH, Errno::ENOENT
721
+ # don't unlink stale pid files, racy without non-portable locking...
722
+ end
723
+
724
+ def load_config!
725
+ loaded_app = app
726
+ logger.info "reloading config_file=#{config.config_file}"
727
+ config[:listeners].replace(@init_listeners)
728
+ config.reload
729
+ config.commit!(self)
730
+ soft_kill_each_worker(:QUIT)
731
+ Unicorn::Util.reopen_logs
732
+ self.app = @orig_app
733
+ build_app! if preload_app
734
+ logger.info "done reloading config_file=#{config.config_file}"
735
+ rescue StandardError, LoadError, SyntaxError => e
736
+ Unicorn.log_error(@logger,
737
+ "error reloading config_file=#{config.config_file}", e)
738
+ self.app = loaded_app
739
+ end
740
+
741
+ # returns an array of string names for the given listener array
742
+ def listener_names(listeners = LISTENERS)
743
+ listeners.map { |io| sock_name(io) }
744
+ end
745
+
746
+ def build_app!
747
+ if app.respond_to?(:arity) && app.arity == 0
748
+ if defined?(Gem) && Gem.respond_to?(:refresh)
749
+ logger.info "Refreshing Gem list"
750
+ Gem.refresh
751
+ end
752
+ self.app = app.call
753
+ end
754
+ end
755
+
756
+ def proc_name(tag)
757
+ $0 = ([ File.basename(START_CTX[0]), tag
758
+ ]).concat(START_CTX[:argv]).join(' ')
759
+ end
760
+
761
+ def redirect_io(io, path)
762
+ File.open(path, 'ab') { |fp| io.reopen(fp) } if path
763
+ io.sync = true
764
+ end
765
+
766
+ def inherit_listeners!
767
+ # inherit sockets from parents, they need to be plain Socket objects
768
+ # before they become Kgio::UNIXServer or Kgio::TCPServer
769
+ inherited = ENV['UNICORN_FD'].to_s.split(',')
770
+
771
+ # emulate sd_listen_fds() for systemd
772
+ sd_pid, sd_fds = ENV.values_at('LISTEN_PID', 'LISTEN_FDS')
773
+ if sd_pid.to_i == $$ # n.b. $$ can never be zero
774
+ # 3 = SD_LISTEN_FDS_START
775
+ inherited.concat((3...(3 + sd_fds.to_i)).to_a)
776
+ end
777
+ # to ease debugging, we will not unset LISTEN_PID and LISTEN_FDS
778
+
779
+ inherited.map! do |fd|
780
+ io = Socket.for_fd(fd.to_i)
781
+ io.autoclose = false
782
+ io = server_cast(io)
783
+ set_server_sockopt(io, listener_opts[sock_name(io)])
784
+ logger.info "inherited addr=#{sock_name(io)} fd=#{io.fileno}"
785
+ io
786
+ end
787
+
788
+ config_listeners = config[:listeners].dup
789
+ LISTENERS.replace(inherited)
790
+
791
+ # we start out with generic Socket objects that get cast to either
792
+ # Kgio::TCPServer or Kgio::UNIXServer objects; but since the Socket
793
+ # objects share the same OS-level file descriptor as the higher-level
794
+ # *Server objects; we need to prevent Socket objects from being
795
+ # garbage-collected
796
+ config_listeners -= listener_names
797
+ if config_listeners.empty? && LISTENERS.empty?
798
+ config_listeners << Unicorn::Const::DEFAULT_LISTEN
799
+ @init_listeners << Unicorn::Const::DEFAULT_LISTEN
800
+ START_CTX[:argv] << "-l#{Unicorn::Const::DEFAULT_LISTEN}"
801
+ end
802
+ NEW_LISTENERS.replace(config_listeners)
803
+ end
804
+
805
+ # call only after calling inherit_listeners!
806
+ # This binds any listeners we did NOT inherit from the parent
807
+ def bind_new_listeners!
808
+ NEW_LISTENERS.each { |addr| listen(addr) }.clear
809
+ raise ArgumentError, "no listeners" if LISTENERS.empty?
810
+ end
811
+
812
+ # try to use the monotonic clock in Ruby >= 2.1, it is immune to clock
813
+ # offset adjustments and generates less garbage (Float vs Time object)
814
+ begin
815
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
816
+ def time_now
817
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
818
+ end
819
+ rescue NameError, NoMethodError
820
+ def time_now # Ruby <= 2.0
821
+ Time.now
822
+ end
823
+ end
824
+ end