Ejemplo n.º 1
0
 def _handle_socketio_error(self, client_fd, error):
     """Internal only. Handles error during socket io operation"""
     err_code = get_errno_from_exception(error)
     # means operation would have blocked but this is a non-blocking socket or client crashed or peer socket timed out
     if err_code == errno.EAGAIN or err_code == errno.EWOULDBLOCK:
         pass
     elif (err_code == errno.ECONNRESET or err_code == errno.ECONNABORTED
           or err_code == errno.ETIMEDOUT or err_code == errno.EPIPE):
         self.close_client_connection(client_fd)
Ejemplo n.º 2
0
def test_get_errno_from_exception(get_exception_obj):
    errno = utils.get_errno_from_exception(get_exception_obj)
    assert errno == None, "returns none if errno, args not present"
Ejemplo n.º 3
0
def test_get_errno_from_exception(get_exception_obj):
    get_exception_obj.args = [1, 2, 3, 4]
    errno = utils.get_errno_from_exception(get_exception_obj)
    assert errno == 1, "correctly gets errno from exception args"
Ejemplo n.º 4
0
def test_get_errno_from_exception(get_exception_obj):
    get_exception_obj.errno = 1
    errno = utils.get_errno_from_exception(get_exception_obj)
    assert errno == 1, "correctly gets errno from exception errno"
Ejemplo n.º 5
0
    def serve(self, processes=1):
        """Starts the server that listens on PORT and HOST

        The function to listen to incoming requests using asynchronous I/O. This is a non-blocking server
        Args:
            processes: Number of processes that will accept incoming requests
        """
        try:
            if processes > 1:
                self._process_manager = ProcessManagerFactory.getProcessManager(
                )
                self._process_manager.fork_processes(processes)
            # always create epoll after forking process as otherwise file descriptor to same epoll instance will be duplicated
            # and all of them will watch same socket descriptors. The line below however creates separate socket objects for different processes
            # all connected to same hardware underneath. This creates a problem: When a new connection is received, poll() of
            # individual processes will notify that their socket is ready to accept. But which one of those should accept as all cannot.
            # This problem is present due to level-triggered behavior. In edge triggered, only one gets notified
            # The problem is solved by using EPOLLEXCLUSIVE flag when registering with Epoll.
            # when epoll is not available, it is solved by SO_REUSEPORT flag when creating socket. The only problem with this is when a process
            # dies, it may not be possible to switchover ongoing requests on that process to others which is ok.
            # For systems like Unix/MacOS that dont support both EPOLLEXCLUSIVE and SO_REUSEPORT, we can use mutexes, but since
            # number of processes is gonna be less or equal to number of CPUs, we can simply ignore it even though it leads to wasted CPU cycles
            # on every connection request. Not recommended to do this in production
            all_sockets = self._create_nonblocking_server()
            self._listener_socket_fds = {}
            for s in all_sockets:
                self._listener_socket_fds[s.fileno()] = s

            while True:
                # acquire lock here if implemented
                events = self._non_block_io.poll(1)
                for client_fd, event_code in events:
                    if client_fd in self._listener_socket_fds:
                        try:
                            # accepts incoming requests
                            listener_socket = self._listener_socket_fds[
                                client_fd]
                            connection, address = listener_socket.accept()
                        except ConnectionAbortedError:
                            # ECONNABORTED indicates that there was a connection
                            # but it was closed while still in the accept queue.
                            continue
                        except IOError as e:
                            err_code = get_errno_from_exception(e)
                            # restart 'accept' if it was interrupted
                            if err_code == errno.EINTR or err_code == errno.EAGAIN:
                                continue
                            else:
                                raise
                        # release lock here if implemented, add logic to not
                        # release it multiple times as this is in a loop
                        self.init_request_async(connection, address)
                    else:
                        # release lock here too if implemented, add logic to not
                        # release it multiple times as this is in a loop
                        self._handle_poll(client_fd, event_code)

        except Exception:
            traceback.print_exc()
            print("Exception occurred. Server shutting down ...")

        finally:
            # python will auto cleanup these sockets when server crashes. These are just
            # added for good faith and to show whats going on. Has no performance impact as server
            # is crashing
            # server is going down, OS will auto cleanup client connections
            # so no need to clean them up. It will also reclaim all memory from server
            # so need to delete _requests, _responses, _handlers or _connections
            for fd in self._listener_socket_fds:
                self._non_block_io.unregister(fd)
                self._listener_socket_fds[fd].close()

            # close FDs associated with io
            self._non_block_io.close()

            # kill all processes if parent crashed, otherwise just kill this one
            # actually killing current process is not needed as it's dying anyway
            # this is added just to make sure OS will auto cleanup everything if process
            # dies for any reason
            if hasattr(self, "_process_manager"):
                self._process_manager.kill_process()
