Async DNS Resolution

16 points by matklad


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!