Skip to content

Commit

Permalink
Fix eventlet graceful timeout handling
Browse files Browse the repository at this point in the history
The `StopServer` exception can lead to a handler blocked waiting for
an available greenthread to never be processed.
This change ensures we attempt to handle any accepted socket connection
within the graceful timeout period.
  • Loading branch information
mjjbell authored and Michael Bell committed Mar 15, 2018
1 parent d1f5268 commit 70c3b91
Show file tree
Hide file tree
Showing 3 changed files with 17 additions and 11 deletions.
3 changes: 1 addition & 2 deletions gunicorn/workers/async.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,11 @@ def is_already_handled(self, respiter):
# some workers will need to overload this function to raise a StopIteration
return respiter == ALREADY_HANDLED

def handle(self, listener, client, addr):
def handle(self, listener_name, client, addr):
req = None
try:
parser = http.RequestParser(self.cfg, client)
try:
listener_name = listener.getsockname()
if not self.cfg.keepalive:
req = six.next(parser)
self.handle_request(listener_name, req, client, addr)
Expand Down
18 changes: 12 additions & 6 deletions gunicorn/workers/geventlet.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,18 @@ def _eventlet_serve(sock, handle, concurrency):
pool = eventlet.greenpool.GreenPool(concurrency)
server_gt = eventlet.greenthread.getcurrent()

def _eventlet_handle(conn, addr):
gt = pool.spawn(handle, conn, addr)
gt.link(_eventlet_stop, server_gt, conn)

while True:
try:
conn, addr = sock.accept()
gt = pool.spawn(handle, conn, addr)
gt.link(_eventlet_stop, server_gt, conn)
conn, addr, gt = None, None, None
_eventlet_handle(conn, addr)
conn, addr = None, None
except eventlet.StopServe:
if conn and addr:
_eventlet_handle(conn, addr)
sock.close()
pool.waitall()
return
Expand Down Expand Up @@ -111,19 +116,20 @@ def handle_usr1(self, sig, frame):
def timeout_ctx(self):
return eventlet.Timeout(self.cfg.keepalive or None, False)

def handle(self, listener, client, addr):
def handle(self, listener_name, client, addr):
if self.cfg.is_ssl:
client = eventlet.wrap_ssl(client, server_side=True,
**self.cfg.ssl_options)

super(EventletWorker, self).handle(listener, client, addr)
super(EventletWorker, self).handle(listener_name, client, addr)

def run(self):
acceptors = []
for sock in self.sockets:
gsock = GreenSocket(sock)
gsock.setblocking(1)
hfun = partial(self.handle, gsock)
listener_name = gsock.getsockname()
hfun = partial(self.handle, listener_name)
acceptor = eventlet.spawn(_eventlet_serve, gsock, hfun,
self.worker_connections)

Expand Down
7 changes: 4 additions & 3 deletions gunicorn/workers/ggevent.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@ def run(self):
handler_class=self.wsgi_handler, environ=environ,
**ssl_args)
else:
hfun = partial(self.handle, s)
listener_name = s.getsockname()
hfun = partial(self.handle, listener_name)
server = StreamServer(s, handle=hfun, spawn=pool, **ssl_args)

server.start()
Expand Down Expand Up @@ -148,11 +149,11 @@ def run(self):
except:
pass

def handle(self, listener, client, addr):
def handle(self, listener_name, client, addr):
# Connected socket timeout defaults to socket.getdefaulttimeout().
# This forces to blocking mode.
client.setblocking(1)
super(GeventWorker, self).handle(listener, client, addr)
super(GeventWorker, self).handle(listener_name, client, addr)

def handle_request(self, listener_name, req, sock, addr):
try:
Expand Down

0 comments on commit 70c3b91

Please sign in to comment.