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