3 ms·
There is more than one way to make awaitables in asyncio -- at the core, this is about sharing a single future, for which there's a joyfully boring native stand
by lmeyerov 6y ago
There is more than one way to make awaitables in asyncio -- at the core, this is about sharing a single future, for which there's a joyfully boring native standard constructor.
For example, when working w/ immutable GPU dataframes to represent our user's datasets, we often get into variants where loading a dataset may take a bit and thus get multiple services requesting it before ETL is done. So, we want to only trigger the parser once per file and have any subsequent calls wait on the first one:
datasets = {}
async def load_once(name):
if not (name in datasets): # sync, many
fut = asyncio.create_future() # sync, once
datasets[name] = fut # sync, once
fut.set_result(await load(name)) # async, once
return await datasets[name] # async, many
And then throw in an async lru.. :)
- jaen 6y agoUnfortunately, this naive method is buggy, I have had to debug and fix this exact code in production :) The issue is with exception safety - first, this does not handle exceptions in load() properly, but that is a trivial fix. The more insidious problem is due to the fact that Python future are cancellable - and exceptions cancel futures. What this means is that if two callers call load_once() in parallel, and the first caller encounters an exception (eg. from calling something else in parallel), the load() future will be cancelled for _all_ callers (eg. the second one), and will remain in a permanently wedged state. Fixing that is, well, quite a bit more code...
- lmeyerov 6y agoYep, we see the same, good to point out! So load() needs a try/except, and except is either kill process / retry / clear / cancel, and the other loader should also expect an exn depending on that choice. All of that is app/use-case dependent. Usefully, Futures and async/await natively represent most of these. The case we find missing is around back pressure, retry, etc., but I haven't seen a good lang construct for that. We used to do a lot of Rx for that, and now a lot of HTTP headers/libs when a remote call, but it all feels messy. This is somewhat orthogonal to futures for load once tho.
- infinite8s 6y agoHave you seen trio (https://trio.readthedocs.io/en/stable/ https://trio.readthedocs.io/en/stable/) and more generally the notion of structured concurrency? (https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/ https://vorpus.org/blog/notes-on-structured-concurrency-or-g...)
- lmeyerov 6y agoWe have trio somewhere in our stack and it is on the list of to-delete, but mostly as part of continued elimination of maintenance and consistency burdens I tried skimming that article, but it comes off as a long and hard to read rant, which suggests the author needs to understand their idea better or pick an explanation/analogy that is more direct. Maybe something like the ocap reasoning for promises vs futures, or say the issues of coloring, may help...