TIL: Gunicorn's gthread Worker Timeout Doesn't Work as Expected
When using Gunicorn to run our Django application in production, I had a simple assumption: setting a timeout value (or using the default 30-second timeout) would make sure that any request taking longer than that would be stopped. This is important for keeping the system stable and preventing resource problems.
The Unexpected Discovery
While looking into issues in our system, I created a simple Django server similar to our application to test how it works.
What I found was surprising: when using Gunicorn with the gthread
worker class and testing a delayed response, the worker didn't stop after the expected 30-second timeout. Instead, it kept waiting for the response.
Note: This behavior may change in future versions of Gunicorn. If you're reading this in the future, check the latest documentation and test the behavior in your system.
Digging Deeper
After some research, I found an existing issue describing similar behavior. The key point: Gunicorn's timeout setting doesn't work as described in the docs when using the gthread
worker class.
Reproducing the Issue
To show this behavior, I created a simple test:
Dockerfile
FROM python:3.13-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8080
CMD ["gunicorn", "--bind", "0.0.0.0:8080", "project.wsgi:application", "-k", "gthread", "-t", "30"]
A Simple View That Sleeps
def long_sleep(request) -> HttpResponseBase:
logger.info(f"Going to sleep!")
time.sleep(30)
logger.info(f"30 seconds passed! Am I still alive?")
time.sleep(30)
logger.info(f"Isn't my timeout suposed to kill me? Weird...")
return JsonResponse({"message": "ok"})
Server Logs
server-1 | [2025-03-06 14:26:06 +0000] [1] [INFO] Starting gunicorn 23.0.0
server-1 | [2025-03-06 14:26:06 +0000] [1] [INFO] Listening at: http://0.0.0.0:8080 (1)
server-1 | [2025-03-06 14:26:06 +0000] [1] [INFO] Using worker: gthread
server-1 | [2025-03-06 14:26:06 +0000] [7] [INFO] Booting worker with pid: 7
server-1 | INFO 2025-03-06 14:26:10,175: Going to sleep!
server-1 | INFO 2025-03-06 14:26:40,176: 30 seconds passed! Am I still alive?
server-1 | INFO 2025-03-06 14:27:10,176: Isn't my timeout suposed to kill me? Weird...
The worker simply kept running without any timeout, which goes against what the Gunicorn's documentation says:
Workers silent for more than this many seconds are killed and restarted.
Default: 30
Conclusion
If you're using Gunicorn with the gthread
worker in production, I strongly suggest testing your timeout behavior to make sure it works as you expect.