From dfcc93953d761f7e13c04800d4d3b35257d4b840 Mon Sep 17 00:00:00 2001 From: Tim Burke Date: Tue, 6 Feb 2024 05:50:13 -0800 Subject: [PATCH] wsgi: Handle Timeouts from applications (#911) 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. --- eventlet/wsgi.py | 2 +- tests/wsgi_test.py | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/eventlet/wsgi.py b/eventlet/wsgi.py index 1308c02eb..b9b428f7f 100644 --- a/eventlet/wsgi.py +++ b/eventlet/wsgi.py @@ -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) diff --git a/tests/wsgi_test.py b/tests/wsgi_test.py index efb681342..9b4fce24f 100644 --- a/tests/wsgi_test.py +++ b/tests/wsgi_test.py @@ -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):