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:       # allow Configurator to parse cli switches embedded in the ru file
38:       Unicorn::Configurator::RACKUP.update(:file => ru, :optparse => opts)
39: 
40:       # always called after config file parsing, may be called after forking
41:       lambda do ||
42:         inner_app = case ru
43:         when /\.ru$/
44:           raw = File.read(ru)
45:           raw.sub!(/^__END__\n.*/, '')
46:           eval("Rack::Builder.new {(#{raw}\n)}.to_app", TOPLEVEL_BINDING, ru)
47:         else
48:           require ru
49:           Object.const_get(File.basename(ru, '.rb').capitalize)
50:         end
51: 
52:         pp({ :inner_app => inner_app }) if $DEBUG
53: 
54:         # return value, matches rackup defaults based on env
55:         case ENV["RACK_ENV"]
56:         when "development"
57:           Rack::Builder.new do
58:             use Rack::CommonLogger, $stderr
59:             use Rack::ShowExceptions
60:             use Rack::Lint
61:             run inner_app
62:           end.to_app
63:         when "deployment"
64:           Rack::Builder.new do
65:             use Rack::CommonLogger, $stderr
66:             run inner_app
67:           end.to_app
68:         else
69:           inner_app
70:         end
71:       end
72:     end
listener_names() click to toggle source

returns an array of strings representing TCP listen socket addresses and Unix domain socket paths. This is useful for use with Raindrops::Middleware under Linux: raindrops.bogomips.org/

    # File lib/unicorn.rb, line 77
77:     def listener_names
78:       HttpServer::LISTENERS.map { |io| SocketHelper.sock_name(io) }
79:     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
    # 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 383
383:     def join
384:       respawn = true
385:       last_check = Time.now
386: 
387:       proc_name 'master'
388:       logger.info "master process ready" # test_exec.rb relies on this message
389:       if ready_pipe
390:         ready_pipe.syswrite($$.to_s)
391:         ready_pipe.close rescue nil
392:         self.ready_pipe = nil
393:       end
394:       begin
395:         loop do
396:           reap_all_workers
397:           case SIG_QUEUE.shift
398:           when nil
399:             # avoid murdering workers after our master process (or the
400:             # machine) comes out of suspend/hibernation
401:             if (last_check + timeout) >= (last_check = Time.now)
402:               murder_lazy_workers
403:             else
404:               # wait for workers to wakeup on suspend
405:               master_sleep(timeout/2.0 + 1)
406:             end
407:             maintain_worker_count if respawn
408:             master_sleep(1)
409:           when :QUIT # graceful shutdown
410:             break
411:           when :TERM, :INT # immediate shutdown
412:             stop(false)
413:             break
414:           when :USR1 # rotate logs
415:             logger.info "master reopening logs..."
416:             Unicorn::Util.reopen_logs
417:             logger.info "master done reopening logs"
418:             kill_each_worker(:USR1)
419:           when :USR2 # exec binary, stay alive in case something went wrong
420:             reexec
421:           when :WINCH
422:             if Process.ppid == 1 || Process.getpgrp != $$
423:               respawn = false
424:               logger.info "gracefully stopping all workers"
425:               kill_each_worker(:QUIT)
426:               self.worker_processes = 0
427:             else
428:               logger.info "SIGWINCH ignored because we're not daemonized"
429:             end
430:           when :TTIN
431:             respawn = true
432:             self.worker_processes += 1
433:           when :TTOU
434:             self.worker_processes -= 1 if self.worker_processes > 0
435:           when :HUP
436:             respawn = true
437:             if config.config_file
438:               load_config!
439:               redo # immediate reaping since we may have QUIT workers
440:             else # exec binary and exit if there's no config file
441:               logger.info "config_file not present, reexecuting binary"
442:               reexec
443:               break
444:             end
445:           end
446:         end
447:       rescue Errno::EINTR
448:         retry
449:       rescue => e
450:         logger.error "Unhandled master loop exception #{e.inspect}."
451:         logger.error e.backtrace.join("\n")
452:         retry
453:       end
454:       stop # gracefully shutdown all workers on our way out
455:       logger.info "master complete"
456:       unlink_pid_safe(pid) if pid
457:     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 350
350:     def listen(address, opt = {}.merge(listener_opts[address] || {}))
351:       address = config.expand_addr(address)
352:       return if String === address && listener_names.include?(address)
353: 
354:       delay = opt[:delay] || 0.5
355:       tries = opt[:tries] || 5
356:       begin
357:         io = bind_listen(address, opt)
358:         unless TCPServer === io || UNIXServer === io
359:           IO_PURGATORY << io
360:           io = server_cast(io)
361:         end
362:         logger.info "listening on addr=#{sock_name(io)} fd=#{io.fileno}"
363:         LISTENERS << io
364:         io
365:       rescue Errno::EADDRINUSE => err
366:         logger.error "adding listener failed addr=#{address} (in use)"
367:         raise err if tries == 0
368:         tries -= 1
369:         logger.error "retrying in #{delay} seconds "                       "(#{tries < 0 ? 'infinite' : tries} tries left)"
370:         sleep(delay)
371:         retry
372:       rescue => err
373:         logger.fatal "error adding listener addr=#{address}"
374:         raise err
375:       end
376:     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
     # 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:           if x == reexec_pid && pid =~ /\.oldbin\z/
