Unicorn

Unicorn module containing all of the classes (include C extensions) for running a Unicorn web server. It contains a minimalist HTTP server with just enough functionality to service web application requests fast as possible.

Constants

QUEUE_SIGS

list of signals we care about and trap in master.

Public Class Methods

builder(ru, opts) click to toggle source

This returns a lambda to pass in as the app, this does not “build” the app (which we defer based on the outcome of “preload_app“ in the Unicorn config). The returned lambda will be called when it is time to build the app.

    # File lib/unicorn.rb, line 36
36:     def builder(ru, opts)
37:       if ru =~ /\.ru\z/
38:         # parse embedded command-line options in config.ru comments
39:         /^#\\(.*)/ =~ File.read(ru) and opts.parse!($1.split(/\s+/))
40:       end
41: 
42:       lambda do ||
43:         inner_app = case ru
44:         when /\.ru$/
45:           raw = File.read(ru)
46:           raw.sub!(/^__END__\n.*/, '')
47:           eval("Rack::Builder.new {(#{raw}\n)}.to_app", TOPLEVEL_BINDING, ru)
48:         else
49:           require ru
50:           Object.const_get(File.basename(ru, '.rb').capitalize)
51:         end
52: 
53:         pp({ :inner_app => inner_app }) if $DEBUG
54: 
55:         # return value, matches rackup defaults based on env
56:         case ENV["RACK_ENV"]
57:         when "development"
58:           Rack::Builder.new do
59:             use Rack::CommonLogger, $stderr
60:             use Rack::ShowExceptions
61:             use Rack::Lint
62:             run inner_app
63:           end.to_app
64:         when "deployment"
65:           Rack::Builder.new do
66:             use Rack::CommonLogger, $stderr
67:             run inner_app
68:           end.to_app
69:         else
70:           inner_app
71:         end
72:       end
73:     end
new(app, options = {}) click to toggle source

Creates a working server on host:port (strange things happen if port isn’t a Number). Use HttpServer::run to start the server and HttpServer.run.join to join the thread that’s processing incoming requests on the socket.

     # File lib/unicorn.rb, line 205
205:     def initialize(app, options = {})
206:       self.app = app
207:       self.reexec_pid = 0
208:       self.ready_pipe = options.delete(:ready_pipe)
209:       self.init_listeners = options[:listeners] ? options[:listeners].dup : []
210:       self.config = Configurator.new(options.merge(:use_defaults => true))
211:       self.listener_opts = {}
212: 
213:       # we try inheriting listeners first, so we bind them later.
214:       # we don't write the pid file until we've bound listeners in case
215:       # unicorn was started twice by mistake.  Even though our #pid= method
216:       # checks for stale/existing pid files, race conditions are still
217:       # possible (and difficult/non-portable to avoid) and can be likely
218:       # to clobber the pid if the second start was in quick succession
219:       # after the first, so we rely on the listener binding to fail in
220:       # that case.  Some tests (in and outside of this source tree) and
221:       # monitoring tools may also rely on pid files existing before we
222:       # attempt to connect to the listener(s)
223:       config.commit!(self, :skip => [:listeners, :pid])
224:       self.orig_app = app
225:     end
run(app, options = {}) click to toggle source

(Not documented)

    # File lib/unicorn.rb, line 28
28:     def run(app, options = {})
29:       HttpServer.new(app, options).start.join
30:     end

Public Instance Methods

join() click to toggle source

monitors children and receives signals forever (or until a termination signal is sent). This handles signals one-at-a-time time and we’ll happily drop signals in case somebody is signalling us too often.

     # File lib/unicorn.rb, line 378
