Skip to content

Commit

Permalink
Run the app as ASGI
Browse files Browse the repository at this point in the history
  • Loading branch information
sarayourfriend committed Sep 11, 2023
1 parent f4ab93d commit 5112061
Show file tree
Hide file tree
Showing 10 changed files with 349 additions and 84 deletions.
3 changes: 1 addition & 2 deletions api/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,7 @@ EXPOSE 8000
# Wait for ES to accept connections
ENTRYPOINT ["./run.sh"]

# gunicorn configuration in gunicorn.conf.py
CMD ["gunicorn"]
CMD ["python", "run.py"]

#########
# NGINX #
Expand Down
1 change: 1 addition & 0 deletions api/Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ redlock-py = "~=1.0"
requests-oauthlib = "~=1.3"
sentry-sdk = "~=1.30"
django-split-settings = "*"
uvicorn = {extras = ["standard"], version = "*"}

[requires]
python_version = "3.11"
251 changes: 235 additions & 16 deletions api/Pipfile.lock

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions api/conf/asgi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import os

import django

from conf.asgi_handler import OpenverseASGIHandler


os.environ.setdefault("DJANGO_SETTINGS_MODULE", "conf.settings")


def get_asgi_application():
django.setup(set_prefix=False)
return OpenverseASGIHandler()


application = get_asgi_application()
75 changes: 75 additions & 0 deletions api/conf/asgi_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import asyncio
import inspect
import logging
import weakref
from collections.abc import Callable

from django.core.handlers.asgi import ASGIHandler


parent_logger = logging.getLogger(__name__)


class OpenverseASGIHandler(ASGIHandler):
"""
Extend default ASGIHandler to implement lifetime hooks.
Handlers are registered by calling `register_shutdown_handler`.
The class maintains only weak references to handler functions
and methods to prevent memory leaks. This removes the need
for explicit deregistration of handlers if, for example, their associated
objects (if a method) or contexts are garbage collected.
Asynchronous handlers are automatically supported via `async_to_sync`
and do not need special consideration at registration time.
"""

logger = parent_logger.getChild("OpenverseASGIHandler")

def __init__(self):
super().__init__()
self._on_shutdown: list[weakref.WeakMethod | weakref.ref] = []

def _clean_ref(self, ref):
self.logger.info("Cleaning up a ref")
self._on_shutdown.remove(ref)

def register_shutdown_handler(self, handler: Callable[[], None]):
"""Register an individual shutdown handler."""
if inspect.ismethod(handler):
ref = weakref.WeakMethod(handler, self._clean_ref)
else:
ref = weakref.ref(handler, self._clean_ref)

self._on_shutdown.append(ref)

async def __call__(self, scope, receive, send):
if scope["type"] == "lifespan":
while True:
message = await receive()
if message["type"] == "lifespan.startup":
await send({"type": "lifespan.startup.complete"})

elif message["type"] == "lifespan.shutdown":
await self.shutdown()
await send({"type": "lifespan.shutdown.complete"})
return

await super().__call__(scope, receive, send)

async def shutdown(self):
live_handlers = 0

for handler_ref in self._on_shutdown:
if not (handler := handler_ref()):
self.logger.debug("Reference dead, skipping handler")
continue

live_handlers += 1

if asyncio.iscoroutinefunction(handler):
await handler()
else:
handler()

self.logger.info(f"Executed {live_handlers} handler(s) before shutdown.")
2 changes: 0 additions & 2 deletions api/conf/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,6 @@
},
]

WSGI_APPLICATION = "conf.wsgi.application"

# Default primary key field type
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field

Expand Down
17 changes: 0 additions & 17 deletions api/conf/wsgi.py

This file was deleted.

46 changes: 0 additions & 46 deletions api/gunicorn.conf.py

This file was deleted.

21 changes: 21 additions & 0 deletions api/run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import os

import uvicorn


if __name__ == "__main__":
uvicorn.run(
"conf.asgi:application",
host="0.0.0.0",
port=8000,
reload=os.getenv("ENVIRONMENT") == "local",
log_level="debug",
log_config={
"version": 1,
"formatters": {
"generic": {
"format": "[%(asctime)s - %(name)s - %(lineno)3d][%(levelname)s] %(message)s", # noqa: E501
},
},
},
)
1 change: 0 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,6 @@ services:
- api/.env
stdin_open: true
tty: true
command: gunicorn --reload -w 1

ingestion_server:
profiles:
Expand Down

0 comments on commit 5112061

Please sign in to comment.