4 ms·
Asyncio allows you to replace the event loop with an implementation of your own. For Temporal Python we represent workflows as a custom, durable asyncio event l
by kodablah 2y ago
Asyncio allows you to replace the event loop with an implementation of your own. For Temporal Python we represent workflows as a custom, durable asyncio event loops so things like asyncio.sleep are durable timers (i.e. code can resume on another machine, so you can sleep for weeks). Here is a post explaining how it's done: https://temporal.io/blog/durable-distributed-asyncio-event-loop https://temporal.io/blog/durable-distributed-asyncio-event-l....
The biggest problem with asyncio is how easy/common it is in Python to be able to block the asyncio thread with synchronous calls, gumming up the whole system. Python sorely needs a static analysis tool that can build a call graph to help detect if a known thread-blocking call is called directly or directly from an async def.
- guappa 2y agoGood luck with that. A simple read() might block or not, depending on what the descriptor is and how it's configured. How would you statically detect this?
- kodablah 2y agoLike many static analyzers in ambiguous situations, you have to decide whether the possibility of false positives is worth the safety. In this case I'd flag it knowing that it could be marked ignored as needed (same with open() if it was used). I have toyed with a MyPy plugin to do this, but the arbitrary state storage/cache is a bit limited to do good call graph and function coloring (metadata can be on types but not functions). And there's just not many other good _extensible_ static analysis libraries. Language like Go have static analysis libraries that let you store/cache arbitrary state/facts on all constructs.
- guappa 2y agoYou must also detect "too long loops"… which is dangerously close to solving the halting problem.
- kodablah 2y agoThis and other similar CPU-bound operations may be unreasonable to detect at analysis time. I would encourage users to run with asyncio debug mode in tests to help catch these: https://docs.python.org/3/library/asyncio-dev.html#debug-mode https://docs.python.org/3/library/asyncio-dev.html#debug-mod.... Granted that's runtime, but there are limitations on what analysis can perform, it's just meant to help catch obvious issues.
- chippiewill 2y agoTemporal is seriously cool. When I found out how it had implemented the asyncio event loop it was a real mind blown moment.
- swyx 2y agohow? is there a source to read?
- hot_gril 2y agoMaybe this is a bad idea, but... What if, instead of the current way where every call (including native stuff) is sync by default, it were the other way around? You'd quickly whitelist basic things like arithmetic and data struct access as sync, then you could maybe detect other things that should be sync if the event loop is spinning suspiciously fast.
- 999900000999 2y agoWhy not use threads ? I'm still trying to understand if Python is really meant for concurrency. Asyncio always felt like it's barely hanging on, maybe it's me, by C# has a cleaner async implementation in my opinion.
- kodablah 2y agoWe need hundreds or thousands and they need to be cheap and, in Temporal's case, the cooperative multitasking needs to be deterministic which native threads are not. Temporal's C# SDK uses .NET tasks the same way and tasks have a lot of parallels with asyncio (though they combine with threads in one static thread pool which can be confusing in itself).
- 999900000999 2y agoIs C# generally faster or more efficient here? Forgive my ignorance, but I always figured Python just doesn't scale well for this use case.
- kodablah 2y agoYes, C# is faster just because it's a faster runtime, but Python does scale reasonably well for this use case.
- 999900000999 2y agoThanks for the info!
- neonsunset 2y agoMassively so. You can spawn 1 million tasks waiting for a Task.Delay and threadpool will trivially survive the punishment. Not so much with Python, which has a lot of problems scaling to all cores and also deals with interpreter lock (I assume it is better now and no longer pure GIL style?). The advice for Python seems to be to use multi-processing and even then it is orders of magnitude of difference in CPU time spent per operation. One language compiles to codegen that is not far from what you see out of GCC or Clang, another one is purely interpreted which means significant difference in resource utilization. Although top positions do a lot of "cheating", take a look where most Python entries sit: https://www.techempower.com/benchmarks/#hw=ph&test=fortune§ion=data-r22 https://www.techempower.com/benchmarks/#hw=ph&test=fortune&s... Fastest Python entry starts at 234th place while .NET one is at 16th (and that with still somewhat heavy impl., upper bracket of Fortunes is bottle-necked by DB driver as well which is what top entries win with).
- dnadler 2y agoIn the workflow example, is ` Purchaser.purchase` supposed to be `do_purchase`?
- kodablah 2y agoFixed, thanks! (it had undergone revisions hence the function name mismatch)