Skip to content

Commit

Permalink
Add workaround for Django 3 memory leak under gevent
Browse files Browse the repository at this point in the history
We've been suffering from a problematic memory leak since upgrading to Django 3 which is believed to be caused by django/asgiref#144.

The memory leak is causing Gunicorn worker processes to exhaust available memory and crash and restart.

This adds a workaround which is inactive by default. This is so it can be we can verify that it fixes the problem in e.g. the dev environment.
  • Loading branch information
reupen committed Mar 12, 2020
1 parent 6f01571 commit f222503
Show file tree
Hide file tree
Showing 3 changed files with 19 additions and 3 deletions.
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -302,6 +302,7 @@ Data Hub API can run on any Heroku-style platform. Configuration is performed vi
| `GUNICORN_ACCESS_LOG_FORMAT` | No | |
| `GUNICORN_ENABLE_ASYNC_PSYCOPG2` | No | Whether to enable asynchronous psycopg2 when the worker class is 'gevent' (default=True). |
| `GUNICORN_ENABLE_STATSD` | No | Whether to enable Gunicorn StatD instrumentation (default=False). |
| `GUNICORN_PATCH_ASGIREF` | No | Whether to enable enable a workaround for https://github.com/django/asgiref/issues/144 when the worker class is 'gevent' (default=False). |
| `GUNICORN_WORKER_CLASS` | No | [Type of Gunicorn worker.](http://docs.gunicorn.org/en/stable/settings.html#worker-class) Uses async workers via gevent by default. |
| `GUNICORN_WORKER_CONNECTIONS` | No | Maximum no. of connections for async workers (default=10). |
| `INTERACTION_ADMIN_CSV_IMPORT_MAX_SIZE` | No | Maximum file size in bytes for interaction admin CSV uploads (default=2MB). |
Expand Down
1 change: 1 addition & 0 deletions changelog/django-3-gevent-workaround.internal.md
@@ -0,0 +1 @@
A workaround was added for a memory leak when using Django 3 with gevent. This is inactive by default.
20 changes: 17 additions & 3 deletions config/gunicorn.py
Expand Up @@ -41,6 +41,9 @@
_enable_async_psycopg2 = (
os.environ.get('GUNICORN_ENABLE_ASYNC_PSYCOPG2', 'true').lower() in ('true', '1')
)
_patch_asgiref = (
os.environ.get('GUNICORN_PATCH_ASGIREF', 'false').lower() in ('true', '1')
)


def post_fork(server, worker):
Expand All @@ -49,6 +52,17 @@ def post_fork(server, worker):
Enables async processing in Psycopg2 if GUNICORN_ENABLE_ASYNC_PSYCOPG2 is set.
"""
if worker_class == 'gevent' and _enable_async_psycopg2:
patch_psycopg()
worker.log.info('Enabled async Psycopg2')
if worker_class == 'gevent':
if _enable_async_psycopg2:
patch_psycopg()
worker.log.info('Enabled async Psycopg2')

# Temporary workaround for https://github.com/django/asgiref/issues/144.
# Essentially reverts part of
# https://github.com/django/django/commit/a415ce70bef6d91036b00dd2c8544aed7aeeaaed.
if _patch_asgiref:
import asgiref.local
import threading

asgiref.local.Local = lambda **kwargs: threading.local()
worker.log.info('Patched asgiref.local.Local')

0 comments on commit f222503

Please sign in to comment.