def work(self): error_log.info('Entering endless loop of processing sockets.') while True: sock, address = (None, None) # noinspection PyBroadException try: if not self.connections: self.recv_new_sockets() sock, address = self.connections.popleft() self.handle_connection(socket=sock, address=address) except SignalReceivedException as err: if err.signum == ws.signals.SIGTERM: error_log.info('Breaking work() loop due to signal %s.', ws.signals.Signals(err.signum).name) break else: error_log.exception('Unknown signal during work() loop') except KeyboardInterrupt: break except Exception: error_log.exception('Exception occurred during work() loop.') continue finally: if sock: sock.shutdown(ws.sockets.SHUT_RDWR, pass_silently=True) sock.close(pass_silently=True) # noinspection PyUnreachableCode self.cleanup() return 0
def cleanup(self): """ Closing listening socket and reap children. This method sleeps for the maximum timeout of SIGTERM signal sent to a worker. (Worker.sigterm_timeout) """ # don't cleanup workers because their sockets were already closed # during the self.fork_worker() call if self.execution_context == self.ExecutionContext.worker: return error_log.info("Closing server's listening socket") try: self.sock.close() except OSError: error_log.exception('close() on listening socket failed.') ws.signals.signal(ws.signals.SIGCHLD, ws.signals.SIG_DFL) active_workers = [ worker for worker in self.workers.values() if worker.pid not in self.reaped_pids ] for worker in active_workers: worker.terminate() if active_workers: timeout = max(worker.sigterm_timeout for worker in active_workers) error_log.info('Waiting %s seconds for children to finish.', timeout) time.sleep(timeout) for worker in active_workers: pid, exit = os.waitpid(worker.pid, os.WNOHANG) if not pid: worker.kill_if_hanged()
def close(self, pass_silently=False): error_log.debug2('Closing socket %d', self.fileno()) if pass_silently: try: super().close() except OSError: error_log.exception('Closing socket %s failed.', self.fileno()) else: super().close()
def shutdown(self, how, pass_silently=False): error_log.debug2(SHUTDOWN_MSGS[how], super().fileno()) if pass_silently: try: super().shutdown(how) except OSError: error_log.exception('Shutting down socket %s failed.', self.fileno()) else: super().shutdown(how)
def cleanup(self): error_log.info('Cleaning up... %s total leftover connections.', len(self.connections)) self.fd_transport.discard() for sock, address in self.connections: # noinspection PyBroadException try: res = hutils.build_response(503) self.handle_connection(socket=sock, address=address, quick_reply_with=res) except Exception: error_log.exception( 'Error while cleaning up client on ' '%s / %s', sock, address) finally: sock.close(pass_silently=True)
def __exit__(self, exc_type, exc_val, exc_tb): if not exc_val: return False if self.exchange['written']: error_log.warning( 'An exception occurred after worker had sent bytes over ' 'the socket(fileno=%s). Client will receive an invalid ' 'HTTP response.', self.sock.fileno() ) return False if exc_handler.can_handle(exc_val): response, suppress = exc_handler.handle(exc_val) if not response: # no need to send back a response return suppress else: error_log.exception('Could not handle exception. Client will ' 'receive a 500 Internal Server Error.') response, suppress = ws.http.utils.build_response(500), False response.headers['Connection'] = 'close' try: self.respond(response) except OSError as e: error_log.warning('During cleanup of worker tried to respond to ' 'client and close the connection but: ' 'caught OSError with ERRNO=%s and MSG=%s', e.errno, e.strerror) if e.errno == errno.ECONNRESET: error_log.warning('Client stopped listening prematurely.' ' (no Connection: close header was received)') return suppress else: raise return suppress
def main(): # Main process should never exit from the server.listen() loop unless # an exception occurs. fd_limit = subprocess.check_output(['ulimit', '-n'], shell=True) error_log.info('ulimit -n is "%s"', fd_limit.decode('ascii')) server = Server() server.setup() with profile(SERVER_PROFILING_ON): # noinspection PyBroadException try: server.listen() except SignalReceivedException as err: if err.signum == ws.signals.SIGTERM: error_log.info('SIGTERM signal broke listen() loop.') else: error_log.exception('Unknown signal broke listen() loop.') except KeyboardInterrupt: error_log.info('KeyboardInterrupt broke listen() loop.') except BaseException: error_log.exception('Unhandled exception broke listen() loop.') finally: server.cleanup()
def fork_workers(self, count=1): error_log.info('Forking %s workers', self.process_count_limit) assert isinstance(count, int) for _ in range(count): fd_transport = ws.sockets.FDTransport() pid = os.fork() if pid: self.execution_context = self.ExecutionContext.main error_log.debug('Forked worker with pid=%s', pid) ws.sockets.randomize_ssl_after_fork() fd_transport.mode = 'sender' wp = WorkerProcess(pid=pid, fd_transport=fd_transport) self.workers[wp.pid] = wp else: self.execution_context = self.ExecutionContext.worker ws.signals.reset_handlers(excluding={ws.signals.SIGTERM}) ws.signals.signal(ws.signals.SIGCHLD, ws.signals.SIG_IGN) # noinspection PyBroadException try: ws.logs.setup_worker_handlers() fd_transport.mode = 'receiver' for other_worker in self.workers.values(): other_worker.close_ipc() self.sock.close() os.close(0) os.close(1) os.close(2) with profile(WORKER_PROFILING_ON): worker = ws.worker.Worker(fd_transport=fd_transport) exit_code = worker.work() except BaseException: error_log.exception('Worker failed.') exit_code = 1 # noinspection PyProtectedMember os._exit(exit_code)
def execute_script(request, socket, body_start=b''): assert isinstance(socket, (ws.sockets.SSLSocket, ws.sockets.Socket)) assert isinstance(body_start, (bytes, bytearray)) assert can_handle_request(request) uri = request.request_line.request_target cgi_script = find_cgi_script(uri) error_log.info('Executing CGI script %s', cgi_script.name) script_env = prepare_cgi_script_env(request, socket) error_log.debug('CGIScript environment will be: %s', script_env) has_body = 'Content-Length' in request.headers try: proc = subprocess.Popen( args=os.path.abspath(cgi_script.script_path), env=script_env, stdin=subprocess.PIPE if has_body else socket.fileno(), stdout=socket.fileno()) except (OSError, ValueError): error_log.exception( 'Failed to open subprocess for cgi script {}'.format( cgi_script.name)) return ws.http.utils.build_response(500) # noinspection PyBroadException try: if not has_body: return error_log.debug('Request to CGI has body. Writing to stdin...') start = time.time() length = int(request.headers['Content-Length']) read_bytes = 0 chunk_size = 4096 timeout_reached = False while read_bytes < length and not timeout_reached: if body_start: chunk = body_start body_start = b'' else: chunk = socket.recv(chunk_size) read_bytes += len(chunk) if not chunk: break while chunk: avail = select.select([], [proc.stdin], [], cgi_script.timeout) _, wlist, _ = avail if wlist: written = wlist[0].write(chunk) chunk = chunk[written:] else: timeout_reached = True break if time.time() - start > cgi_script.timeout: timeout_reached = True if timeout_reached: error_log.warning( 'CGI script %s took too long to read body. ' 'Leaving process alive but no more data will ' 'be piped to it.', cgi_script) except ws.sockets.TimeoutException: error_log.warning("Client sent data too slowly - socket timed out.") except Exception: error_log.exception('Failed to write body to CGI script.') finally: try: proc.stdin.close() except OSError as err: error_log.warning( 'Closing CGI stdin pipe failed. ERRNO=%s MSG=%s.', err.errno, err.strerror)
def server_err_handler(exc): error_log.exception('Internal server error.') return ws.http.utils.build_response(500), True