"Timeouts and cancellation for humans"

I just read this interesting discussion of cancelation using “cancelation tokens”: Timeouts and cancellation for humans — njs blog

I don’t think it’s as applicable to Zig, since supposedly any timeout code in Zig would be based on a task that had one job, mainly to return error.Canceled after a fixed period of time, but it’s still an interesting approach.

4 Likes

Zio, which was very much influenced by Trio, has a similar concept of cancel scopes, but obviously it uses defer instead of context managers. It’s an extremely powerful concept, where you need to enforce timeouts to larger chunks of code. In std.Io, you can kind of emulate it with two concurrent tasks, one running your code, one futexWait-ing on a variable that controls the timeout and cancelling the task if it expires.

var timeout: zio.AutoCancel = .init;
defer timeout.clear();

timeout.set(.fromSeconds(60));

handleRequest(...) catch |err| switch (err) {
    error.Canceled => |e| return if (timeout.check(e)) error.RequestTimeout else e,
    else => |e| return e,
};

You can read more about it here: Timeouts in zio | Lukáš Lalinský

2 Likes

I have implemented timeouting recently. I went with approach of one task awaitTimeouting on Io.Event per connection for which one timeout at a time works fine. But I am not completely happy with that because it takes additional fiber per connection, so I think I will be moving to timer wheel per OS thread which should reduce overhead alot.

But later I also want much shorter timeout on tcp read/write that wont be cancelling the whole connection so that I can demote buffer sizes on inactive connections. This is currently an issue because the read/write cannot be cancelled individually without wrapping it in its own task which would again introduce alot of overhead. But I think zig 0.17 and netRead/netWrite becoming an operation may help me solve the issue efficiently, even though it will be alot more complex.

Yes, that’s correct. You can use both with operateTimeout in Zig 0.17, which gives you the short per op timeouts. There is another open PR to integrate this into reader/writer

1 Like

This has another set of complications. You can only wait on task from one thread, and there is no way to cancel task without waiting for it to finish. That also means if you have one task that refuses to cancel (the error being swallowed, for example), then the entire timer thread is blocked. The only way to solve it is unfortunately another group.concurent per each cancel.

2 Likes

will Io.reader have timeouts or just the implementations? maybe something like io.concurrent which accepts an io param and can return the error TimeOutUnavailable