Example #1
0
 def serve(self):
     sock = get_systemd_socket()
     if sock is not None:
         log('Using inherited socket %r' % (sock.getsockname(), ))
     else:
         sock = self.get_listening_socket()
     self.serve_on_socket(sock)
Example #2
0
 def extract_session_id(self, conn):
     """Find a session indentifier for the current request.  Return
     the ID and the SCGI environment dictionary.
     """
     # we have to be careful here, we don't want to block the server
     # processing look while waiting on sockets.  In practice this
     # implementation seems to work well (tested with nginx and Apache).
     env = {}
     # select is necessary since even with MSG_PEEK the recv() can block
     # if there is no data available
     r, w, e = select.select([conn], [], [], 0.2)
     if r:
         headers = conn.recv(4096, socket.MSG_PEEK)
         headers = ns_reads(io.BytesIO(headers))
         env = parse_env(headers)
         cookies = env.get('HTTP_COOKIE')
         if cookies:
             m = re.search(self.SESSION_ID_PATTERN, cookies)
             if m:
                 return m.group('id'), env
     else:
         log('gave up waiting to peek at session id')
     ip = env.get('HTTP_X_FORWARDED_FOR') or env.get('REMOTE_ADDR')
     if ip:
         return ip, env
     try:
         return conn.getpeername()[0], env
     except socket.error:
         return self.DEFAULT_SESSION_ID, env
Example #3
0
    def delegate_request(self, conn):
        """Pass a request fd to a child process to handle.  This method
        blocks if all the children are busy and we have reached the
        max_children limit."""

        # There lots of subtleties here.  First, to determine the readiness
        # of a child we can't use the writable status of the Unix domain
        # socket connected to the child, since select will return true
        # if the buffer is not filled.  Instead, each child writes one
        # byte of data when it is ready for a request.  The normal case
        # is that a child is ready for a request.  We want that case to
        # be fast.  Also, we want to pass requests to the same child if
        # possible.  Finally, we need to gracefully handle children
        # dying at any time.

        # If no children are ready and we haven't reached max_children
        # then we want another child to be started without delay.
        timeout = 0

        # Number of times to retry delegating request.  If no child can
        # be found after those tries, give up.  In that case 'conn' will
        # be closed without handling it.
        retry_count = 30

        for i in range(retry_count):
            fds = [child.fd for child in self.children if not child.closed]
            r, w, e = select.select(fds, [], [], timeout)
            if r:
                # One or more children look like they are ready.  Sort
                # the file descriptions so that we keep preferring the
                # same child.
                child = None
                for child in self.children:
                    if not child.closed and child.fd in r:
                        break
                if child is None:
                    continue  # no child found, should not get here
                if child.process(conn):
                    return  # passed fd to child, we are done

            # didn't find any child, check if any died
            self.reap_children()

            # start more children if we haven't met max_children limit
            if len(self.children) < self.max_children:
                self.spawn_child(conn)

            # Start blocking inside select.  We might have reached
            # max_children limit and they are all busy.
            timeout = 2
        log('failed to delegate request %s' % conn)
Example #4
0
 def prune_children(self):
     n = len(self.children)
     if n == 0:
         return
     now = time.time()
     if now - self.last_prune < 20:
         return
     self.last_prune = now
     for child in list(self.children.values()):
         if self._is_old(child):
             log('closed old child (pid=%s nchild=%s)' %
                 (child.pid, len(self.children) - 1))
             child.close()
             del self.children[child.session_id]
     self.reap_children()
Example #5
0
 def delegate_request(self, conn):
     """Pass a request fd to a child process to handle.
     """
     try:
         session_id, env = self.extract_session_id(conn)
     except Exception:
         log('error extracting session id, traceback follows')
         traceback.print_exc(file=sys.stderr)
         session_id = self.DEFAULT_SESSION_ID
         env = {}
     child = self.children.get(session_id)
     if child is None or child.closed:
         child = self.spawn_child(session_id, conn)
     log('process pid=%s %s %s %s' %
         (child.pid, env.get('REMOTE_ADDR'), env.get('REQUEST_METHOD'),
          env.get('REQUEST_URI')))
     child.queue_request(conn)
Example #6
0
 def spawn_child(self, conn=None):
     parent_fd, child_fd = passfd.socketpair(socket.AF_UNIX,
                                             socket.SOCK_STREAM)
     # make child fd non-blocking
     flags = fcntl.fcntl(child_fd, fcntl.F_GETFL, 0)
     fcntl.fcntl(child_fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
     pid = os.fork()
     if pid == 0:
         if conn is not None:
             conn.close()  # in the midst of handling a request, close
             # the connection in the child
         os.close(child_fd)
         self.socket.close()
         for child in self.children:
             child.close()
         self.handler_class(parent_fd).serve()
         sys.exit(0)
     else:
         os.close(parent_fd)
         self.children.append(Child(pid, child_fd))
         log('started child (pid=%s nchild=%s)' % (pid, len(self.children)))
Example #7
0
 def log(self, msg):
     log('%s (pid=%s)' % (msg, self.pid))
Example #8
0
 def do_restart(self):
     log('restarting child processes')
     self.do_stop()
     self.restart = 0
Example #9
0
 def hup_signal(self, signum, frame):
     log('got HUP signal, scheduling restart')
     self.restart = 1