378:     def join
379:       respawn = true
380:       last_check = Time.now
381: 
382:       proc_name 'master'
383:       logger.info "master process ready" # test_exec.rb relies on this message
384:       if ready_pipe
385:         ready_pipe.syswrite($$.to_s)
386:         ready_pipe.close rescue nil
387:         self.ready_pipe = nil
388:       end
389:       begin
390:         loop do
391:           reap_all_workers
392:           case SIG_QUEUE.shift
393:           when nil
394:             # avoid murdering workers after our master process (or the
395:             # machine) comes out of suspend/hibernation
396:             if (last_check + timeout) >= (last_check = Time.now)
397:               murder_lazy_workers
398:             end
399:             maintain_worker_count if respawn
400:             master_sleep
401:           when :QUIT # graceful shutdown
402:             break
403:           when :TERM, :INT # immediate shutdown
404:             stop(false)
405:             break
406:           when :USR1 # rotate logs
407:             logger.info "master reopening logs..."
408:             Unicorn::Util.reopen_logs
409:             logger.info "master done reopening logs"
410:             kill_each_worker(:USR1)
411:           when :USR2 # exec binary, stay alive in case something went wrong
412:             reexec
413:           when :WINCH
414:             if Process.ppid == 1 || Process.getpgrp != $$
415:               respawn = false
416:               logger.info "gracefully stopping all workers"
417:               kill_each_worker(:QUIT)
418:             else
419:               logger.info "SIGWINCH ignored because we're not daemonized"
420:             end
421:           when :TTIN
422:             self.worker_processes += 1
423:           when :TTOU
424:             self.worker_processes -= 1 if self.worker_processes > 0
425:           when :HUP
426:             respawn = true
427:             if config.config_file
428:               load_config!
429:               redo # immediate reaping since we may have QUIT workers
430:             else # exec binary and exit if there's no config file
431:               logger.info "config_file not present, reexecuting binary"
432:               reexec
433:               break
434:             end
435:           end
436:         end
437:       rescue Errno::EINTR
438:         retry
439:       rescue => e
440:         logger.error "Unhandled master loop exception #{e.inspect}."
441:         logger.error e.backtrace.join("\n")
442:         retry
443:       end
444:       stop # gracefully shutdown all workers on our way out
445:       logger.info "master complete"
446:       unlink_pid_safe(pid) if pid
447:     end
listen(address, opt = {}.merge(listener_opts[address] || {})) click to toggle source

add a given address to the listeners set, idempotently Allows workers to add a private, per-process listener via the after_fork hook. Very useful for debugging and testing. :tries may be specified as an option for the number of times to retry, and :delay may be specified as the time in seconds to delay between retries. A negative value for :tries indicates the listen will be retried indefinitely, this is useful when workers belonging to different masters are spawned during a transparent upgrade.

     # File lib/unicorn.rb, line 345
345:     def listen(address, opt = {}.merge(listener_opts[address] || {}))
346:       address = config.expand_addr(address)
347:       return if String === address && listener_names.include?(address)
348: 
349:       delay = opt[:delay] || 0.5
350:       tries = opt[:tries] || 5
351:       begin
352:         io = bind_listen(address, opt)
353:         unless TCPServer === io || UNIXServer === io
354:           IO_PURGATORY << io
355:           io = server_cast(io)
356:         end
357:         logger.info "listening on addr=#{sock_name(io)} fd=#{io.fileno}"
358:         LISTENERS << io
359:         io
360:       rescue Errno::EADDRINUSE => err
361:         logger.error "adding listener failed addr=#{address} (in use)"
362:         raise err if tries == 0
363:         tries -= 1
364:         logger.error "retrying in #{delay} seconds " \
365:                      "(#{tries < 0 ? 'infinite' : tries} tries left)"
366:         sleep(delay)
367:         retry
368:       rescue => err
369:         logger.fatal "error adding listener addr=#{address}"
370:         raise err
371:       end
372:     end
listeners=(listeners) click to toggle source

replaces current listener set with listeners. This will close the socket if it will not exist in the new listener set

     # File lib/unicorn.rb, line 276