Ejemplo n.º 6
0
    def _bind_sockets(self):
        """For internal use only. Binds available sockets over ipv4 and ipv6

        This function creates a list of listener socket for our server. If underlying OS supports
        ipv6, then the socket will support both ipv4 and ipv6 connections or otherwise the
        socket will accept only ipv4 connections.

        Args:
            None
        Returns:
            A list of sockets that can be used to accept client connections.
        """
        # emtpy set
        unique_addresses = set()

        # list of available sockets
        sockets = []

        # support connections over both ipv4 and ipv6
        sock_family = socket.AF_UNSPEC

        if not self.has_ipv6():
            # OS has only ipv4 socket
            sock_family = socket.AF_INET

        for res in sorted(
                socket.getaddrinfo(
                    self._host,
                    self._port,
                    sock_family,
                    socket.SOCK_STREAM,
                    0,
                    socket.AI_PASSIVE,
                ),
                key=lambda x: x[0],
        ):
            if res in unique_addresses:
                continue

            unique_addresses.add(res)

            (
                family,
                socktype,
                proto,
                canonname,
                sockaddr,
            ) = res  # pylint: disable=unused-variable
            try:
                #  create socket
                sock = socket.socket(family, socktype, proto)
            except OSError as e:
                # means address family not supported, continue
                if get_errno_from_exception(e) == errno.EAFNOSUPPORT:
                    continue
                raise

            # add reuseaddr option
            # if server needs to be restarted, the same socket might not be available immediately
            # as kernel has not been able to free it due to TIME_WAIT. This line tells kernel to reuse the socket
            # and not throw EADDRINUSE error
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

            # Allow multiple sockets to be bound to same HOST and PORT. With this multiple processes
            # can listen on same socket and only one of them will process it. No 'Thundering Herd'
            if hasattr(socket, "SO_REUSEPORT"):
                sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)

            # make socket non blocking
            sock.setblocking(False)

            # bind socket to address
            try:
                sock.bind(sockaddr)
                print("HTTP server started, listening on {address}".format(
                    address=sock.getsockname()))
            except OSError as e:
                if (get_errno_from_exception(e) == errno.EADDRNOTAVAIL
                        and self._host == "localhost"
                        and sockaddr[0] == "::1"):
                    # On some systems (most notably docker with default
                    # configurations), ipv6 is partially disabled:
                    # socket.has_ipv6 is true, we can create AF_INET6
                    # sockets, and getaddrinfo("localhost", ...,
                    # AF_PASSIVE) resolves to ::1, but we get an error
                    # when binding.
                    #
                    # Swallow the error, but only for this specific case.
                    # If EADDRNOTAVAIL occurs in other situations, it
                    # might be a real problem like a typo in a
                    # configuration.
                    print(
                        "IPv6 disabled for localhost, it will not be available"
                    )
                    sock.close()
                    continue
                else:
                    sock.close()
                    msg = "%s (while attempting to bind on address %r)" % (
                        e.strerror,
                        (self._host, self._port),
                    )
                    raise OSError(get_errno_from_exception(e), msg) from None

            # start listening on the socket
            sock.listen(self._request_queue_size)
            sockets.append(sock)

        # return all sockets available
        return sockets