Is it safe to ignore the return value of io.async and io.concurrent?

Can I create async tasks in an infinite loop and not care about calling cancel or await on them? Like in a socket acceptor loop for example:

const std = @import("std");

pub fn main(init: std.process.Init) !void {
    const io = init.io;
    const address = try std.Io.net.IpAddress.parse("127.0.0.1", 8080);
    var listener = try address.listen(io, .{ .reuse_address = true });
    defer listener.deinit(io);

    while (true) {
        const stream = try listener.accept(io);
        _ = io.async(handleConnection, .{ io, stream });
    }
}

fn handleConnection(io: std.Io, stream: std.Io.net.Stream) void {
    defer stream.close(io);
    // ... handle http request ...
}

Judging by the fact that io.async doesn’t take an allocator, I’m assuming that this is not leaking memory, but might this leak anything else? File descriptors?

Actually, I’ve just wrapped this in a test, and the debug allocator did detect a leak. I wonder, how one would write such a loop then. Create a std.Io.Group and call await on it periodically? But that would block the acceptor loop during the periodic cleanups…

From my understanding, if you never call await, your async function may never even get called, depending on the io implementation used.

Yes a Group is how you would track an arbitrary amount of futures.
If you don’t want to block the acceptor loop, then you should be using concurrent instead of async

The Io interface itself, doesn’t take an allocator for any(?) of its functions. But the implementation may have, and usually does, an allocator.

Regardless, futures themselves, are resources that must be awaited or canceled.

Note that since tasks run via a group can’t return values, they can be immediately cleaned up on completion, unlike other futures.

This was the case previously but I dont think it is anymore:

/// When this function returns, it is guaranteed that `function` has already
/// been called and completed, or it has successfully been assigned a unit of
/// concurrency.

It is guaranteed to be assigned a unit of concurrency, if it has not been run synchronously. So the task will run soon :tm:.

I think this:

/// Calls `function` with `args`, such that the return value of the function is
/// not guaranteed to be available until `await` is called.

Is just refering to the implementation needing to hold onto the result, since it needs a stable address for the return possition as the future does not need to be awaited in the same function it was created.

i should just look at the code, but i could imagine the following sequence of events being correct

  • async is called on f, putting it into some kind of work queue.
  • some kind of parent is canceled before f starts
  • instead of starting it, error.Canceled is raised