276:     def listeners=(listeners)
277:       cur_names, dead_names = [], []
278:       listener_names.each do |name|
279:         if ?/ == name[0]
280:           # mark unlinked sockets as dead so we can rebind them
281:           (File.socket?(name) ? cur_names : dead_names) << name
282:         else
283:           cur_names << name
284:         end
285:       end
286:       set_names = listener_names(listeners)
287:       dead_names.concat(cur_names - set_names).uniq!
288: 
289:       LISTENERS.delete_if do |io|
290:         if dead_names.include?(sock_name(io))
291:           IO_PURGATORY.delete_if do |pio|
292:             pio.fileno == io.fileno && (pio.close rescue nil).nil? # true
293:           end
294:           (io.close rescue nil).nil? # true
295:         else
296:           set_server_sockopt(io, listener_opts[sock_name(io)])
297:           false
298:         end
299:       end
300: 
301:       (set_names - cur_names).each { |addr| listen(addr) }
302:     end
logger=(obj) click to toggle source

(Not documented)

     # File lib/unicorn.rb, line 307
307:     def logger=(obj)
308:       HttpRequest::DEFAULTS["rack.logger"] = super
309:     end
pid=(path) click to toggle source

sets the path for the PID file of the master process

     # File lib/unicorn.rb, line 312
312:     def pid=(path)
313:       if path
314:         if x = valid_pid?(path)
315:           return path if pid && path == pid && x == $$
316:           raise ArgumentError, "Already running on PID:#{x} " \
317:                                "(or pid=#{path} is stale)"
318:         end
319:       end
320:       unlink_pid_safe(pid) if pid
321: 
322:       if path
323:         fp = begin
324:           tmp = "#{File.dirname(path)}/#{rand}.#$$"
325:           File.open(tmp, File::RDWR|File::CREAT|File::EXCL, 0644)
326:         rescue Errno::EEXIST
327:           retry
328:         end
329:         fp.syswrite("#$$\n")
330:         File.rename(fp.path, path)
331:         fp.close
332:       end
333:       super(path)
334:     end
start() click to toggle source

Runs the thing. Returns self so you can run join on it

     # File lib/unicorn.rb, line 228
228:     def start
229:       BasicSocket.do_not_reverse_lookup = true
230: 
231:       # inherit sockets from parents, they need to be plain Socket objects
232:       # before they become UNIXServer or TCPServer
233:       inherited = ENV['UNICORN_FD'].to_s.split(/,/).map do |fd|
234:         io = Socket.for_fd(fd.to_i)
235:         set_server_sockopt(io, listener_opts[sock_name(io)])
236:         IO_PURGATORY << io
237:         logger.info "inherited addr=#{sock_name(io)} fd=#{fd}"
238:         server_cast(io)
239:       end
240: 
241:       config_listeners = config[:listeners].dup
242:       LISTENERS.replace(inherited)
243: 
244:       # we start out with generic Socket objects that get cast to either
245:       # TCPServer or UNIXServer objects; but since the Socket objects
246:       # share the same OS-level file descriptor as the higher-level *Server
247:       # objects; we need to prevent Socket objects from being garbage-collected
248:       config_listeners -= listener_names
249:       if config_listeners.empty? && LISTENERS.empty?
250:         config_listeners << Unicorn::Const::DEFAULT_LISTEN
251:         init_listeners << Unicorn::Const::DEFAULT_LISTEN
252:         START_CTX[:argv] << "-l#{Unicorn::Const::DEFAULT_LISTEN}"
253:       end
254:       config_listeners.each { |addr| listen(addr) }
255:       raise ArgumentError, "no listeners" if LISTENERS.empty?
256: 
257:       # this pipe is used to wake us up from select(2) in #join when signals
258:       # are trapped.  See trap_deferred.
259:       init_self_pipe!
260: 
261:       # setup signal handlers before writing pid file in case people get
262:       # trigger happy and send signals as soon as the pid file exists.
263:       # Note that signals don't actually get handled until the #join method
264:       QUEUE_SIGS.each { |sig| trap_deferred(sig) }
265:       trap(:CHLD) { |_| awaken_master }
266:       self.pid = config[:pid]
267: 
268:       self.master_pid = $$
269:       build_app! if preload_app
270:       maintain_worker_count
271:       self
272:     end
stderr_path=(path) click to toggle source

(Not documented)

     # File lib/unicorn.rb, line 305
305:     def stderr_path=(path); redirect_io($stderr, path); end
stdout_path=(path) click to toggle source

