Interface struct ‣ Abort signal ᴾᴴᴾ - chung-leong/zigar GitHub Wiki
JavaScript | PHP
The zigar.function.AbortSignal struct provides an interface to a
an AbortSignal object. It serves the same purpose as its
JavaScript counterpart--
allowing early termination of a time-consuming asychronous task. It's always used
alongside a Promise.
PHP-to-Zig function calls
Like other special arguments like Allocator and
Promise, Zigar will provide an AbortSignal automatically on the
PHP 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 provide your own signal object:
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);
}
<?php
require __DIR__ . '/vendor/autoload.php';
use Revolt\EventLoop;
ini_set('zigar.event_loop', 'revolt');
EventLoop::defer(function() {
$m = zigar_use(__DIR__ . '/abort-signal-example-1.zig');
try {
$m->start();
$signal = new AbortSignal();
EventLoop::delay(0.2, function() use($signal) {
$signal->abort();
});
$m->idle(signal: $signal);
} catch (Exception $e) {
echo $e->getMessage(), "\n";
} finally {
$m->stop();
}
});
EventLoop::run();
ZigException Object
(
[error] => aborted
)
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 function passed to EventLoop::delay() calls the abort
signal's abort() method.
AbortSignal contains a pointer to a i32 that's initially zero. It is set to one when the
abort() is called on PHP side. 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 {
// allowing the value to wrap around
while (signal.off()) slice[index] +%= 1;
return slice;
}
};
<?php
require __DIR__ . '/vendor/autoload.php';
use Revolt\EventLoop;
ini_set('zigar.event_loop', 'revolt');
EventLoop::defer(function() {
$m = zigar_use(__DIR__ . '/abort-signal-example-2.zig');
$m->start();
try {
$signal = new AbortSignal;
EventLoop::delay(0.2, function() use($signal) {
$signal->abort();
});
$numbers = $m->pointless(signal: $signal);
print_r($numbers);
} finally {
$m->stop();
}
});
EventLoop::run();
[]u32 Object
(
[0] => 2612968
[1] => 2133449
[2] => 3935198
[3] => 1426306
[4] => 2049790
[5] => 2185686
[6] => 2344400
[7] => 2432831
[8] => 1600109
[9] => 1029532
[10] => 1777823
[11] => 974398
[12] => 1005438
[13] => 894564
[14] => 1456208
[15] => 645046
[16] => 627468
[17] => 883586
[18] => 2859776
[19] => 1055861
[20] => 2940929
[21] => 2189870
[22] => 769844
[23] => 2410026
[24] => 1035624
[25] => 1349586
[26] => 995647
[27] => 1163327
[28] => 989173
[29] => 755891
[30] => 1133803
[31] => 1208647
)
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, we just return the pointless results.
Zig-to-PHP function calls
In theory, AbortSignal can be used in calls from Zig to PHP. The called function will
received a PHP AbortSignal object as a named argument:
const zigar = @import("zigar");
pub fn call(cb: *const fn(zigar.function.AbortSignal) void ) {
var value: i32 = 1;
cb(.{ .ptr = &value });
}
<?php
$m = zigar_use(__DIR__ . '/abort-signal-example-3.zig');
$m->call(function($signal) {
print_r($signal);
echo "on() => ", $signal->on(), "\n";
echo "off() => ", $signal->off(), "\n";
});
AbortSignal Object
(
[ptr] => *const i32 Object
(
)
)
on() => 1
off() =>