archivebox.core.asgi

ASGI config for archivebox project.

It exposes the ASGI callable as a module-level variable named application.

For more information on this file, see https://docs.djangoproject.com/en/stable/howto/deployment/asgi/

Module Contents

Functions

_patch_thread_sensitive_context_shutdown

Stop ThreadSensitiveContext.__aexit__ from blocking the daphne loop.

Data

application

API

archivebox.core.asgi._patch_thread_sensitive_context_shutdown() None[source]

Stop ThreadSensitiveContext.__aexit__ from blocking the daphne loop.

Django 6.0’s ASGIHandler wraps every request in async with ThreadSensitiveContext(): (django/core/handlers/asgi.py:169). On exit asgiref calls executor.shutdown() with the default wait=True (asgiref/sync.py:148), which is a synchronous Thread.join() inside an async function — so it blocks the daphne event loop until the executor’s worker thread exits.

That’s normally fine because the request handler has already awaited every sync_to_async it submitted, so the worker is idle and dies as soon as the shutdown sentinel reaches it. The blocking turns into a problem when a client disconnects mid-request:

  • SyncToAsync.__call__ shields the executor work with await asyncio.shield(exec_coro) (asgiref/sync.py:506) so that the sync DB call doesn’t get torn down halfway through.

  • On cancellation it calls exec_coro.cancel() (line 522) which only flips the asyncio Future to cancelled — the underlying thread keeps running the SQL query.

  • Control unwinds to __aexit__ while the orphaned thread is still mid-query. shutdown(wait=True) then blocks the event loop until that orphan finishes.

Under heavy SQLite contention (the load-test scenario that surfaced this on cabbage) those orphan threads can take 30 seconds each waiting for write locks, and the daphne loop is single-threaded — so every such orphan stalls every other in-flight request, healthchecks time out, and the container goes unhealthy.

Switching to shutdown(wait=False) queues the sentinel and returns immediately; the worker thread still exits cleanly once its current task finishes, and asgiref’s WeakKeyDictionary releases the executor as soon as the request’s context is GC’d. No per-request teardown guarantee is lost — there was no caller relying on it.

archivebox.core.asgi.application[source]

‘get_asgi_application(…)’