Open HMaker opened 1 year ago
@andrewgodwin any thoughts on this? You authored the DEP about async support for Django. Is my adhoc solution for async transactions safe?
Related to https://code.djangoproject.com/ticket/33882
By the way, this custom SyncToAsync
with conditional thread sensitive mode is compatible with async HTTP views by wrapping the calls with ThreadSensitiveContext
:
from asgiref.sync import ThreadSensitiveContext
# DatabaseSyncToAsync is defined as above
# MyModel is any subclass of models.Model
async def my_view(request):
async with ThreadSensitiveContext():
await DatabaseSyncToAsync(MyModel.save)() # this is safe on a HTTP request context
But I am not sure if an explicit context manager is really needed here, I guess the ASGI handler always create one when dispatching the request to middlewares and the view. Am I right?
EDIT: yes it's right, see https://github.com/django/django/blob/1250483ebf73f7a82ff820b94092c63ce4238264/django/core/handlers/asgi.py#L148-L161
thanks a lot for overtaking the issue.
I worked around the issue by using status variables (also I couldn't wrap every access into a transaction), so I cannot provide you with more information.
@devkral what is a status variable?
I split the transaction in locking phase, transfer (reads the foreign url), unlocking phase. THe locking is done via a status variable
I split the transaction in locking phase, transfer (reads the foreign url), unlocking phase. THe locking is done via a status variable
You wrapped the transaction.atomic()
context manager with sync_to_async()
? By default sync_to_async()
runs all code in a single thread (thread_sensitive=True
). Django devs assume you are always on a HTTP request context where you have a dedicated thread for every request, here it makes sense running all sync code on an unique thread, there is still concurrency between requests. This also brings thread-safety as the default.
When using Django ORM outside of HTTP request context, like in channels background workers, sync_to_async()
default behavior is a huge bottleneck, all DB queries are serial, there is no concurrency at all. My code removes this bottleneck but still maintains compatibility with Django code running on HTTP request context (i.e. thread-safety is honored).
No, it is not a real transaction. It is the standard way to mark an object as in use (or locked). And change the status back after the update is complete. This is a little bit off-topic.
Channels provides a decorator
DatabaseSyncToAsync
to defer database queries to a background threadpool. That decorator can be used to run multiple, independent queries. Transactions can also be done given all queries are made from the same decorated function.I use Django ORM outside of HTTP context within channels and other asyncio applications. I made
DatabaseSyncToAsync
not run in thread sensitive mode to allow queries to run in parallel. To make the async API easier to use I partially replicated the sync API of django ORM, it works good for independent queries.I needed to support asynchronous transactions. The easier solution was deferring the full
transaction.atomic()
block to aDatabaseSyncToAsync
-decorated function, then make all queries from there. But this breaks the async API, I would like to enter the atomic block from the async code and use the same async API to make transaction queries.I found the thread sensitive mode together with
ThreadSensitiveContext
could be used to resolve all queries in the same thread that started the transaction, but it required a customization to bothDatabaseSyncToAsync
and the base classSyncToAsync
.