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
Stop |
Data
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 callsexecutor.shutdown()with the defaultwait=True(asgiref/sync.py:148), which is a synchronousThread.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_asyncit 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 withawait 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 asyncioFutureto 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’sWeakKeyDictionaryreleases 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.