use Env.PersistentPromiseResolver

This commit is contained in:
Muki Kiboigo
2025-09-16 12:09:54 -07:00
parent 2a7a8bc2a6
commit d0621510cc
5 changed files with 97 additions and 118 deletions

View File

@@ -19,7 +19,6 @@
const std = @import("std");
const log = @import("../../log.zig");
const v8 = @import("v8");
const Env = @import("../env.zig").Env;
const Page = @import("../page.zig").Page;
@@ -46,7 +45,7 @@ pub const Interfaces = .{
pub const FetchContext = struct {
arena: std.mem.Allocator,
js_ctx: *Env.JsContext,
promise_resolver: v8.Persistent(v8.PromiseResolver),
promise_resolver: Env.PersistentPromiseResolver,
method: Http.Method,
url: []const u8,
@@ -82,13 +81,9 @@ pub const FetchContext = struct {
pub fn destructor(self: *FetchContext) void {
if (self.transfer) |_| {
const resolver = Env.PromiseResolver{
.js_context = self.js_ctx,
.resolver = self.promise_resolver.castToPromiseResolver(),
};
resolver.reject("TypeError") catch unreachable;
self.promise_resolver.reject("TypeError") catch unreachable;
self.promise_resolver.deinit();
self.transfer = null;
}
}
};
@@ -99,10 +94,7 @@ pub fn fetch(input: RequestInput, options: ?RequestInit, page: *Page) !Env.Promi
const req = try Request.constructor(input, options, page);
const resolver = Env.PromiseResolver{
.js_context = page.main_context,
.resolver = v8.PromiseResolver.init(page.main_context.v8_context),
};
const resolver = page.main_context.createPersistentPromiseResolver();
var headers = try Http.Headers.init();
@@ -125,10 +117,7 @@ pub fn fetch(input: RequestInput, options: ?RequestInit, page: *Page) !Env.Promi
fetch_ctx.* = .{
.arena = arena,
.js_ctx = page.main_context,
.promise_resolver = v8.Persistent(v8.PromiseResolver).init(
page.main_context.isolate,
resolver.resolver,
),
.promise_resolver = resolver,
.method = req.method,
.url = req.url,
};
@@ -205,24 +194,23 @@ pub fn fetch(input: RequestInput, options: ?RequestInit, page: *Page) !Env.Promi
});
const response = try self.toResponse();
const promise_resolver: Env.PromiseResolver = .{
.js_context = self.js_ctx,
.resolver = self.promise_resolver.castToPromiseResolver(),
};
try promise_resolver.resolve(response);
try self.promise_resolver.resolve(response);
}
}.doneCallback,
.error_callback = struct {
fn errorCallback(ctx: *anyopaque, err: anyerror) void {
const self: *FetchContext = @ptrCast(@alignCast(ctx));
self.transfer = null;
if (self.transfer != null) {
self.transfer = null;
log.err(.http, "error", .{
.url = self.url,
.err = err,
.source = "fetch error",
});
log.err(.http, "error", .{
.url = self.url,
.err = err,
.source = "fetch error",
});
self.promise_resolver.reject(@errorName(err)) catch unreachable;
}
}
}.errorCallback,
});

View File