(Not documented)

     # File lib/unicorn.rb, line 304
304:     def stdout_path=(path); redirect_io($stdout, path); end
stop(graceful = true) click to toggle source

Terminates all workers, but does not exit master process

     # File lib/unicorn.rb, line 450
450:     def stop(graceful = true)
451:       self.listeners = []
452:       limit = Time.now + timeout
453:       until WORKERS.empty? || Time.now > limit
454:         kill_each_worker(graceful ? :QUIT : :TERM)
455:         sleep(0.1)
456:         reap_all_workers
457:       end
458:       kill_each_worker(:KILL)
459:     end

Private Instance Methods

awaken_master() click to toggle source

(Not documented)

     # File lib/unicorn.rb, line 490
490:     def awaken_master
491:       begin
492:         SELF_PIPE.last.write_nonblock('.') # wakeup master process from select
493:       rescue Errno::EAGAIN, Errno::EINTR
494:         # pipe is full, master should wake up anyways
495:         retry
496:       end
497:     end
build_app!() click to toggle source

(Not documented)

     # File lib/unicorn.rb, line 801
801:     def build_app!
802:       if app.respond_to?(:arity) && app.arity == 0
803:         # exploit COW in case of preload_app.  Also avoids race
804:         # conditions in Rainbows! since load/require are not thread-safe
805:         Unicorn.constants.each { |x| Unicorn.const_get(x) }
806: 
807:         if defined?(Gem) && Gem.respond_to?(:refresh)
808:           logger.info "Refreshing Gem list"
809:           Gem.refresh
810:         end
811:         self.app = app.call
812:       end
813:     end
handle_error(client, e) click to toggle source

if we get any error, try to write something back to the client assuming we haven’t closed the socket, but don’t get hung up if the socket is already closed or broken. We’ll always ensure the socket is closed at the end of this function

     # File lib/unicorn.rb, line 613
613:     def handle_error(client, e)
614:       msg = case e
615:       when EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,Errno::EBADF
616:         Const::ERROR_500_RESPONSE
617:       when HttpParserError # try to tell the client they're bad
618:         Const::ERROR_400_RESPONSE
619:       else
620:         logger.error "Read error: #{e.inspect}"
621:         logger.error e.backtrace.join("\n")
622:         Const::ERROR_500_RESPONSE
623:       end
624:       client.write_nonblock(msg)
625:       client.close
626:       rescue
627:         nil
628:     end
init_self_pipe!() click to toggle source

(Not documented)

     # File lib/unicorn.rb, line 825
825:     def init_self_pipe!
826:       SELF_PIPE.each { |io| io.close rescue nil }
827:       SELF_PIPE.replace(IO.pipe)
828:       SELF_PIPE.each { |io| io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) }
829:     end
init_worker_process(worker) click to toggle source

gets rid of stuff the worker has no business keeping track of to free some resources and drops all sig handlers. traps for USR1, USR2, and HUP may be set in the after_fork Proc by the user.

     # File lib/unicorn.rb, line 650
650:     def init_worker_process(worker)
651:       QUEUE_SIGS.each { |sig| trap(sig, nil) }
652:       trap(:CHLD, 'DEFAULT')
653:       SIG_QUEUE.clear
654:       proc_name "worker[#{worker.nr}]"
655:       START_CTX.clear
656:       init_self_pipe!
657:       WORKERS.values.each { |other| other.tmp.close rescue nil }
658:       WORKERS.clear
659:       LISTENERS.each { |sock| sock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) }
660:       worker.tmp.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
661:       after_fork.call(self, worker) # can drop perms
662:       worker.user(*user) if user.kind_of?(Array) && ! worker.switched
663:       self.timeout /= 2.0 # halve it for select()
664:       build_app! unless preload_app
665:     end
kill_each_worker(signal) click to toggle source

delivers a signal to each worker

     # File lib/unicorn.rb, line 751
751:     def kill_each_worker(signal)
752:       WORKERS.keys.each { |wpid| kill_worker(signal, wpid) }
753:     end
kill_worker(signal, wpid) click to toggle source

