Skip to content

Commit

Permalink
wsgi: Handle Timeouts from applications (#911)
Browse files Browse the repository at this point in the history
If you're using eventlet as a web server, it's not unlikely that you'll
be using eventlet.Timeouts at some point in your application callable
or the response iterator that's returned. If they escape, don't let that
blow up the whole worker greenthread, but treat it like other exceptions.
  • Loading branch information
tipabu committed Feb 6, 2024
1 parent 799dabc commit dfcc939
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 1 deletion.
2 changes: 1 addition & 1 deletion eventlet/wsgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -631,7 +631,7 @@ def cap(x):
write(b''.join(towrite))
if not headers_sent or (use_chunked[0] and just_written_size):
write(b'')
except Exception:
except (Exception, eventlet.Timeout):
self.close_connection = 1
tb = traceback.format_exc()
self.server.log.info(tb)
Expand Down
41 changes: 41 additions & 0 deletions tests/wsgi_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1570,6 +1570,47 @@ def wsgi_app(environ, start_response):
self.assertEqual(result.status, 'HTTP/1.1 500 Internal Server Error')
self.assertEqual(result.headers_lower['connection'], 'close')
assert 'transfer-encoding' not in result.headers_lower
assert 'Traceback' in self.logfile.getvalue()
assert 'RuntimeError: intentional error' in self.logfile.getvalue()

def test_timeouts_in_app_call(self):
def wsgi_app(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/plain')])
yield b'partial '
raise eventlet.Timeout()
yield b'body\n'
self.site.application = wsgi_app
sock = eventlet.connect(self.server_addr)
sock.sendall(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
result = read_http(sock)
self.assertEqual(result.status, 'HTTP/1.1 500 Internal Server Error')
self.assertEqual(result.headers_lower['connection'], 'close')
assert 'transfer-encoding' not in result.headers_lower
assert 'content-length' in result.headers_lower
assert 'Traceback' in self.logfile.getvalue()
assert 'Timeout' in self.logfile.getvalue()

def test_timeouts_in_app_iter(self):
def wsgi_app(environ, start_response):
environ['eventlet.minimum_write_chunk_size'] = 1
start_response('200 OK', [('Content-Type', 'text/plain'),
('Content-Length', '13')])

def app_iter():
yield b'partial '
raise eventlet.Timeout()
yield b'body\n'
return app_iter()
self.site.application = wsgi_app
sock = eventlet.connect(self.server_addr)
sock.sendall(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
result = read_http(sock)
self.assertEqual(result.status, 'HTTP/1.1 200 OK')
assert 'connection' not in result.headers_lower
self.assertEqual(result.headers_lower['content-length'], '13')
self.assertEqual(len(result.body), 8)
assert 'Traceback' in self.logfile.getvalue()
assert 'Timeout' in self.logfile.getvalue()

def test_unicode_with_only_ascii_characters_works(self):
def wsgi_app(environ, start_response):
Expand Down

0 comments on commit dfcc939

Please sign in to comment.