Skip to content

Commit

Permalink
optimize the sync worker
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
benoitc committed Oct 25, 2014
1 parent c152ce0 commit 4c601ce
Showing 1 changed file with 72 additions and 41 deletions.
113 changes: 72 additions & 41 deletions gunicorn/workers/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,65 +21,96 @@

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()

# Accept a connection. If we get an error telling us
# 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
Expand Down

2 comments on commit 4c601ce

@matrixise
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you explain your commit? Thanks

@benoitc
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's actually pretty simple, it remove the unnecessary calls for the simple case when gunicorn is listening on only one interface. T

he multiple interface case was bugged because we were only exhausting ready sockets and continue until it fails. While at first ready were all sockets, once they come from the select, they are only the readable sockets and we were excluding from time to times some interfaces for a long time.

Please sign in to comment.