#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 django-updates+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/065.925adbd625dc7310b9eba23415358a88%40djangoproject.com.

Reply via email to