Async DNS Resolution
16 points by matklad
16 points by matklad
Some thoughts, other than "yes, please":
No heap allocation.
This is a fun one. In general, when you do concurrency, you have several stack frames active at the same time, and you need to store them somewhere. Reading the implementation of threaded IO, it seems that there's an optional heap allocation: we try to heap-allocate space for concurrent closure, but, if that fails, we fallback to synchronous execution on the parent's stack. That's because fn async(...) Future(T) doesn't guarantee a separate control thread of execution. There's fn concurrent(...) error{OOM}!Future(T) which does allocate. In TigerBeetle, the way we deal with this pattern is that the caller always passes in the storage for "stack frames".
it reads like standard, idiomatic Zig code.
I am not entirely agreeing here:
var connect_many = io.async(connectMany, .{ host_name, io, port, &connect_many_queue, options });
Looks a bit off to me. This is essentially a closure-passing API, and Zig doesn't promote closures as a universal control flow primitive, unlike, e.g., Rust. In this particular example this look actually fine, because this is interesting concurrent code with non-trivial logic. But for boringly concurrent code, where you just don't necessary want serialize foo and bar, the difference between
foo(x);
bar(y, z);
and
var foo_future = io.async(foo, .{x});
var bar_future = io.async(foo, .{y, z});
_ = foo_future.await(io);
_ = bar_future.await(io);
feels rather dramatic!
I would compare it vs starting two threads rather than two sequential calls. async is inherently more complex than sync, and if we compare what you had to do before to get some form of async vs now, IMO we get a huge bang for our buck (especially once you factor in cancellation).
I'd say it's a question of degree, because, pushed further, this argument proves too much:
Loops are inherently more complex than straight-line code, therefore
loop(
conditionFn, .{ conditon_fn_args, ... },
bodyFn, .{ body_fn_args, ... },
);
is an ok way to write a while loop.
Doesn't sound right! With concurrency, my gut feeling is that expressing just the absence of ordering shouldn't be punished syntactically. Spawning threads doesn't just dispenses with sequencing, it actually starts a new thread of execution! It's concurrent, not async, and I think that is fine to be salty.
Also worth mentioning that Zig has explicit async/implicit await: foo(x); bar(y, z); looks the same in sync and async code, which I think is great, and I want a bit more of that :0) But maybe I am wrong!