A Design Space Exploration of Async/Await
Many programming languages now provide async/await keywords for expressing concurrency, with a fairly consistent design rationale: to make concurrent programs look more like straight-line code, as seen in Python, Rust, or Swift. The post describes this paradigm as 'straight-line asynchrony,' contrasting it with event loops or callbacks. Language designs for straight-line asynchrony have been brewing for over 15 years, and this project set out to understand how similar or different async/await is between languages. The short answer is that they are a lot more different than expected, which the authors explain in the paper 'A Design Space Exploration of Async/Await.'
To demonstrate the divergence, the excerpt presents a small pseudocode program: one async function write_to_log prints 'A', awaits sleep(2), and prints 'B'; another async function fire_and_forget spawns write_to_log as a background task and returns without awaiting it; and main awaits fire_and_forget, awaits sleep(1), and prints 'C'. The question is what the program prints. There is no single right answer because the result depends on the language, and the reader was probably right for some language. Across seven modern async runtimes, the program actually produces four different answers, despite its entire job being to write a log line in the background. The paper further shows that across those seven runtimes, no two produce the same output for three variations of this simple program.
The disagreement comes down to what are often called 'hot' versus 'cold' async function calls. A hot start returns a task that is immediately running in the runtime, while a cold start returns an inert object that does nothing until it is awaited. Hot versus cold functions is treated as an async design dimension: a decision that affects the observable semantics of program execution, as opposed to a matter of pure performance. This particular dimension is named 'Eagerness,' and the paper identifies nine such dimensions from modern implementations of straight-line asynchrony. These are grouped into three categories that roughly correspond to the lifetime of a task: Start of Life, End of Life, and Cancellation.
Under Start of Life, the excerpt lists Eagerness, which concerns how to evaluate an async function application: Lazy evaluates to a coroutine without executing further, while Eager evaluates in the current thread and schedules as a task on await. It also lists Suspension, concerning guarantees on whether await points suspend: Static means await points are guaranteed to suspend, while Dynamic means there are no guarantees on awaiting tasks. Under End of Life, it begins to describe Extent, the default interval of time during which a task may exist, with 'Indefinite' referring to tasks that by default may exist until the end of t... The excerpt cuts off mid-sentence at that point.