317:             logger.warn("will not set pid=#{path} while reexec-ed "                         "child is running PID:#{x}")
318:             return
319:           end
320:           raise ArgumentError, "Already running on PID:#{x} "                                 "(or pid=#{path} is stale)"
321:         end
322:       end
323:       unlink_pid_safe(pid) if pid
324: 
325:       if path
326:         fp = begin
327:           tmp = "#{File.dirname(path)}/#{rand}.#$$"
328:           File.open(tmp, File::RDWR|File::CREAT|File::EXCL, 0644)
329:         rescue Errno::EEXIST
330:           retry
331:         end
332:         fp.syswrite("#$$\n")
333:         File.rename(fp.path, path)
334:         fp.close
335:       end
336:       super(path)
337:     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
     # File lib/unicorn.rb, line 305
305:     def stderr_path=(path); redirect_io($stderr, path); end
stdout_path=(path) click to toggle source
     # 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 460
460:     def stop(graceful = true)
461:       self.listeners = []
462:       limit = Time.now + timeout
463:       until WORKERS.empty? || Time.now > limit
464:         kill_each_worker(graceful ? :QUIT : :TERM)
465:         sleep(0.1)
466:         reap_all_workers
467:       end
468:       kill_each_worker(:KILL)
469:     end

Private Instance Methods

awaken_master() click to toggle source
     # File lib/unicorn.rb, line 500
500:     def awaken_master
501:       begin
502:         SELF_PIPE[1].write_nonblock('.') # wakeup master process from select
503:       rescue Errno::EAGAIN, Errno::EINTR
504:         # pipe is full, master should wake up anyways
505:         retry
506:       end
507:     end
build_app!() click to toggle source
     # File lib/unicorn.rb, line 811
811:     def build_app!
812:       if app.respond_to?(:arity) && app.arity == 0
813:         if defined?(Gem) && Gem.respond_to?(:refresh)
814:           logger.info "Refreshing Gem list"
815:           Gem.refresh
816:         end
817:         self.app = app.call
818:       end
819:     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 623
623:     def handle_error(client, e)
624:       msg = case e
625:       when EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,Errno::EBADF
626:         Const::ERROR_500_RESPONSE
627:       when HttpParserError # try to tell the client they're bad
628:         Const::ERROR_400_RESPONSE
629:       else
630:         logger.error "Read error: #{e.inspect}"
631:         logger.error e.backtrace.join("\n")
632:         Const::ERROR_500_RESPONSE
633:       end
634:       client.write_nonblock(msg)
635:       client.close
636:       rescue
637:         nil
638:     end
init_self_pipe!() click to toggle source
     # File lib/unicorn.rb, line 831
