-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Description
Hi,
one of users tried to download a file via service based on Flask, gunicorn and sync workers. He was using a very slow connection and file was pretty big, so connection was interrupted after 60 seconds due to worker timeout. Endpoint was supporting Range header, so wget made a new request for a missing part of the file and download was finished.
Unfortunately, the file appeared to be incorrect. After inspection, we noticed that ISE 500 headers and body were mixed into content:

This situation can be easily reproduced using that code:
import time
from flask import Flask, Response
app = Flask(__name__)
def stream_endlessly():
while True:
yield b'a'*16 + b'\n'
time.sleep(1)
@app.route('/')
def index():
return Response(stream_endlessly(), mimetype='text/plain')
If we spawn it using gunicorn app:app -t 10
and make a request curl http://127.0.0.1:8000 --http1.0 -vvv
(using HTTP 1.0 to avoid chunked encoding), we will see the following output:
$ curl http://127.0.0.1:8000 --http1.0 -vvv
* Trying 127.0.0.1:8000...
* Connected to 127.0.0.1 (127.0.0.1) port 8000
> GET / HTTP/1.0
> Host: 127.0.0.1:8000
> User-Agent: curl/8.5.0
> Accept: */*
>
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Server: gunicorn
< Date: Tue, 12 Aug 2025 18:51:19 GMT
< Connection: close
< Content-Type: text/plain; charset=utf-8
<
aaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaa
HTTP/1.1 500 Internal Server Error
Connection: close
Content-Type: text/html
Content-Length: 141
<html>
<head>
<title>Internal Server Error</title>
</head>
<body>
<h1><p>Internal Server Error</p></h1>
</body>
</html>
* Closing connection
The root cause is well described by the stack trace:
[2025-08-12 20:51:35 +0200] [46057] [CRITICAL] WORKER TIMEOUT (pid:46085)
[2025-08-12 20:51:35 +0200] [46085] [ERROR] Error handling request /
Traceback (most recent call last):
File "/home/psrok1/gunicorn-bug-20250812/.venv/lib/python3.12/site-packages/gunicorn/workers/sync.py", line 134, in handle
self.handle_request(listener, req, client, addr)
File "/home/psrok1/gunicorn-bug-20250812/.venv/lib/python3.12/site-packages/gunicorn/workers/sync.py", line 182, in handle_request
for item in respiter:
File "/home/psrok1/gunicorn-bug-20250812/.venv/lib/python3.12/site-packages/werkzeug/wsgi.py", line 256, in __next__
return self._next()
^^^^^^^^^^^^
File "/home/psrok1/gunicorn-bug-20250812/.venv/lib/python3.12/site-packages/werkzeug/wrappers/response.py", line 32, in _iter_encoded
for item in iterable:
File "/home/psrok1/gunicorn-bug-20250812/app.py", line 9, in stream_endlessly
time.sleep(1)
File "/home/psrok1/gunicorn-bug-20250812/.venv/lib/python3.12/site-packages/gunicorn/workers/base.py", line 204, in handle_abort
sys.exit(1)
SystemExit: 1
[2025-08-12 20:51:35 +0200] [46085] [INFO] Worker exiting (pid: 46085)
When regular Exceptions are handled, handle_request
checks if headers were already sent (
gunicorn/gunicorn/workers/sync.py
Line 194 in a86ea1e
if resp and resp.headers_sent: |
gunicorn/gunicorn/workers/sync.py
Line 157 in a86ea1e
self.handle_error(req, client, addr, e) |
Line 309 in a86ea1e
def write_error(sock, status_int, reason, mesg): |
I think that Exception handler in handle_request
should be extended to handle BaseException as well.