From 4c601ce447fafbeed27f0f0a238e0e48c928b6f9 Mon Sep 17 00:00:00 2001 From: benoitc Date: Sat, 25 Oct 2014 11:58:28 +0200 Subject: [PATCH] optimize the sync worker This change optimize the sync worker when we only have to listen on one interface. While I'm here, I fixed a long and unnoticed outstanding issue when we were accepting on multiple interfaces (wonder if someone really use it), at some point soe interfaces were skipped. --- gunicorn/workers/sync.py | 113 +++++++++++++++++++++++++-------------- 1 file changed, 72 insertions(+), 41 deletions(-) diff --git a/gunicorn/workers/sync.py b/gunicorn/workers/sync.py index 12a50f873..42f0c68e0 100644 --- a/gunicorn/workers/sync.py +++ b/gunicorn/workers/sync.py @@ -21,13 +21,38 @@ class SyncWorker(base.Worker): - def run(self): - # self.socket appears to lose its blocking status after - # we fork in the arbiter. Reset it here. - for s in self.sockets: - s.setblocking(0) + def accept(self, listener): + client, addr = listener.accept() + client.setblocking(1) + util.close_on_exec(client) + self.handle(listener, client, addr) + + def wait(self, timeout): + try: + self.notify() + ret = select.select(self.sockets, [], self.PIPE, timeout) + if ret[0]: + return ret[0] + + except select.error as e: + if e.args[0] == errno.EINTR: + return self.sockets + if e.args[0] == errno.EBADF: + if self.nr < 0: + return self.sockets + else: + return False + raise + + def is_parent_alive(self): + # If our parent changed then we shut down. + if self.ppid != os.getppid(): + self.log.info("Parent changed, shutting down: %s", self) + return False + return True - ready = self.sockets + def run_for_one(self, timeout): + listener = self.sockets[0] while self.alive: self.notify() @@ -35,51 +60,57 @@ def run(self): # that no connection is waiting we fall down to the # select which is where we'll wait for a bit for new # workers to come give us some love. + try: + self.accept(listener) + # Keep processing clients until no one is waiting. This + # prevents the need to select() for every client that we + # process. + continue + + except socket.error as e: + if e.args[0] not in (errno.EAGAIN, errno.ECONNABORTED, + errno.EWOULDBLOCK): + raise + + if not self.is_parent_alive(): + return - for sock in ready: - try: - client, addr = sock.accept() - client.setblocking(1) - util.close_on_exec(client) - self.handle(sock, client, addr) + if not self.wait(timeout): + return - # Keep processing clients until no one is waiting. This - # prevents the need to select() for every client that we - # process. - continue + def run_for_multiple(self, timeout): + while self.alive: + self.notify() + ready = self.wait(timeout) + if not ready: + return + + for listener in ready: + try: + self.accept(listener) except socket.error as e: if e.args[0] not in (errno.EAGAIN, errno.ECONNABORTED, errno.EWOULDBLOCK): raise - # If our parent changed then we shut down. - if self.ppid != os.getppid(): - self.log.info("Parent changed, shutting down: %s", self) + if not self.is_parent_alive(): return - try: - self.notify() - - # if no timeout is given the worker will never wait and will - # use the CPU for nothing. This minimal timeout prevent it. - timeout = self.timeout or 0.5 - - ret = select.select(self.sockets, [], self.PIPE, timeout) - if ret[0]: - ready = ret[0] - continue - except select.error as e: - if e.args[0] == errno.EINTR: - ready = self.sockets - continue - if e.args[0] == errno.EBADF: - if self.nr < 0: - ready = self.sockets - continue - else: - return - raise + def run(self): + # if no timeout is given the worker will never wait and will + # use the CPU for nothing. This minimal timeout prevent it. + timeout = self.timeout or 0.5 + + # self.socket appears to lose its blocking status after + # we fork in the arbiter. Reset it here. + for s in self.sockets: + s.setblocking(0) + + if len(self.sockets) > 1: + self.run_for_multiple(timeout) + else: + self.run_for_one(timeout) def handle(self, listener, client, addr): req = None