@@ -19,7 +19,6 @@
const std = @import("std");
const log = @import("../../log.zig");
const v8 = @import("v8");
const Page = @import("../page.zig").Page;
const Env = @import("../env.zig").Env;
@@ -35,9 +34,9 @@ const State = union(enum) {
};
// This promise resolves when a stream is canceled.
cancel_resolver: v8.Persistent(v8.PromiseResolver),
closed_resolver: v8.Persistent(v8.PromiseResolver),
reader_resolver: ?v8.Persistent(v8.PromiseResolver) = null,
cancel_resolver: Env.PersistentPromiseResolver,
closed_resolver: Env.PersistentPromiseResolver,
reader_resolver: ?Env.PersistentPromiseResolver = null,
locked: bool = false,
state: State = .readable,
@@ -79,15 +78,8 @@ const QueueingStrategy = struct {
pub fn constructor(underlying: ?UnderlyingSource, _strategy: ?QueueingStrategy, page: *Page) !*ReadableStream {
const strategy: QueueingStrategy = _strategy orelse .{};
const cancel_resolver = v8.Persistent(v8.PromiseResolver).init(
page.main_context.isolate,
v8.PromiseResolver.init(page.main_context.v8_context),
);
const closed_resolver = v8.Persistent(v8.PromiseResolver).init(
page.main_context.isolate,
v8.PromiseResolver.init(page.main_context.v8_context),
);
const cancel_resolver = page.main_context.createPersistentPromiseResolver();
const closed_resolver = page.main_context.createPersistentPromiseResolver();
const stream = try page.arena.create(ReadableStream);
stream.* = ReadableStream{ .cancel_resolver = cancel_resolver, .closed_resolver = closed_resolver, .strategy = strategy };
@@ -131,11 +123,6 @@ pub fn _cancel(self: *ReadableStream, reason: ?[]const u8, page: *Page) !Env.Pro
return error.TypeError;
}
const resolver = Env.PromiseResolver{
.js_context = page.main_context,
.resolver = self.cancel_resolver.castToPromiseResolver(),
};
self.state = .{ .cancelled = if (reason) |r| try page.arena.dupe(u8, r) else null };
// Call cancel callback.
@@ -147,8 +134,8 @@ pub fn _cancel(self: *ReadableStream, reason: ?[]const u8, page: *Page) !Env.Pro
}
}
try resolver.resolve({});
return resolver.promise();
try self.cancel_resolver.resolve({});
return self.cancel_resolver.promise();
}
pub fn pullIf(self: *ReadableStream) !void {

View File

@@ -21,7 +21,6 @@ const log = @import("../../log.zig");
const Page = @import("../page.zig").Page;
const Env = @import("../env.zig").Env;
const v8 = @import("v8");
const ReadableStream = @import("./ReadableStream.zig");
const ReadableStreamReadResult = @import("./ReadableStream.zig").ReadableStreamReadResult;
@@ -40,23 +39,14 @@ pub fn _close(self: *ReadableStreamDefaultController, _reason: ?[]const u8, page
self.stream.state = .{ .closed = reason };
// Resolve the Reader Promise
if (self.stream.reader_resolver) |rr| {
const resolver = Env.PromiseResolver{
.js_context = page.main_context,
.resolver = rr.castToPromiseResolver(),
};
try resolver.resolve(ReadableStreamReadResult{ .value = .empty, .done = true });
if (self.stream.reader_resolver) |*rr| {
defer rr.deinit();
try rr.resolve(ReadableStreamReadResult{ .value = .empty, .done = true });
self.stream.reader_resolver = null;
}
// Resolve the Closed promise.
const closed_resolver = Env.PromiseResolver{
.js_context = page.main_context,
.resolver = self.stream.closed_resolver.castToPromiseResolver(),
};
try closed_resolver.resolve({});
try self.stream.closed_resolver.resolve({});
// close just sets as closed meaning it wont READ any more but anything in the queue is fine to read.
// to discard, must use cancel.
@@ -69,37 +59,22 @@ pub fn _enqueue(self: *ReadableStreamDefaultController, chunk: []const u8, page:
return error.TypeError;
}
if (self.stream.reader_resolver) |rr| {
const resolver = Env.PromiseResolver{
.js_context = page.main_context,
.resolver = rr.castToPromiseResolver(),
};
try resolver.resolve(ReadableStreamReadResult{ .value = .{ .data = chunk }, .done = false });
if (self.stream.reader_resolver) |*rr| {
defer rr.deinit();
try rr.resolve(ReadableStreamReadResult{ .value = .{ .data = chunk }, .done = false });
self.stream.reader_resolver = null;
// rr.setWeakFinalizer(@ptrCast(self.stream), struct {
// fn callback(info: ?*v8.c.WeakCallbackInfo) void {
// const inner_stream: *ReadableStream = @ptrCast(@alignCast(v8.c.v8__WeakCallbackInfo__GetParameter(info).?));
// inner_stream.reader_resolver = null;
// }
// }.callback, .kParameter);
}
try self.stream.queue.append(page.arena, chunk);
try self.stream.pullIf();
}
pub fn _error(self: *ReadableStreamDefaultController, err: Env.JsObject, page: *Page) !void {
pub fn _error(self: *ReadableStreamDefaultController, err: Env.JsObject) !void {
self.stream.state = .{ .errored = err };
if (self.stream.reader_resolver) |rr| {
const resolver = Env.PromiseResolver{
.js_context = page.main_context,
.resolver = rr.castToPromiseResolver(),
};
try resolver.reject(err);
if (self.stream.reader_resolver) |*rr| {
defer rr.deinit();
try rr.reject(err);
self.stream.reader_resolver = null;
}
}

View File

@@ -18,8 +18,6 @@
const std = @import("std");
const v8 = @import("v8");
const log = @import("../../log.zig");
const Env = @import("../env.zig").Env;
const Page = @import("../page.zig").Page;
@@ -34,13 +32,8 @@ pub fn constructor(stream: *ReadableStream) ReadableStreamDefaultReader {
return .{ .stream = stream };
}
pub fn get_closed(self: *const ReadableStreamDefaultReader, page: *Page) Env.Promise {
const resolver = Env.PromiseResolver{
.js_context = page.main_context,
.resolver = self.stream.closed_resolver.castToPromiseResolver(),
};
return resolver.promise();
pub fn get_closed(self: *const ReadableStreamDefaultReader) Env.Promise {
return self.stream.closed_resolver.promise();
}
pub fn _cancel(self: *ReadableStreamDefaultReader, reason: ?[]const u8, page: *Page) !Env.Promise {
@@ -50,61 +43,53 @@ pub fn _cancel(self: *ReadableStreamDefaultReader, reason: ?[]const u8, page: *P
pub fn _read(self: *const ReadableStreamDefaultReader, page: *Page) !Env.Promise {
const stream = self.stream;
const resolver = Env.PromiseResolver{
.js_context = page.main_context,
.resolver = v8.PromiseResolver.init(page.main_context.v8_context),
};
switch (stream.state) {
.readable => {
if (stream.queue.items.len > 0) {
const data = self.stream.queue.orderedRemove(0);
const resolver = page.main_context.createPromiseResolver();
try resolver.resolve(ReadableStreamReadResult{ .value = .{ .data = data }, .done = false });
try self.stream.pullIf();
return resolver.promise();
} else {
if (self.stream.reader_resolver) |rr| {
const r_resolver = Env.PromiseResolver{
.js_context = page.main_context,
.resolver = rr.castToPromiseResolver(),
};
return r_resolver.promise();
return rr.promise();
} else {
const p_resolver = v8.Persistent(v8.PromiseResolver).init(page.main_context.isolate, resolver.resolver);
self.stream.reader_resolver = p_resolver;
return resolver.promise();
const persistent_resolver = page.main_context.createPersistentPromiseResolver();
self.stream.reader_resolver = persistent_resolver;
return persistent_resolver.promise();
}
try self.stream.pullIf();
}
},
.closed => |_| {
const resolver = page.main_context.createPromiseResolver();
if (stream.queue.items.len > 0) {
const data = self.stream.queue.orderedRemove(0);
try resolver.resolve(ReadableStreamReadResult{ .value = .{ .data = data }, .done = false });
} else {
try resolver.resolve(ReadableStreamReadResult{ .value = .empty, .done = true });
}
return resolver.promise();
},
.cancelled => |_| {
const resolver = page.main_context.createPromiseResolver();
try resolver.resolve(ReadableStreamReadResult{ .value = .empty, .done = true });
return resolver.promise();
},
.errored => |err| {
const resolver = page.main_context.createPromiseResolver();
try resolver.reject(err);
return resolver.promise();
},
}
return resolver.promise();
}
pub fn _releaseLock(self: *const ReadableStreamDefaultReader, page: *Page) !void {
pub fn _releaseLock(self: *const ReadableStreamDefaultReader) !void {
self.stream.locked = false;
if (self.stream.reader_resolver) |rr| {
const resolver = Env.PromiseResolver{
.js_context = page.main_context,
.resolver = rr.castToPromiseResolver(),
};
try resolver.reject("TypeError");
try rr.reject("TypeError");
}
}

View File

@@ -1261,6 +1261,13 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
};
}
pub fn createPersistentPromiseResolver(self: *JsContext) PersistentPromiseResolver {
return .{
.js_context = self,
.resolver = v8.Persistent(v8.PromiseResolver).init(self.isolate, v8.PromiseResolver.init(self.v8_context)),
};
}
// Probing is part of trying to map a JS value to a Zig union. There's
// a lot of ambiguity in this process, in part because some JS values
// can almost always be coerced. For example, anything can be coerced
@@ -2231,6 +2238,43 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
}
};
pub const PersistentPromiseResolver = struct {
js_context: *JsContext,
resolver: v8.Persistent(v8.PromiseResolver),
pub fn deinit(self: *PersistentPromiseResolver) void {
self.resolver.deinit();
}
pub fn promise(self: PersistentPromiseResolver) Promise {
return .{
.promise = self.resolver.castToPromiseResolver().getPromise(),
};
}
pub fn resolve(self: PersistentPromiseResolver, value: anytype) !void {
const js_context = self.js_context;
const js_value = try js_context.zigValueToJs(value);
// resolver.resolve will return null if the promise isn't pending
const ok = self.resolver.castToPromiseResolver().resolve(js_context.v8_context, js_value) orelse return;
if (!ok) {
return error.FailedToResolvePromise;
}
}
pub fn reject(self: PersistentPromiseResolver, value: anytype) !void {
const js_context = self.js_context;
const js_value = try js_context.zigValueToJs(value);
// resolver.reject will return null if the promise isn't pending
const ok = self.resolver.castToPromiseResolver().reject(js_context.v8_context, js_value) orelse return;
if (!ok) {
return error.FailedToRejectPromise;
}
}
};
pub const Promise = struct {
promise: v8.Promise,
};