delivers a signal to a worker and fails gracefully if the worker is no longer running.

     # File lib/unicorn.rb, line 742
742:     def kill_worker(signal, wpid)
743:       begin
744:         Process.kill(signal, wpid)
745:       rescue Errno::ESRCH
746:         worker = WORKERS.delete(wpid) and worker.tmp.close rescue nil
747:       end
748:     end
listener_names(listeners = LISTENERS) click to toggle source

returns an array of string names for the given listener array

     # File lib/unicorn.rb, line 797
797:     def listener_names(listeners = LISTENERS)
798:       listeners.map { |io| sock_name(io) }
799:     end
load_config!() click to toggle source

(Not documented)

     # File lib/unicorn.rb, line 777
777:     def load_config!
778:       loaded_app = app
779:       begin
780:         logger.info "reloading config_file=#{config.config_file}"
781:         config[:listeners].replace(init_listeners)
782:         config.reload
783:         config.commit!(self)
784:         kill_each_worker(:QUIT)
785:         Unicorn::Util.reopen_logs
786:         self.app = orig_app
787:         build_app! if preload_app
788:         logger.info "done reloading config_file=#{config.config_file}"
789:       rescue StandardError, LoadError, SyntaxError => e
790:         logger.error "error reloading config_file=#{config.config_file}: " \
791:                      "#{e.class} #{e.message}"
792:         self.app = loaded_app
793:       end
794:     end
maintain_worker_count() click to toggle source

(Not documented)

     # File lib/unicorn.rb, line 601
601:     def maintain_worker_count
602:       (off = WORKERS.size - worker_processes) == 0 and return
603:       off < 0 and return spawn_missing_workers
604:       WORKERS.dup.each_pair { |wpid,w|
605:         w.nr >= worker_processes and kill_worker(:QUIT, wpid) rescue nil
606:       }
607:     end
master_sleep() click to toggle source

wait for a signal hander to wake us up and then consume the pipe Wake up every second anyways to run murder_lazy_workers

     # File lib/unicorn.rb, line 481
481:     def master_sleep
482:       begin
483:         ready = IO.select([SELF_PIPE.first], nil, nil, 1) or return
484:         ready.first && ready.first.first or return
485:         loop { SELF_PIPE.first.read_nonblock(Const::CHUNK_SIZE) }
486:       rescue Errno::EAGAIN, Errno::EINTR
487:       end
488:     end
murder_lazy_workers() click to toggle source

forcibly terminate all workers that haven’t checked in in timeout seconds. The timeout is implemented using an unlinked File shared between the parent process and each worker. The worker runs File#chmod to modify the ctime of the File. If the ctime is stale for >timeout seconds, then we’ll kill the corresponding worker.

     # File lib/unicorn.rb, line 576
576:     def murder_lazy_workers
577:       WORKERS.dup.each_pair do |wpid, worker|
578:         stat = worker.tmp.stat
579:         # skip workers that disable fchmod or have never fchmod-ed
580:         stat.mode == 0100600 and next
581:         (diff = (Time.now - stat.ctime)) <= timeout and next
582:         logger.error "worker=#{worker.nr} PID:#{wpid} timeout " \
583:                      "(#{diff}s > #{timeout}s), killing"
584:         kill_worker(:KILL, wpid) # take no prisoners for timeout violations
585:       end
586:     end
proc_name(tag) click to toggle source

(Not documented)

     # File lib/unicorn.rb, line 815
815:     def proc_name(tag)
816:       $0 = ([ File.basename(START_CTX[0]), tag
817:             ]).concat(START_CTX[:argv]).join(' ')
818:     end
process_client(client) click to toggle source

once a client is accepted, it is processed in its entirety here in 3 easy steps: read request, call app, write app response

     # File lib/unicorn.rb, line 632
632:     def process_client(client)
633:       client.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
634:       response = app.call(env = REQUEST.read(client))
635: 
636:       if 100 == response.first.to_i
637:         client.write(Const::EXPECT_100_RESPONSE)
638:         env.delete(Const::HTTP_EXPECT)
639:         response = app.call(env)
640:       end
641:       HttpResponse.write(client, response, HttpRequest::PARSER.headers?)
642:     rescue => e
643:       handle_error(client, e)
644:     end
reap_all_workers() click to toggle source

