-
Notifications
You must be signed in to change notification settings - Fork 6
Interface struct ‣ Abort signal
The zigar.function.AbortSignal struct provides an interface to a JavaScript AbortSignal object. It gives you the ability to terminate a time-consuming asychronous task. It's always used alongside a Promise.
Like other special arguments like Allocator and
Promise, Zigar will provide an AbortSignal automatically on the
JavaScript side when it's in a function's list of arguments. This signal would be completely
non-functional though, since you wouldn't have a mean to activate it. To actually abort an async
operation, you must create your own
AbortController and pass its
signal:
const std = @import("std");
const zigar = @import("zigar");
const Promise = zigar.function.Promise(error{Aborted}!void);
pub fn start() !void {
try zigar.thread.use();
}
pub fn stop() void {
zigar.thread.end();
}
pub fn idle(promise: Promise, signal: zigar.function.AbortSignal) !void {
const thread = try std.Thread.spawn(.{}, spin, .{ promise, signal });
thread.detach();
}
pub fn spin(promise: Promise, signal: zigar.function.AbortSignal) void {
while (signal.off()) {}
promise.resolve(error.Aborted);
}import { idle, start, stop } from './abort-signal-example-1.zig';
try {
start();
const controller = new AbortController();
const { signal } = controller;
setTimeout(() => controller.abort(), 200);
await idle({ signal });
} catch (err) {
console.log(err);
} finally {
stop();
}[Error: Aborted] { number: 169 }
The Zig code above spawns a thread that doesn't do anything except spin in place until the abort
signal comes on. This happens when the timer function calls the abort controller's abort()
method.
AbortSignal contains a pointer to a i32 that's initially zero. It is set to one when the
JavaScript AbortSignal object emits an
abort event. On the Zig
side, the on() and
off() methods simply check this i32 for the
corresponding values.
One AbortSignal can cause early termination in multiple threads. The following code demonstrates
how 32 threads are told to stop performing a pointless task:
const std = @import("std");
const zigar = @import("zigar");
var gpa = std.heap.DebugAllocator(.{}).init;
const allocator = gpa.allocator();
const Promise = zigar.function.PromiseOf(thread_ns.pointless);
var work_queue: zigar.thread.WorkQueue(thread_ns) = .{};
pub fn start(promise: zigar.function.Promise(void)) !void {
try work_queue.init(.{
.allocator = allocator,
.n_jobs = 32,
});
work_queue.waitAsync(promise);
}
pub fn stop(promise: zigar.function.Promise(void)) void {
work_queue.deinitAsync(promise);
}
pub fn pointless(a: std.mem.Allocator, promise: Promise, signal: zigar.function.AbortSignal) !void {
const slice = try a.alloc(u32, 32);
const multipart_promise = try promise.partition(allocator, 32);
for (0..32) |i| {
slice[i] = 0;
try work_queue.push(thread_ns.pointless, .{ slice, i, signal }, multipart_promise);
}
}
const thread_ns = struct {
pub fn pointless(slice: []u32, index: usize, signal: zigar.function.AbortSignal) []u32 {
while (signal.off()) slice[index] += 1;
return slice;
}
};import { pointless, start, stop } from './abort-signal-example-2.zig';
try {
await start();
const signal = AbortSignal.timeout(200);
const numbers = await pointless({ signal });
console.log(numbers.valueOf());
} finally {
await stop();
}[
2662940, 2603501, 2779043, 2993669,
951147, 1908218, 910741, 997887,
1880495, 829757, 709525, 1447493,
1097731, 851886, 1767386, 843635,
691862, 1164306, 582661, 606771,
1009091, 733561, 1389341, 1020940,
1761980, 594635, 948148, 885895,
1422610, 1521989, 1441227, 1108398
]
We rely on WorkQueue for management of our threads. We use Promise.partition() to create a new promise object that would resolve the original promise after its resolve() method has been called the given number of times (32). The threads themselves just increment an element of an array continually, stopping only when the abort signal turns on. Instead of returning an error indicating an abort has occurred, here we just return the pointless results.
In theory, AbortSignal can be used in calls from Zig to JavaScript. The called function will
received a Javascript AbortSignal object through its options argument:
const zigar = @import("zigar");
pub fn call(cb: *const fn(zigar.function.AbortSignal) void ) {
var value: i32 = 1;
cb(.{ .ptr = &value });
}import { call } from './abort-signal-example-3.zig';
call(({ signal }) => console.log(signal));AbortSignal { aborted: true }
Usage scenarios for the feature are hard to imagine though. And the actual implementation is not
very efficient, relying on an interval function to check if the value of the signal's i32
pointer target has changed.