831:     def init_self_pipe!
832:       SELF_PIPE.each { |io| io.close rescue nil }
833:       SELF_PIPE.replace(IO.pipe)
834:       SELF_PIPE.each { |io| io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) }
835:     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 660
660:     def init_worker_process(worker)
661:       QUEUE_SIGS.each { |sig| trap(sig, nil) }
662:       trap(:CHLD, 'DEFAULT')
663:       SIG_QUEUE.clear
664:       proc_name "worker[#{worker.nr}]"
665:       START_CTX.clear
666:       init_self_pipe!
667:       WORKERS.values.each { |other| other.tmp.close rescue nil }
668:       WORKERS.clear
669:       LISTENERS.each { |sock| sock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) }
670:       worker.tmp.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
671:       after_fork.call(self, worker) # can drop perms
672:       worker.user(*user) if user.kind_of?(Array) && ! worker.switched
673:       self.timeout /= 2.0 # halve it for select()
674:       build_app! unless preload_app
675:     end
kill_each_worker(signal) click to toggle source

delivers a signal to each worker

     # File lib/unicorn.rb, line 761
761:     def kill_each_worker(signal)
762:       WORKERS.keys.each { |wpid| kill_worker(signal, wpid) }
763:     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 752
752:     def kill_worker(signal, wpid)
753:       begin
754:         Process.kill(signal, wpid)
755:       rescue Errno::ESRCH
756:         worker = WORKERS.delete(wpid) and worker.tmp.close rescue nil
757:       end
758:     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 807
807:     def listener_names(listeners = LISTENERS)
808:       listeners.map { |io| sock_name(io) }
809:     end
load_config!() click to toggle source
     # File lib/unicorn.rb, line 787
787:     def load_config!
788:       loaded_app = app
789:       begin
790:         logger.info "reloading config_file=#{config.config_file}"
791:         config[:listeners].replace(init_listeners)
792:         config.reload
793:         config.commit!(self)
794:         kill_each_worker(:QUIT)
795:         Unicorn::Util.reopen_logs
796:         self.app = orig_app
797:         build_app! if preload_app
798:         logger.info "done reloading config_file=#{config.config_file}"
799:       rescue StandardError, LoadError, SyntaxError => e
800:         logger.error "error reloading config_file=#{config.config_file}: "                       "#{e.class} #{e.message} #{e.backtrace}"
801:         self.app = loaded_app
802:       end
803:     end
maintain_worker_count() click to toggle source
     # File lib/unicorn.rb, line 611
611:     def maintain_worker_count
612:       (off = WORKERS.size - worker_processes) == 0 and return
613:       off < 0 and return spawn_missing_workers
614:       WORKERS.dup.each_pair { |wpid,w|
615:         w.nr >= worker_processes and kill_worker(:QUIT, wpid) rescue nil
616:       }
617:     end
master_sleep(sec) 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 491
491:     def master_sleep(sec)
492:       begin
493:         IO.select([ SELF_PIPE[0] ], nil, nil, sec) or return
494:         SELF_PIPE[0].read_nonblock(Const::CHUNK_SIZE, HttpRequest::BUF)
495:       rescue Errno::EAGAIN, Errno::EINTR
496:         break
497:       end while true
498:     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 586
586:     def murder_lazy_workers
587:       WORKERS.dup.each_pair do |wpid, worker|
588:         stat = worker.tmp.stat
589:         # skip workers that disable fchmod or have never fchmod-ed
590:         stat.mode == 0100600 and next
591:         (diff = (Time.now - stat.ctime)) <= timeout and next
592:         logger.error "worker=#{worker.nr} PID:#{wpid} timeout "                       "(#{diff}s > #{timeout}s), killing"
593:         kill_worker(:KILL, wpid) # take no prisoners for timeout violations
594:       end
595:     end
proc_name(tag) click to toggle source
     # File lib/unicorn.rb, line 821
