-
-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix compatibility with third-party sync-only middleware #19
Conversation
@brianglass can you see if your GCP middleware works with this branch?
|
@Archmonger, yes, that works. |
@Archmonger, I deployed this to production and it is working well. I can confirm that the warnings have gone away. |
Fairly frustrating since this proves how incredibly brittle async support is within Django. I'll work on getting this merged soon. |
Yes, it's pretty difficult to get your stack clear of synchronous middleware. I should probably look for a way to get rid of the Google middleware since it causes a context switch. it's frustrating to go through all the effort of building everything async only to need something like this in the end. |
For future reference to anyone reading this conversation, I created a wrapper around the Google logging middleware to make it async compatible. The middleware doesn't actually do anything other than assign the request object to a thread local. from asgiref.sync import iscoroutinefunction
from django.utils.decorators import sync_and_async_middleware
from google.cloud.logging_v2.handlers.middleware import RequestMiddleware
@sync_and_async_middleware
def google_logging_middleware(get_response):
if iscoroutinefunction(get_response):
async def middleware(request):
response = await get_response(request)
google_middleware = RequestMiddleware(lambda request: response)
return google_middleware(request)
else:
middleware = RequestMiddleware(get_response)
return middleware |
@brianglass I just pushed a different attempt at fixing this issue. Let me know if it works in your environment. |
AsyncToSync
@Archmonger, that again produces the following error:
If I switch to using the wrapped middleware I posted above, the error goes away. |
This reverts commit 1964aeb.
Okay - I've reverted back to the solution that initially worked. Unfortunately Django is extremely sensitive to mixed sync/async contexts, so unless some changes happen to asgiref/django our middleware will now be purely async. The downside of this change is that Django WSGI will now need to convert our middleware from async to sync. On the bright side, if we can keep things purely async it makes maintainability a lot easier!
|
Now that I've made such a todo about this, I realize that the Google logging middleware may not even be safe for use in async applications. Since it is using a thread-local to share the request object with the logger and async Django might be processing multiple requests concurrently in a single thread, it doesn't make sense to use this. I'll be looking for an alternative or simply going back to vanilla logging. |
Django does not reuse threads. It is one thread per request. |
According to the Django async Docs, "The main benefits are the ability to service hundreds of connections without using Python threads. This allows you to use slow streaming, long-polling, and other exciting response types." I take that to mean that multiple requests can be handled in a single thread. So instead of using a thread local, one should use contextvars to manage state. These docs say, "Context managers that have state should use Context Variables instead of threading.local() to prevent their state from bleeding to other code unexpectedly, when used in concurrent code." Am I missing something? |
The explanation is a bit of a sore point for me, since it drives home how async is still half-baked in Django. Upon the release of Django 3.1, the asgiref converter functions ( Enough people complained how cripplingly bad Django's async performance was, so around the release of Django ~4.0, they opted for a "middle ground" solution, where Django would create a temporary thread for each HTTP request and use that as a safe execution environment when needed whenever conversion is needed within the Django stack. However, to this day all ORM, cache, and template rendering is still performed directly on the main thread via Things are more performant if you step outside the Django stack (such as the way I haven't had time to benchmark ServeStatic, but I suspect this also means that using |
Thanks for that detailed explanation @Archmonger. |
Description
asgiref.AsyncToSync
Checklist
Please update this checklist as you complete each item:
By submitting this pull request I agree that all contributions comply with this project's open source license(s).