diff --git a/docs/content/news.md b/docs/content/news.md index 76c533b2d..2f83d23a8 100644 --- a/docs/content/news.md +++ b/docs/content/news.md @@ -1,6 +1,15 @@ # Changelog +## Unreleased + +### Bug Fixes + +- Worker timeout used to dump a 500 response onto a body that had already + started, because `sys.exit()` from the abort handler is a `BaseException`. + Once headers are out we just close the connection. + ([#3410](https://github.com/benoitc/gunicorn/issues/3410)). + ## 26.2.0 - 2026-08-24 ### New Features diff --git a/gunicorn/workers/base_async.py b/gunicorn/workers/base_async.py index 9adc913a7..a3aea4c5f 100644 --- a/gunicorn/workers/base_async.py +++ b/gunicorn/workers/base_async.py @@ -306,10 +306,10 @@ def handle_request(self, listener_name, req, sock, addr): # If the original exception was a socket.error we delegate # handling it to the caller (where handle() might ignore it) util.reraise(*sys.exc_info()) - except Exception: + except (Exception, SystemExit): if resp and resp.headers_sent: - # If the requests have already been sent, we should close the - # connection to indicate the error. + # Timeout abort is SystemExit. If we already started the + # response, writing a 500 on the same socket corrupts it. self.log.exception("Error handling request") try: sock.shutdown(socket.SHUT_RDWR) diff --git a/gunicorn/workers/gthread.py b/gunicorn/workers/gthread.py index 08391eb1d..a5a61a460 100644 --- a/gunicorn/workers/gthread.py +++ b/gunicorn/workers/gthread.py @@ -730,10 +730,10 @@ def handle_request(self, req, conn): except OSError: # pass to next try-except level util.reraise(*sys.exc_info()) - except Exception: + except (Exception, SystemExit): if resp and resp.headers_sent: - # If the requests have already been sent, we should close the - # connection to indicate the error. + # Timeout abort is SystemExit. If we already started the + # response, writing a 500 on the same socket corrupts it. self.log.exception("Error handling request") util.close_graceful(conn.sock) raise StopIteration() diff --git a/gunicorn/workers/sync.py b/gunicorn/workers/sync.py index c11597f22..cde3fb212 100644 --- a/gunicorn/workers/sync.py +++ b/gunicorn/workers/sync.py @@ -197,10 +197,10 @@ def handle_request(self, listener, req, client, addr): except OSError: # pass to next try-except level util.reraise(*sys.exc_info()) - except Exception: + except (Exception, SystemExit): if resp and resp.headers_sent: - # If the requests have already been sent, we should close the - # connection to indicate the error. + # Timeout abort is SystemExit. If we already started the + # response, writing a 500 on the same socket corrupts it. self.log.exception("Error handling request") util.close_graceful(client) raise StopIteration()