821:     def proc_name(tag)
822:       $0 = ([ File.basename(START_CTX[0]), tag
823:             ]).concat(START_CTX[:argv]).join(' ')
824:     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 642
642:     def process_client(client)
643:       client.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
644:       response = app.call(env = REQUEST.read(client))
645: 
646:       if 100 == response[0].to_i
647:         client.write(Const::EXPECT_100_RESPONSE)
648:         env.delete(Const::HTTP_EXPECT)
649:         response = app.call(env)
650:       end
651:       HttpResponse.write(client, response, HttpRequest::PARSER.headers?)
652:     rescue => e
653:       handle_error(client, e)
654:     end
reap_all_workers() click to toggle source

reaps all unreaped workers

     # File lib/unicorn.rb, line 510
510:     def reap_all_workers
511:       begin
512:         loop do
513:           wpid, status = Process.waitpid2(1, Process::WNOHANG)
514:           wpid or break
515:           if reexec_pid == wpid
516:             logger.error "reaped #{status.inspect} exec()-ed"
517:             self.reexec_pid = 0
518:             self.pid = pid.chomp('.oldbin') if pid
519:             proc_name 'master'
520:           else
521:             worker = WORKERS.delete(wpid) and worker.tmp.close rescue nil
522:             logger.info "reaped #{status.inspect} "                          "worker=#{worker.nr rescue 'unknown'}"
523:           end
524:         end
525:       rescue Errno::ECHILD
526:       end
527:     end
redirect_io(io, path) click to toggle source
     # File lib/unicorn.rb, line 826
826:     def redirect_io(io, path)
827:       File.open(path, 'ab') { |fp| io.reopen(fp) } if path
828:       io.sync = true
829:     end
reexec() click to toggle source

reexecutes the START_CTX with a new binary

     # File lib/unicorn.rb, line 531
531:     def reexec
532:       if reexec_pid > 0
533:         begin
534:           Process.kill(0, reexec_pid)
535:           logger.error "reexec-ed child already running PID:#{reexec_pid}"
536:           return
537:         rescue Errno::ESRCH
538:           self.reexec_pid = 0
539:         end
540:       end
541: 
542:       if pid
543:         old_pid = "#{pid}.oldbin"
544:         prev_pid = pid.dup
545:         begin
546:           self.pid = old_pid  # clear the path for a new pid file
547:         rescue ArgumentError
548:           logger.error "old PID:#{valid_pid?(old_pid)} running with "                         "existing pid=#{old_pid}, refusing rexec"
549:           return
550:         rescue => e
551:           logger.error "error writing pid=#{old_pid} #{e.class} #{e.message}"
552:           return
553:         end
554:       end
555: 
556:       self.reexec_pid = fork do
557:         listener_fds = LISTENERS.map { |sock| sock.fileno }
558:         ENV['UNICORN_FD'] = listener_fds.join(',')
559:         Dir.chdir(START_CTX[:cwd])
560:         cmd = [ START_CTX[0] ].concat(START_CTX[:argv])
561: 
562:         # avoid leaking FDs we don't know about, but let before_exec
563:         # unset FD_CLOEXEC, if anything else in the app eventually
564:         # relies on FD inheritence.
565:         (3..1024).each do |io|
566:           next if listener_fds.include?(io)
567:           io = IO.for_fd(io) rescue nil
568:           io or next
569:           IO_PURGATORY << io
570:           io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
571:         end
572:         logger.info "executing #{cmd.inspect} (in #{Dir.pwd})"
573:         before_exec.call(self)
574:         exec(*cmd)
575:       end
576:       proc_name 'master (old)'
577:     end
reopen_worker_logs(worker_nr) click to toggle source
     # File lib/unicorn.rb, line 677
677:     def reopen_worker_logs(worker_nr)
678:       logger.info "worker=#{worker_nr} reopening logs..."
679:       Unicorn::Util.reopen_logs
680:       logger.info "worker=#{worker_nr} done reopening logs"
681:       init_self_pipe!
682:     end
spawn_missing_workers() click to toggle source
     # File lib/unicorn.rb, line 598
