I’m trying to use cancelation from the new Io (0.16.0) to shut down background task on unix signal. However, it panics with assertion that awaited future can’t be canceled (at least if it isn’t finished and is still awaited):
const std = @import("std");
const Shutdown = struct {
var io: std.Io = undefined;
var event: std.Io.Event = .unset;
fn signalHandler(sig: std.posix.SIG) callconv(.c) void {
_ = sig;
event.set(io);
}
fn setUpSignalHandler(io1: std.Io) void {
io = io1;
const action: std.posix.Sigaction = .{
.handler = .{ .handler = Shutdown.signalHandler },
.flags = 0,
.mask = std.posix.sigemptyset(),
};
std.posix.sigaction(std.posix.SIG.INT, &action, null);
}
};
fn doWork(io: std.Io) void {
io.sleep(.fromSeconds(100), .awake) catch {};
}
fn shutdownWatcher(io: std.Io, doWorkFuture: *std.Io.Future(void)) void {
Shutdown.event.wait(io) catch {};
std.log.debug("shutdownWatcher activated", .{});
doWorkFuture.cancel(io);
}
pub fn main(init: std.process.Init) !void {
const io = init.io;
var doWorkFuture = io.async(doWork, .{io});
defer doWorkFuture.cancel(io);
var shutdownWatcherFuture = io.async(shutdownWatcher, .{ io, &doWorkFuture });
defer shutdownWatcherFuture.cancel(io);
Shutdown.setUpSignalHandler(io);
std.log.debug("awaiting doWorkFuture", .{});
doWorkFuture.await(io);
}
$ zig run example.zig
debug: awaiting doWorkFuture
^Cdebug: shutdownWatcher activated
thread 304813 panic: reached unreachable code
/usr/lib/zig/std/Io/Threaded.zig:2507:29: 0x11a37df in cancel (std.zig)
.pending_awaited => unreachable, // `await` raced with `await`
^
/usr/lib/zig/std/Io.zig:1193:29: 0x11d8208 in cancel (std.zig)
io.vtable.cancel(io.userdata, any_future, @ptrCast(&f.result), .of(Result));
^
/tmp/example.zig:30:24: 0x11d92f3 in shutdownWatcher (example.zig)
doWorkFuture.cancel(io);
^
A variant of the same code sample without signal handling to be sure that the problem is not with signal handling
const std = @import("std");
const Shutdown = struct {
var io: std.Io = undefined;
var event: std.Io.Event = .unset;
};
fn doWork(io: std.Io) void {
io.sleep(.fromSeconds(100), .awake) catch {};
}
fn shutdownWatcher(io: std.Io, doWorkFuture: *std.Io.Future(void)) void {
Shutdown.event.wait(io) catch {};
std.log.debug("shutdownWatcher activated", .{});
doWorkFuture.cancel(io);
}
fn timedCancel(io: std.Io) void {
io.sleep(.fromSeconds(3), .awake) catch {};
Shutdown.event.set(io);
}
pub fn main(init: std.process.Init) !void {
const io = init.io;
var doWorkFuture = io.async(doWork, .{io});
defer doWorkFuture.cancel(io);
var shutdownWatcherFuture = io.async(shutdownWatcher, .{ io, &doWorkFuture });
defer shutdownWatcherFuture.cancel(io);
var timedCancelFuture = io.async(timedCancel, .{io});
defer timedCancelFuture.cancel(io);
std.log.debug("awaiting doWorkFuture", .{});
doWorkFuture.await(io);
}
The same happens with Group as well:
/usr/lib/zig/std/Io/Threaded.zig:2365:11: 0x114ffe0 in groupCancel (other_example)
assert(!pre_cancel_status.have_awaiter);
^
Is it the universal rule that awaitable can’t be canceled while being awaited, or some other structure of threads/contexts allow it?
Is it possible to have main thread to wait for some thread that have infinite loop (such as accept on a socket) and to cancel that infinite loop with cancel on that thread’s Future/Group/etc, or I’m trying to misuse await and cancel for that? Any suggestions of the right way to do this?