#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.

Reply via email to