#32798: StreamingHttpResponse raises SynchronousOnlyOperation in ASGI server
--------------------------------+------------------------------------
Reporter: Ralph Broenink | Owner: nobody
Type: Bug | Status: new
Component: HTTP handling | Version: 3.2
Severity: Normal | Resolution:
Keywords: ASGI, async | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
--------------------------------+------------------------------------
Comment (by Michael Brown):
I suspect we should probably look at running any response generators
inside sync_to_async, unless we can detect they're an asynchronous
generator somehow and then use `async for` instead of `for`?
Even if we detect async vs sync generators at some point, I think response
generators should be called inside `sync_to_async`. Database calls in sync
generators work in WSGIHandler, so it should also work in ASGIHandler. I
can see a usecase for async response generators, but I think that's a
different issue.
As for how to change `for` to `async for`, it would be nice if
`sync_to_async` handled async generators, but according to
[https://github.com/django/asgiref/issues/142 asgiref issue #142] this
isn't the case. The pull request for that issue does something like call
`next` in `sync_to_async`, with a workaround to convert `StopIteration` to
`StopAsyncIteration`:
{{{#!python
async def _sync_to_async_iterator(iterator):
iterator = iter(iterator)
def _next():
try:
return next(iterator)
except StopIteration:
raise StopAsyncIteration
while True:
try:
yield await sync_to_async(_next)()
except StopAsyncIteration:
break
}}}
Would it be more appropriate to use a queue here? Here is an
implementation using a queue, but it's more complicated so I'm less sure
it's correct (although it does work):
{{{#!python
async def _sync_to_async_iterator(iterator):
q = asyncio.Queue(maxsize=1)
def sync_iterator():
for item in iterator:
async_to_sync(q.put)(item)
task = asyncio.create_task(sync_to_async(sync_iterator)())
try:
while not task.done():
next_item = asyncio.create_task(q.get())
done, pending = await asyncio.wait([next_item, task],
return_when=asyncio.FIRST_COMPLETED)
if next_item in done:
yield next_item.result()
elif task in done:
next_item.cancel()
break
finally:
task.cancel()
task.result()
}}}
If one of those options seems good then I can work on a patch with tests.
--
Ticket URL: <https://code.djangoproject.com/ticket/32798#comment:3>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
--
You received this message because you are subscribed to the Google Groups
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/django-updates/065.925adbd625dc7310b9eba23415358a88%40djangoproject.com.