Add finalizer mode

When a type is finalized by V8, it's because it's fallen out of scope. When a
type is finalized by Zig, it's because the Context is being shutdown.

Those are two different environments and might require distinct cleanup logic.
Specifically, a zig-initiated finalization needs to consider that the page and
context are being shutdown. It isn't necessarily safe to execute JavaScript at
this point, and thus, not safe to execute a callback (on_error, on_abort,
ready_state_change, ...).
This commit is contained in:
Karl Seguin
2026-01-22 10:58:28 +08:00
parent d5f26f6d15
commit fc64abee8f
3 changed files with 18 additions and 11 deletions

View File

@@ -92,11 +92,11 @@ pub fn Builder(comptime T: type) type {
return entries;
}
pub fn finalizer(comptime func: *const fn (self: *T) void) Finalizer {
pub fn finalizer(comptime func: *const fn (self: *T, comptime shutdown: bool) void) Finalizer {
return .{
.from_zig = struct {
fn wrap(ptr: *anyopaque) void {
func(@ptrCast(@alignCast(ptr)));
func(@ptrCast(@alignCast(ptr)), true);
}
}.wrap,
@@ -115,7 +115,7 @@ pub fn Builder(comptime T: type) type {
if (!ctx.identity_map.contains(@intFromPtr(ptr))) {
return;
}
func(self);
func(self, false);
ctx.release(ptr);
}
}.wrap,

View File

@@ -90,9 +90,13 @@ pub fn init(page: *Page) !*XMLHttpRequest {
});
}
pub fn deinit(self: *XMLHttpRequest) void {
pub fn deinit(self: *XMLHttpRequest, comptime shutdown: bool) void {
if (self._transfer) |transfer| {
if (shutdown) {
transfer.terminate();
} else {
transfer.abort(error.Abort);
}
self._transfer = null;
}
self._page.releaseArena(self._arena);

View File

@@ -371,7 +371,7 @@ fn makeTransfer(self: *Client, req: Request) !*Transfer {
return transfer;
}
fn requestFailed(self: *Client, transfer: *Transfer, err: anyerror) void {
fn requestFailed(self: *Client, transfer: *Transfer, err: anyerror, comptime execute_callback: bool) void {
// this shouldn't happen, we'll crash in debug mode. But in release, we'll
// just noop this state.
if (comptime IS_DEBUG) {
@@ -390,7 +390,9 @@ fn requestFailed(self: *Client, transfer: *Transfer, err: anyerror) void {
});
}
if (execute_callback) {
transfer.req.error_callback(transfer.ctx, err);
}
}
// Restrictive since it'll only work if there are no inflight requests. In some
@@ -600,7 +602,7 @@ fn processMessages(self: *Client) !bool {
if (!transfer._header_done_called) {
const proceed = transfer.headerDoneCallback(easy) catch |err| {
log.err(.http, "header_done_callback", .{ .err = err });
self.requestFailed(transfer, err);
self.requestFailed(transfer, err, true);
continue;
};
if (!proceed) {
@@ -611,7 +613,7 @@ fn processMessages(self: *Client) !bool {
transfer.req.done_callback(transfer.ctx) catch |err| {
// transfer isn't valid at this point, don't use it.
log.err(.http, "done_callback", .{ .err = err });
self.requestFailed(transfer, err);
self.requestFailed(transfer, err, true);
continue;
};
@@ -622,7 +624,7 @@ fn processMessages(self: *Client) !bool {
}
processed = true;
} else |err| {
self.requestFailed(transfer, err);
self.requestFailed(transfer, err, true);
}
}
return processed;
@@ -972,7 +974,7 @@ pub const Transfer = struct {
}
pub fn abort(self: *Transfer, err: anyerror) void {
self.client.requestFailed(self, err);
self.client.requestFailed(self, err, true);
if (self._handle != null) {
self.client.endTransfer(self);
}
@@ -980,6 +982,7 @@ pub const Transfer = struct {
}
pub fn terminate(self: *Transfer) void {
self.client.requestFailed(self, error.Shutdown, false);
if (self._handle != null) {
self.client.endTransfer(self);
}