reaps all unreaped workers

     # File lib/unicorn.rb, line 500
500:     def reap_all_workers
501:       begin
502:         loop do
503:           wpid, status = Process.waitpid2(-1, Process::WNOHANG)
504:           wpid or break
505:           if reexec_pid == wpid
506:             logger.error "reaped #{status.inspect} exec()-ed"
507:             self.reexec_pid = 0
508:             self.pid = pid.chomp('.oldbin') if pid
509:             proc_name 'master'
510:           else
511:             worker = WORKERS.delete(wpid) and worker.tmp.close rescue nil
512:             logger.info "reaped #{status.inspect} " \
513:                         "worker=#{worker.nr rescue 'unknown'}"
514:           end
515:         end
516:       rescue Errno::ECHILD
517:       end
518:     end
redirect_io(io, path) click to toggle source

(Not documented)

     # File lib/unicorn.rb, line 820
820:     def redirect_io(io, path)
821:       File.open(path, 'ab') { |fp| io.reopen(fp) } if path
822:       io.sync = true
823:     end
reexec() click to toggle source

reexecutes the START_CTX with a new binary

     # File lib/unicorn.rb, line 521
521:     def reexec
522:       if reexec_pid > 0
523:         begin
524:           Process.kill(0, reexec_pid)
525:           logger.error "reexec-ed child already running PID:#{reexec_pid}"
526:           return
527:         rescue Errno::ESRCH
528:           self.reexec_pid = 0
529:         end
530:       end
531: 
532:       if pid
533:         old_pid = "#{pid}.oldbin"
534:         prev_pid = pid.dup
535:         begin
536:           self.pid = old_pid  # clear the path for a new pid file
537:         rescue ArgumentError
538:           logger.error "old PID:#{valid_pid?(old_pid)} running with " \
539:                        "existing pid=#{old_pid}, refusing rexec"
540:           return
541:         rescue => e
542:           logger.error "error writing pid=#{old_pid} #{e.class} #{e.message}"
543:           return
544:         end
545:       end
546: 
547:       self.reexec_pid = fork do
548:         listener_fds = LISTENERS.map { |sock| sock.fileno }
549:         ENV['UNICORN_FD'] = listener_fds.join(',')
550:         Dir.chdir(START_CTX[:cwd])
551:         cmd = [ START_CTX[0] ].concat(START_CTX[:argv])
552: 
553:         # avoid leaking FDs we don't know about, but let before_exec
554:         # unset FD_CLOEXEC, if anything else in the app eventually
555:         # relies on FD inheritence.
556:         (3..1024).each do |io|
557:           next if listener_fds.include?(io)
558:           io = IO.for_fd(io) rescue nil
559:           io or next
560:           IO_PURGATORY << io
561:           io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
562:         end
563:         logger.info "executing #{cmd.inspect} (in #{Dir.pwd})"
564:         before_exec.call(self)
565:         exec(*cmd)
566:       end
567:       proc_name 'master (old)'
568:     end
reopen_worker_logs(worker_nr) click to toggle source

(Not documented)

     # File lib/unicorn.rb, line 667
667:     def reopen_worker_logs(worker_nr)
668:       logger.info "worker=#{worker_nr} reopening logs..."
669:       Unicorn::Util.reopen_logs
670:       logger.info "worker=#{worker_nr} done reopening logs"
671:       init_self_pipe!
672:     end
spawn_missing_workers() click to toggle source

(Not documented)

     # File lib/unicorn.rb, line 588
588:     def spawn_missing_workers
589:       (0...worker_processes).each do |worker_nr|
590:         WORKERS.values.include?(worker_nr) and next
591:         worker = Worker.new(worker_nr, Unicorn::Util.tmpio)
592:         before_fork.call(self, worker)
593:         WORKERS[fork {
594:           ready_pipe.close if ready_pipe
595:           self.ready_pipe = nil
596:           worker_loop(worker)
597:         }] = worker
598:       end
599:     end
trap_deferred(signal) click to toggle source