598:     def spawn_missing_workers
599:       (0...worker_processes).each do |worker_nr|
600:         WORKERS.values.include?(worker_nr) and next
601:         worker = Worker.new(worker_nr, Unicorn::Util.tmpio)
602:         before_fork.call(self, worker)
603:         WORKERS[fork {
604:           ready_pipe.close if ready_pipe
605:           self.ready_pipe = nil
606:           worker_loop(worker)
607:         }] = worker
608:       end
609:     end
trap_deferred(signal) click to toggle source

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

     # File lib/unicorn.rb, line 478
478:     def trap_deferred(signal)
479:       trap(signal) do |sig_nr|
480:         if SIG_QUEUE.size < 5
481:           SIG_QUEUE << signal
482:           awaken_master
483:         else
484:           logger.error "ignoring SIG#{signal}, queue=#{SIG_QUEUE.inspect}"
485:         end
486:       end
487:     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 775
775:     def valid_pid?(path)
776:       wpid = File.read(path).to_i
777:       wpid <= 0 and return nil
778:       begin
779:         Process.kill(0, wpid)
780:         wpid
781:       rescue Errno::ESRCH
782:         # don't unlink stale pid files, racy without non-portable locking...
783:       end
784:       rescue Errno::ENOENT
785:     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 687
687:     def worker_loop(worker)
688:       ppid = master_pid
689:       init_worker_process(worker)
690:       nr = 0 # this becomes negative if we need to reopen logs
691:       alive = worker.tmp # tmp is our lifeline to the master process
692:       ready = LISTENERS
693: 
694:       # closing anything we IO.select on will raise EBADF
695:       trap(:USR1) { nr = 65536; SELF_PIPE[0].close rescue nil }
696:       trap(:QUIT) { alive = nil; LISTENERS.each { |s| s.close rescue nil } }
697:       [:TERM, :INT].each { |sig| trap(sig) { exit!(0) } } # instant shutdown
698:       logger.info "worker=#{worker.nr} ready"
699:       m = 0
700: 
701:       begin
702:         nr < 0 and reopen_worker_logs(worker.nr)
703:         nr = 0
704: 
705:         # we're a goner in timeout seconds anyways if alive.chmod
706:         # breaks, so don't trap the exception.  Using fchmod() since
707:         # futimes() is not available in base Ruby and I very strongly
708:         # prefer temporary files to be unlinked for security,
709:         # performance and reliability reasons, so utime is out.  No-op
710:         # changes with chmod doesn't update ctime on all filesystems; so
711:         # we change our counter each and every time (after process_client
712:         # and before IO.select).
713:         alive.chmod(m = 0 == m ? 1 : 0)
714: 
715:         ready.each do |sock|
716:           begin
717:             process_client(sock.accept_nonblock)
718:             nr += 1
719:             alive.chmod(m = 0 == m ? 1 : 0)
720:           rescue Errno::EAGAIN, Errno::ECONNABORTED
721:           end
722:           break if nr < 0
723:         end
724: 
725:         # make the following bet: if we accepted clients this round,
726:         # we're probably reasonably busy, so avoid calling select()
727:         # and do a speculative accept_nonblock on ready listeners
728:         # before we sleep again in select().
729:         redo unless nr == 0 # (nr < 0) => reopen logs
730: 
731:         ppid == Process.ppid or return
732:         alive.chmod(m = 0 == m ? 1 : 0)
733:         begin
734:           # timeout used so we can detect parent death:
735:           ret = IO.select(LISTENERS, nil, SELF_PIPE, timeout) or redo
736:           ready = ret[0]
737:         rescue Errno::EINTR
738:           ready = LISTENERS
739:         rescue Errno::EBADF
740:           nr < 0 or return
741:         end
742:       rescue => e
743:         if alive
744:           logger.error "Unhandled listen loop exception #{e.inspect}."
745:           logger.error e.backtrace.join("\n")
746:         end
747:       end while alive
748:     end

Disabled; run with --debug to generate this.

[Validate]

Generated with the Darkfish Rdoc Generator 1.1.6.