6 ms·
I think the async/await debugging experience is easier to understand. For example in the structured concurrency example, it seems like it would require a lot of
by mgraczyk 4y ago
I think the async/await debugging experience is easier to understand. For example in the structured concurrency example, it seems like it would require a lot of tooling support to get a readable stack trace for something like this (in python)
Code
import asyncio
async def right(directions):
await call_tree(directions)
async def left(directions):
await call_tree(directions)
async def call_tree(directions):
if len(directions) == 0:
raise Exception("call stack");
if directions[0]:
await left(directions[1:])
else:
await right(directions[1:])
directions = [0, 1, 0, 0, 1]
asyncio.run(call_tree(directions))
Trace
Traceback (most recent call last):
File "/Users/mgraczyk/tmp/test.py", line 19, in <module>
asyncio.run(call_tree(directions))
File "/usr/local/Cellar/python@3.9/3.9.13_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/asyncio/runners.py", line 44, in run
return loop.run_until_complete(main)
File "/usr/local/Cellar/python@3.9/3.9.13_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/asyncio/base_events.py", line 647, in run_until_complete
return future.result()
File "/Users/mgraczyk/tmp/test.py", line 16, in call_tree
await right(directions[1:])
File "/Users/mgraczyk/tmp/test.py", line 4, in right
await call_tree(directions)
File "/Users/mgraczyk/tmp/test.py", line 14, in call_tree
await left(directions[1:])
File "/Users/mgraczyk/tmp/test.py", line 7, in left
await call_tree(directions)
File "/Users/mgraczyk/tmp/test.py", line 16, in call_tree
await right(directions[1:])
File "/Users/mgraczyk/tmp/test.py", line 4, in right
await call_tree(directions)
File "/Users/mgraczyk/tmp/test.py", line 16, in call_tree
await right(directions[1:])
File "/Users/mgraczyk/tmp/test.py", line 4, in right
await call_tree(directions)
File "/Users/mgraczyk/tmp/test.py", line 14, in call_tree
await left(directions[1:])
File "/Users/mgraczyk/tmp/test.py", line 7, in left
await call_tree(directions)
File "/Users/mgraczyk/tmp/test.py", line 11, in call_tree
raise Exception("call stack");
Exception: call stack
- pron 4y agoNo, the existing tooling will give you such a stack trace already (and you don't need any `async` or `await` boilerplate, and you can even run code written and compiled 25 years ago in a virtual thread). But you do realise that async/await and threads are virtually the same abstraction. What makes you think implementing tooling for one would be harder than for the other?
- mgraczyk 4y agoHow does the tooling know to hide the call to "fork" in the scoped task example?
- pron 4y agoJDK methods can be annotated as "internal" and optionally hidden in stack-traces, but in this case it's unnecessary. The fork call takes place on the parent thread and isn't part of any stack trace when an exception in a child occurs. The regular structuring of exception stack traces takes care of the rest. Remember that Java has been multithreaded since its inception, and virtual threads don't change any of the threading model. They just make Java threads cheap. It's as if they've been there all along.
- mgraczyk 4y agoWouldn't the exception actually come from throwIfFailed? Or does it come from resultNow? How does the tool show it as corresponding to the call to findUser?
- pron 4y agothrowIfFailed throws an exception that wraps one thrown by the child as a "caused by" and lists their stack traces. Java users have had this for many years, but threads were simply costly, so they were shared among tasks. The new thing structured concurrency brings -- in addition to making some best practices easier to follow -- is that the runtime now records parent-child relationships among threads (that now make sense when threads are no longer shared). You can see these relationships and the tree hierarchy for the entire application with a new JSON thread-dump.