defer a signal for later processing in join (master process)

     # File lib/unicorn.rb, line 468
468:     def trap_deferred(signal)
469:       trap(signal) do |sig_nr|
470:         if SIG_QUEUE.size < 5
471:           SIG_QUEUE << signal
472:           awaken_master
473:         else
474:           logger.error "ignoring SIG#{signal}, queue=#{SIG_QUEUE.inspect}"
475:         end
476:       end
477:     end
valid_pid?(path) click to toggle source

returns a PID if a given path contains a non-stale PID file, nil otherwise.

     # File lib/unicorn.rb, line 765
765:     def valid_pid?(path)
766:       wpid = File.read(path).to_i
767:       wpid <= 0 and return nil
768:       begin
769:         Process.kill(0, wpid)
770:         wpid
771:       rescue Errno::ESRCH
772:         # don't unlink stale pid files, racy without non-portable locking...
773:       end
774:       rescue Errno::ENOENT
775:     end
worker_loop(worker) click to toggle source

runs inside each forked worker, this sits around and waits for connections and doesn’t die until the parent dies (or is given a INT, QUIT, or TERM signal)

     # File lib/unicorn.rb, line 677
677:     def worker_loop(worker)
678:       ppid = master_pid
679:       init_worker_process(worker)
680:       nr = 0 # this becomes negative if we need to reopen logs
681:       alive = worker.tmp # tmp is our lifeline to the master process
682:       ready = LISTENERS
683: 
684:       # closing anything we IO.select on will raise EBADF
685:       trap(:USR1) { nr = -65536; SELF_PIPE.first.close rescue nil }
686:       trap(:QUIT) { alive = nil; LISTENERS.each { |s| s.close rescue nil } }
687:       [:TERM, :INT].each { |sig| trap(sig) { exit!(0) } } # instant shutdown
688:       logger.info "worker=#{worker.nr} ready"
689:       m = 0
690: 
691:       begin
692:         nr < 0 and reopen_worker_logs(worker.nr)
693:         nr = 0
694: 
695:         # we're a goner in timeout seconds anyways if alive.chmod
696:         # breaks, so don't trap the exception.  Using fchmod() since
697:         # futimes() is not available in base Ruby and I very strongly
698:         # prefer temporary files to be unlinked for security,
699:         # performance and reliability reasons, so utime is out.  No-op
700:         # changes with chmod doesn't update ctime on all filesystems; so
701:         # we change our counter each and every time (after process_client
702:         # and before IO.select).
703:         alive.chmod(m = 0 == m ? 1 : 0)
704: 
705:         ready.each do |sock|
706:           begin
707:             process_client(sock.accept_nonblock)
708:             nr += 1
709:             alive.chmod(m = 0 == m ? 1 : 0)
710:           rescue Errno::EAGAIN, Errno::ECONNABORTED
711:           end
712:           break if nr < 0
713:         end
714: 
715:         # make the following bet: if we accepted clients this round,
716:         # we're probably reasonably busy, so avoid calling select()
717:         # and do a speculative accept_nonblock on ready listeners
718:         # before we sleep again in select().
719:         redo unless nr == 0 # (nr < 0) => reopen logs
720: 
721:         ppid == Process.ppid or return
722:         alive.chmod(m = 0 == m ? 1 : 0)
723:         begin
724:           # timeout used so we can detect parent death:
725:           ret = IO.select(LISTENERS, nil, SELF_PIPE, timeout) or redo
726:           ready = ret.first
727:         rescue Errno::EINTR
728:           ready = LISTENERS
729:         rescue Errno::EBADF
730:           nr < 0 or return
731:         end
732:       rescue => e
733:         if alive
734:           logger.error "Unhandled listen loop exception #{e.inspect}."
735:           logger.error e.backtrace.join("\n")
736:         end
737:       end while alive
738:     end

Disabled; run with --debug to generate this.

[Validate]

Generated with the Darkfish Rdoc Generator 1.1.6.