Closed Hawk777 closed 7 months ago
1 is fine, 2 is not as it is outside of the return
of the main application callable so the spec has those covered.
3 and 4 won't work as the other thread won't have the same event loop as event loops are basically thread-bound, and I don't believe you can cross things over between event loops like that.
5 is perfectly fine (calling send()
a lot is kind of encouraged by the spec). 6 is legal in the same way 1 is, but doesn't seem very useful.
All the situations you've mentioned are thus basically covered already; I don't see a lot of value in expanding on this much further as the only thing that's really questionable here is 4, and if you've got to those lengths then you should know perfectly well not to do that!
I can think of a lot of weird things an application could theoretically do with the
send
andreceive
callables:asyncio.create_task
some other coroutine and toss them over there. The new task sends and receives things, and the original taskawait
s the new task.asyncio.create_task
some other coroutine and toss them over there. The new task sends and receives things, while the original task immediately terminates.send
andreceive
. It presumably can’tawait
the returned awaitables, because it doesn’t have an event loop, so it just sends the awaitables back to the application to be awaited.asyncio
event loop and tries to call them and thenawait
their return values there.receive
(orsend
, or both!) 17 times, pass all the return values toasyncio.gather
, and expect it to do something sensible.await
them.Which of these are actually legal in the ASGI spec?
Intuitively, it seems like (1) should be legal, and the Extra Coroutines text “applications should ensure that all coroutines launched as part of running an application are terminated either before or at the same time as the application’s coroutine.” somewhat suggests that, as long as applications do that, the extra tasks will work OK, but I’m not certain whether it’s fully explicitly called out.
I’m pretty sure (2) is not legal, given the same text from the Extra Coroutines section, and the subsequent “Any coroutines that continue to run outside of this window have no guarantees about their lifetime and may be killed at any time.”
(3) doesn’t seem especially useful, but it does seem like something that might or might not work depending on the ASGI server implementation—if the callables are just
async def
s, it’ll be fine because they just stash their parameters and immediately return without any side effects, and the only thing you can do with the returned awaitable is send it back to the event loop where the side effects actually happen. On the other hand, the spec doesn’t actually say that they must beasync def
s; they could be ordinarydef
s that do other things and then return any awaitable (a call to anasync def
, a manually createdFuture
, or whatever), in which case the initial side effects would happen on the wrong thread, presumably making this pattern not work. But I don’t see any text explicitly prohibiting it.(4) is completely insane, but also AFAICT isn’t actually prohibited.
(5) seems potentially reasonable, and also seems like something that, absent any explicit specifications, could very well work on some servers but not others—a server that does its I/O in a separate task and uses queues to ship events (in their ASGI dictionary form) to and from the application will work totally fine if you do this, while a server that does its I/O directly in the
send
andreceive
callables will probably fall over badly when the incoming bytes get split up among the differentreceive
tasks, particularly if you’re still in the middle of receiving headers.(6) also seems potentially reasonable; I could imagine someone making something like a WebSocket-based chat server where each task calls and
await
s itsreceive
callable to get a chat message, and then forwards that chat message to the other users in the room by simply directly calling andawait
ing thesend
callbacks belonging to all the other connections (and perhapsasyncio.gather
ing them)—crude, and probably lacking some robustness in terms of flow control, but a potentially workable starting point, and not entirely unreasonable since WebSockets have message framing to separate messages coming from different sources. Again, however, I can imagine this going wrong for certain server implementations for the same reason that (5) could.Is it worth writing some text about what exactly an application is and is not allowed to do with the callables?