👶event loop

This commit is contained in:
Jarred Sumner
2021-12-17 02:44:35 -08:00
parent 8dcaaa9e09
commit 42c7d887d3
18 changed files with 405 additions and 116 deletions

View File

@@ -174,7 +174,7 @@ pub extern "c" fn JSObjectMakeArray(ctx: JSContextRef, argumentCount: usize, arg
pub extern "c" fn JSObjectMakeDate(ctx: JSContextRef, argumentCount: usize, arguments: [*c]const JSValueRef, exception: ExceptionRef) JSObjectRef;
pub extern "c" fn JSObjectMakeError(ctx: JSContextRef, argumentCount: usize, arguments: [*c]const JSValueRef, exception: ExceptionRef) JSObjectRef;
pub extern "c" fn JSObjectMakeRegExp(ctx: JSContextRef, argumentCount: usize, arguments: [*c]const JSValueRef, exception: ExceptionRef) JSObjectRef;
pub extern "c" fn JSObjectMakeDeferredPromise(ctx: JSContextRef, resolve: [*c]JSObjectRef, reject: [*c]JSObjectRef, exception: ExceptionRef) JSObjectRef;
pub extern "c" fn JSObjectMakeDeferredPromise(ctx: JSContextRef, resolve: ?*JSObjectRef, reject: ?*JSObjectRef, exception: ExceptionRef) JSObjectRef;
pub extern "c" fn JSObjectMakeFunction(ctx: JSContextRef, name: JSStringRef, parameterCount: c_uint, parameterNames: [*c]const JSStringRef, body: JSStringRef, sourceURL: JSStringRef, startingLineNumber: c_int, exception: ExceptionRef) JSObjectRef;
pub extern "c" fn JSObjectGetPrototype(ctx: JSContextRef, object: JSObjectRef) JSValueRef;
pub extern "c" fn JSObjectSetPrototype(ctx: JSContextRef, object: JSObjectRef, value: JSValueRef) void;

View File

@@ -31,7 +31,9 @@ pub fn importRoute(
exception: js.ExceptionRef,
) js.JSObjectRef {
const prom = JSModuleLoader.loadAndEvaluateModule(VirtualMachine.vm.global, ZigString.init(this.route.file_path));
VirtualMachine.vm.global.vm().drainMicrotasks();
VirtualMachine.vm.tick();
return prom.result(VirtualMachine.vm.global.vm()).asRef();
}

View File

@@ -1552,6 +1552,7 @@ pub fn castObj(obj: js.JSObjectRef, comptime Type: type) *Type {
const JSNode = @import("../../js_ast.zig").Macro.JSNode;
const LazyPropertiesObject = @import("../../js_ast.zig").Macro.LazyPropertiesObject;
const ModuleNamespace = @import("../../js_ast.zig").Macro.ModuleNamespace;
const FetchTaskletContext = Fetch.FetchTasklet.FetchTaskletContext;
pub const JSPrivateDataPtr = TaggedPointerUnion(.{
ResolveError,
BuildError,
@@ -1564,6 +1565,7 @@ pub const JSPrivateDataPtr = TaggedPointerUnion(.{
JSNode,
LazyPropertiesObject,
ModuleNamespace,
FetchTaskletContext,
});
pub inline fn GetJSPrivateData(comptime Type: type, ref: js.JSObjectRef) ?*Type {

View File

@@ -5,7 +5,7 @@
#include <JavaScriptCore/AggregateError.h>
#include <JavaScriptCore/BytecodeIndex.h>
#include <JavaScriptCore/CallFrameInlines.h>
#include <JavaScriptCore/CatchScope.h>
#include <JavaScriptCore/ClassInfo.h>
#include <JavaScriptCore/CodeBlock.h>
#include <JavaScriptCore/CodeCache.h>
@@ -27,6 +27,7 @@
#include <JavaScriptCore/JSCast.h>
#include <JavaScriptCore/JSClassRef.h>
// #include <JavaScriptCore/JSContextInternal.h>
#include <JavaScriptCore/CatchScope.h>
#include <JavaScriptCore/JSInternalPromise.h>
#include <JavaScriptCore/JSLock.h>
#include <JavaScriptCore/JSMap.h>
@@ -50,6 +51,7 @@
#include <JavaScriptCore/VM.h>
#include <JavaScriptCore/VMEntryScope.h>
#include <JavaScriptCore/WasmFaultSignalHandler.h>
#include <wtf/Gigacage.h>
#include <wtf/StdLibExtras.h>
#include <wtf/URL.h>
#include <wtf/text/ExternalStringImpl.h>
@@ -58,8 +60,6 @@
#include <wtf/text/StringView.h>
#include <wtf/text/WTFString.h>
#include <wtf/Gigacage.h>
#include <cstdlib>
#include <exception>
#include <iostream>
@@ -130,7 +130,7 @@ const JSC::GlobalObjectMethodTable GlobalObject::s_globalObjectMethodTable = {
&supportsRichSourceInfo,
&shouldInterruptScript,
&javaScriptRuntimeFlags,
nullptr, // queueTaskToEventLoop
&queueMicrotaskToEventLoop, // queueTaskToEventLoop
nullptr, // &shouldInterruptScriptBeforeTimeout,
&moduleLoaderImportModule, // moduleLoaderImportModule
&moduleLoaderResolve, // moduleLoaderResolve
@@ -454,4 +454,11 @@ JSC::JSValue GlobalObject::moduleLoaderEvaluate(JSGlobalObject *globalObject,
return result;
}
void GlobalObject::queueMicrotaskToEventLoop(JSC::JSGlobalObject &global,
Ref<JSC::Microtask> &&task) {
Zig__GlobalObject__queueMicrotaskToEventLoop(
&global, &JSMicrotaskCallback::create(global, WTFMove(task)).leakRef());
}
} // namespace Zig

View File

@@ -10,10 +10,10 @@ class Identifier;
} // namespace JSC
#include "ZigConsoleClient.h"
#include <JavaScriptCore/CatchScope.h>
#include <JavaScriptCore/JSGlobalObject.h>
#include <JavaScriptCore/JSTypeInfo.h>
#include <JavaScriptCore/Structure.h>
namespace Zig {
class GlobalObject : public JSC::JSGlobalObject {
@@ -45,6 +45,7 @@ class GlobalObject : public JSC::JSGlobalObject {
static void reportUncaughtExceptionAtEventLoop(JSGlobalObject *, JSC::Exception *);
static void queueMicrotaskToEventLoop(JSC::JSGlobalObject &global, Ref<JSC::Microtask> &&task);
static JSC::JSInternalPromise *moduleLoaderImportModule(JSGlobalObject *, JSC::JSModuleLoader *,
JSC::JSString *moduleNameValue,
JSC::JSValue parameters,
@@ -69,4 +70,29 @@ class GlobalObject : public JSC::JSGlobalObject {
: JSC::JSGlobalObject(vm, structure, &s_globalObjectMethodTable) {}
};
class JSMicrotaskCallback : public RefCounted<JSMicrotaskCallback> {
public:
static Ref<JSMicrotaskCallback> create(JSC::JSGlobalObject &globalObject,
Ref<JSC::Microtask> &&task) {
return adoptRef(*new JSMicrotaskCallback(globalObject, WTFMove(task)));
}
void call() {
auto protectedThis{makeRef(*this)};
JSC::VM &vm = m_globalObject->vm();
JSC::JSLockHolder lock(vm);
auto scope = DECLARE_CATCH_SCOPE(vm);
auto task = &m_task.get();
task->run(m_globalObject.get());
scope.assertNoExceptionExceptTermination();
}
private:
JSMicrotaskCallback(JSC::JSGlobalObject &globalObject, Ref<JSC::Microtask> &&task)
: m_globalObject{globalObject.vm(), &globalObject}, m_task{WTFMove(task)} {}
JSC::Strong<JSC::JSGlobalObject> m_globalObject;
Ref<JSC::Microtask> m_task;
};
} // namespace Zig

View File

@@ -314,6 +314,12 @@ pub fn NewGlobalObject(comptime Type: type) type {
return JSValue.jsUndefined();
}
pub fn queueMicrotaskToEventLoop(global: *JSGlobalObject, microtask: *Microtask) callconv(.C) void {
if (comptime @hasDecl(Type, "queueMicrotaskToEventLoop")) {
@call(.{ .modifier = .always_inline }, Type.queueMicrotaskToEventLoop, .{ global, microtask });
}
}
pub fn onCrash() callconv(.C) void {
if (comptime @hasDecl(Type, "onCrash")) {
return @call(.{ .modifier = .always_inline }, Type.onCrash, .{});
@@ -1504,6 +1510,20 @@ pub const JSValue = enum(i64) {
pub const Extern = [_][]const u8{ "getLengthOfArray", "toZigString", "createStringArray", "createEmptyObject", "putRecord", "asPromise", "isClass", "getNameProperty", "getClassName", "getErrorsProperty", "toInt32", "toBoolean", "isInt32", "isIterable", "forEach", "isAggregateError", "toZigException", "isException", "toWTFString", "hasProperty", "getPropertyNames", "getDirect", "putDirect", "get", "getIfExists", "asString", "asObject", "asNumber", "isError", "jsNull", "jsUndefined", "jsTDZValue", "jsBoolean", "jsDoubleNumber", "jsNumberFromDouble", "jsNumberFromChar", "jsNumberFromU16", "jsNumberFromInt32", "jsNumberFromInt64", "jsNumberFromUint64", "isUndefined", "isNull", "isUndefinedOrNull", "isBoolean", "isAnyInt", "isUInt32AsAnyInt", "isInt32AsAnyInt", "isNumber", "isString", "isBigInt", "isHeapBigInt", "isBigInt32", "isSymbol", "isPrimitive", "isGetterSetter", "isCustomGetterSetter", "isObject", "isCell", "asCell", "toString", "toStringOrNull", "toPropertyKey", "toPropertyKeyValue", "toObject", "toString", "getPrototype", "getPropertyByPropertyName", "eqlValue", "eqlCell", "isCallable" };
};
extern "c" fn Microtask__run(*Microtask, *JSGlobalObject) void;
pub const Microtask = opaque {
pub const name = "Zig::JSMicrotaskCallback";
pub fn run(this: *Microtask, global_object: *JSGlobalObject) void {
if (comptime is_bindgen) {
return;
}
return Microtask__run(this, global_object);
}
};
pub const PropertyName = extern struct {
pub const shim = Shimmer("JSC", "PropertyName", @This());
bytes: shim.Bytes,

View File

@@ -109,6 +109,13 @@ pub const ZigGlobalObject = extern struct {
return @call(.{ .modifier = .always_inline }, Interface.onCrash, .{});
}
pub fn queueMicrotaskToEventLoop(global: *JSGlobalObject, microtask: *Microtask) callconv(.C) void {
if (comptime is_bindgen) {
unreachable;
}
return @call(.{ .modifier = .always_inline }, Interface.queueMicrotaskToEventLoop, .{ global, microtask });
}
pub const Export = shim.exportFunctions(
.{
.@"import" = import,
@@ -119,6 +126,7 @@ pub const ZigGlobalObject = extern struct {
.@"reportUncaughtException" = reportUncaughtException,
.@"createImportMetaProperties" = createImportMetaProperties,
.@"onCrash" = onCrash,
.@"queueMicrotaskToEventLoop" = queueMicrotaskToEventLoop,
},
);
@@ -132,6 +140,7 @@ pub const ZigGlobalObject = extern struct {
@export(reportUncaughtException, .{ .name = Export[4].symbol_name });
@export(createImportMetaProperties, .{ .name = Export[5].symbol_name });
@export(onCrash, .{ .name = Export[6].symbol_name });
@export(queueMicrotaskToEventLoop, .{ .name = Export[7].symbol_name });
}
};

View File

@@ -1,4 +1,4 @@
//-- AUTOGENERATED FILE -- 1636158703
//-- AUTOGENERATED FILE -- 1639731411
// clang-format off
#pragma once

View File

@@ -97,4 +97,5 @@ const JSErrorCode JSErrorCodeUserErrorCode = 254;
extern "C" ZigErrorCode Zig_ErrorCodeParserError;
extern "C" void ZigString__free(const unsigned char *ptr, size_t len, void *allocator);
extern "C" void Microtask__run(void *ptr, void *global);
#endif

View File

@@ -1,5 +1,5 @@
// clang-format: off
//-- AUTOGENERATED FILE -- 1636158703
//-- AUTOGENERATED FILE -- 1639731411
#pragma once
#include <stddef.h>
@@ -108,6 +108,7 @@ typedef void* JSClassRef;
typedef ZigException ZigException;
typedef struct JSC__SetIteratorPrototype JSC__SetIteratorPrototype; // JSC::SetIteratorPrototype
typedef bJSC__SourceCode JSC__SourceCode; // JSC::SourceCode
typedef struct Zig__JSMicrotaskCallback Zig__JSMicrotaskCallback; // Zig::JSMicrotaskCallback
typedef bJSC__JSCell JSC__JSCell; // JSC::JSCell
typedef struct JSC__BigIntPrototype JSC__BigIntPrototype; // JSC::BigIntPrototype
typedef struct JSC__GeneratorFunctionPrototype JSC__GeneratorFunctionPrototype; // JSC::GeneratorFunctionPrototype
@@ -175,6 +176,9 @@ typedef void* JSClassRef;
class StringView;
class ExternalStringImpl;
}
namespace Zig {
class JSMicrotaskCallback;
}
namespace Inspector {
class ScriptArguments;
}
@@ -226,6 +230,7 @@ typedef void* JSClassRef;
using WTF__String = WTF::String;
using WTF__StringView = WTF::StringView;
using WTF__ExternalStringImpl = WTF::ExternalStringImpl;
using Zig__JSMicrotaskCallback = Zig::JSMicrotaskCallback;
using Inspector__ScriptArguments = Inspector::ScriptArguments;
#endif
@@ -588,6 +593,7 @@ ZIG_DECL void Zig__GlobalObject__fetch(ErrorableResolvedSource* arg0, JSC__JSGlo
ZIG_DECL ErrorableZigString Zig__GlobalObject__import(JSC__JSGlobalObject* arg0, ZigString* arg1, ZigString* arg2);
ZIG_DECL void Zig__GlobalObject__onCrash();
ZIG_DECL JSC__JSValue Zig__GlobalObject__promiseRejectionTracker(JSC__JSGlobalObject* arg0, JSC__JSPromise* arg1, uint32_t JSPromiseRejectionOperation2);
ZIG_DECL void Zig__GlobalObject__queueMicrotaskToEventLoop(JSC__JSGlobalObject* arg0, Zig__JSMicrotaskCallback* arg1);
ZIG_DECL JSC__JSValue Zig__GlobalObject__reportUncaughtException(JSC__JSGlobalObject* arg0, JSC__Exception* arg1);
ZIG_DECL void Zig__GlobalObject__resolve(ErrorableZigString* arg0, JSC__JSGlobalObject* arg1, ZigString* arg2, ZigString* arg3);

View File

@@ -48,6 +48,8 @@ pub const JSC__JSPromise = bJSC__JSPromise;
pub const JSC__SetIteratorPrototype = struct_JSC__SetIteratorPrototype;
pub const JSC__SourceCode = bJSC__SourceCode;
pub const Zig__JSMicrotaskCallback = struct_Zig__JSMicrotaskCallback;
pub const JSC__JSCell = bJSC__JSCell;
pub const JSC__BigIntPrototype = struct_JSC__BigIntPrototype;

View File

@@ -728,8 +728,10 @@ pub const Module = struct {
};
const FetchTasklet = Fetch.FetchTasklet;
const TaggedPointerUnion = @import("../../tagged_pointer.zig").TaggedPointerUnion;
pub const Task = TaggedPointerUnion(.{
FetchTasklet,
Microtask,
});
// If you read JavascriptCore/API/JSVirtualMachine.mm - https://github.com/WebKit/WebKit/blob/acff93fb303baa670c055cb24c2bad08691a01a0/Source/JavaScriptCore/API/JSVirtualMachine.mm#L101
@@ -768,6 +770,50 @@ pub const VirtualMachine = struct {
origin_timer: std.time.Timer = undefined,
ready_tasks_count: std.atomic.Atomic(u32) = std.atomic.Atomic(u32).init(0),
pending_tasks_count: std.atomic.Atomic(u32) = std.atomic.Atomic(u32).init(0),
microtasks_queue: std.ArrayList(Task) = std.ArrayList(Task).init(default_allocator),
pub fn enqueueTask(this: *VirtualMachine, task: Task) !void {
_ = this.pending_tasks_count.fetchAdd(1, .Monotonic);
try this.microtasks_queue.append(task);
}
pub fn tick(this: *VirtualMachine) void {
this.global.vm().drainMicrotasks();
_ = this.eventLoopTick();
}
// 👶👶👶 event loop 👶👶👶
pub fn eventLoopTick(this: *VirtualMachine) u32 {
var finished: u32 = 0;
var i: usize = 0;
while (i < this.microtasks_queue.items.len) {
var task: Task = this.microtasks_queue.items[i];
switch (task.tag()) {
.Microtask => {
var micro: *Microtask = task.get(Microtask).?;
_ = this.microtasks_queue.swapRemove(i);
micro.run(this.global);
finished += 1;
continue;
},
.FetchTasklet => {
var fetch_task: *Fetch.FetchTasklet = task.get(Fetch.FetchTasklet).?;
if (fetch_task.status == .done) {
_ = this.ready_tasks_count.fetchSub(1, .Monotonic);
_ = this.microtasks_queue.swapRemove(i);
fetch_task.onDone();
finished += 1;
continue;
}
},
else => unreachable,
}
i += 1;
}
return finished;
}
pub const MacroMap = std.AutoArrayHashMap(i32, js.JSObjectRef);
@@ -1219,7 +1265,15 @@ pub const VirtualMachine = struct {
ret.path = result_path.text;
}
pub fn queueMicrotaskToEventLoop(
global: *JSGlobalObject,
microtask: *Microtask,
) void {
std.debug.assert(VirtualMachine.vm_loaded);
std.debug.assert(VirtualMachine.vm.global == global);
vm.enqueueTask(Task.init(microtask)) catch unreachable;
}
pub fn resolve(res: *ErrorableZigString, global: *JSGlobalObject, specifier: ZigString, source: ZigString) void {
var result = ResolveFunctionResult{ .path = "", .result = null };
@@ -1418,10 +1472,10 @@ pub const VirtualMachine = struct {
if (this.node_modules != null) {
promise = JSModuleLoader.loadAndEvaluateModule(this.global, ZigString.init(std.mem.span(bun_file_import_path)));
this.global.vm().drainMicrotasks();
this.tick();
while (promise.status(this.global.vm()) == JSPromise.Status.Pending) {
this.global.vm().drainMicrotasks();
this.tick();
}
if (promise.status(this.global.vm()) == JSPromise.Status.Rejected) {
@@ -1433,10 +1487,10 @@ pub const VirtualMachine = struct {
promise = JSModuleLoader.loadAndEvaluateModule(this.global, ZigString.init(std.mem.span(main_file_name)));
this.global.vm().drainMicrotasks();
this.tick();
while (promise.status(this.global.vm()) == JSPromise.Status.Pending) {
this.global.vm().drainMicrotasks();
this.tick();
}
return promise;
@@ -1453,30 +1507,13 @@ pub const VirtualMachine = struct {
var entry_point = entry_point_entry.value_ptr.*;
var promise: *JSInternalPromise = undefined;
// We first import the node_modules bundle. This prevents any potential TDZ issues.
// The contents of the node_modules bundle are lazy, so hopefully this should be pretty quick.
// if (this.node_modules != null) {
// promise = JSModuleLoader.loadAndEvaluateModule(this.global, ZigString.init(std.mem.span(bun_file_import_path)));
// this.global.vm().drainMicrotasks();
// while (promise.status(this.global.vm()) == JSPromise.Status.Pending) {
// this.global.vm().drainMicrotasks();
// }
// if (promise.status(this.global.vm()) == JSPromise.Status.Rejected) {
// return promise;
// }
// _ = promise.result(this.global.vm());
// }
promise = JSModuleLoader.loadAndEvaluateModule(this.global, ZigString.init(entry_point.source.path.text));
this.global.vm().drainMicrotasks();
this.tick();
while (promise.status(this.global.vm()) == JSPromise.Status.Pending) {
this.global.vm().drainMicrotasks();
this.tick();
}
return promise;
@@ -1914,7 +1951,7 @@ pub const EventListenerMixin = struct {
var result = js.JSObjectCallAsFunctionReturnValue(vm.global.ref(), listener_ref, null, 1, &fetch_args);
var promise = JSPromise.resolvedPromise(vm.global, result);
vm.global.vm().drainMicrotasks();
vm.tick();
if (fetch_event.rejected) return;
@@ -1925,7 +1962,7 @@ pub const EventListenerMixin = struct {
_ = promise.result(vm.global.vm());
}
vm.global.vm().drainMicrotasks();
vm.tick();
if (fetch_event.request_context.has_called_done) {
break;

View File

@@ -7,8 +7,11 @@ usingnamespace @import("../bindings/bindings.zig");
const ZigURL = @import("../../../query_string_map.zig").URL;
const HTTPClient = @import("http");
const NetworkThread = @import("network_thread");
const Method = @import("../../../http/method.zig").Method;
const ObjectPool = @import("../../../pool.zig").ObjectPool;
const picohttp = @import("picohttp");
pub const Response = struct {
pub const Class = NewClass(
@@ -414,10 +417,28 @@ pub const Fetch = struct {
const fetch_error_cant_fetch_same_origin = "fetch to same-origin on the server is not supported yet - sorry! (it would just hang forever)";
pub const FetchTasklet = struct {
promise: *JSPromise = undefined,
promise: *JSInternalPromise = undefined,
http: HTTPClient.AsyncHTTP = undefined,
status: Status = Status.pending,
javascript_vm: *VirtualMachine = undefined,
global_this: *JSGlobalObject = undefined,
empty_request_body: MutableString = undefined,
pooled_body: *BodyPool.Node = undefined,
this_object: js.JSObjectRef = null,
resolve: js.JSObjectRef = null,
reject: js.JSObjectRef = null,
context: FetchTaskletContext = undefined,
const Pool = ObjectPool(FetchTasklet, init);
const BodyPool = ObjectPool(MutableString, MutableString.init2048);
pub const FetchTaskletContext = struct {
tasklet: *FetchTasklet,
};
pub fn init(allocator: *std.mem.Allocator) anyerror!FetchTasklet {
return FetchTasklet{};
}
pub const Status = enum(u8) {
pending,
@@ -425,10 +446,172 @@ pub const Fetch = struct {
done,
};
pub fn onDone(this: *FetchTasklet) void {
var args = [1]js.JSValueRef{undefined};
var callback_object = switch (this.http.state.load(.Monotonic)) {
.success => this.resolve,
.fail => this.reject,
else => unreachable,
};
args[0] = switch (this.http.state.load(.Monotonic)) {
.success => this.onResolve().asObjectRef(),
.fail => this.onReject().asObjectRef(),
else => unreachable,
};
_ = js.JSObjectCallAsFunction(this.global_this.ref(), callback_object, null, 1, &args, null);
this.release();
}
pub fn reset(this: *FetchTasklet) void {}
pub fn release(this: *FetchTasklet) void {
js.JSValueUnprotect(this.global_this.ref(), this.resolve);
js.JSValueUnprotect(this.global_this.ref(), this.reject);
js.JSValueUnprotect(this.global_this.ref(), this.this_object);
this.global_this = undefined;
this.javascript_vm = undefined;
this.promise = undefined;
this.status = Status.pending;
var pooled = this.pooled_body;
BodyPool.release(pooled);
this.pooled_body = undefined;
this.http = undefined;
this.this_object = null;
this.resolve = null;
this.reject = null;
Pool.release(@fieldParentPtr(Pool.Node, "data", this));
}
pub const FetchResolver = struct {
pub fn call(
ctx: js.JSContextRef,
function: js.JSObjectRef,
thisObject: js.JSObjectRef,
arguments_len: usize,
arguments: [*c]const js.JSValueRef,
exception: js.ExceptionRef,
) callconv(.C) js.JSObjectRef {
return JSPrivateDataPtr.from(js.JSObjectGetPrivate(arguments[0]))
.get(FetchTaskletContext).?.tasklet.onResolve().asObjectRef();
// return js.JSObjectGetPrivate(arguments[0]).? .tasklet.onResolve().asObjectRef();
}
};
pub const FetchRejecter = struct {
pub fn call(
ctx: js.JSContextRef,
function: js.JSObjectRef,
thisObject: js.JSObjectRef,
arguments_len: usize,
arguments: [*c]const js.JSValueRef,
exception: js.ExceptionRef,
) callconv(.C) js.JSObjectRef {
return JSPrivateDataPtr.from(js.JSObjectGetPrivate(arguments[0]))
.get(FetchTaskletContext).?.tasklet.onReject().asObjectRef();
}
};
pub fn onReject(this: *FetchTasklet) JSValue {
const fetch_error = std.fmt.allocPrint(
default_allocator,
"Fetch error: {s}\nURL: \"{s}\"",
.{
@errorName(this.http.err orelse error.HTTPFail),
this.http.url.href,
},
) catch unreachable;
return ZigString.init(fetch_error).toErrorInstance(this.global_this);
}
pub fn onResolve(this: *FetchTasklet) JSValue {
var allocator = default_allocator;
var http_response = this.http.response.?;
var response_headers = Headers.fromPicoHeaders(allocator, http_response.headers) catch unreachable;
response_headers.guard = .immutable;
var response = allocator.create(Response) catch unreachable;
var duped = allocator.dupe(u8, this.http.response_buffer.toOwnedSlice()) catch unreachable;
response.* = Response{
.allocator = allocator,
.status_text = allocator.dupe(u8, http_response.status) catch unreachable,
.body = .{
.init = .{
.headers = response_headers,
.status_code = @truncate(u16, http_response.status_code),
},
.value = .{
.Unconsumed = 0,
},
.ptr = duped.ptr,
.len = duped.len,
.ptr_allocator = allocator,
},
};
return JSValue.fromRef(Response.Class.make(@ptrCast(js.JSContextRef, this.global_this), response));
}
pub fn get(
allocator: *std.mem.Allocator,
method: Method,
url: ZigURL,
headers: Headers.Entries,
headers_buf: string,
request_body: ?*MutableString,
timeout: usize,
) !*FetchTasklet.Pool.Node {
var linked_list = FetchTasklet.Pool.get(allocator);
linked_list.data.javascript_vm = VirtualMachine.vm;
linked_list.data.empty_request_body = MutableString.init(allocator, 0) catch unreachable;
linked_list.data.pooled_body = BodyPool.get(allocator);
linked_list.data.http = try HTTPClient.AsyncHTTP.init(
allocator,
method,
url,
headers,
headers_buf,
&linked_list.data.pooled_body.data,
request_body orelse &linked_list.data.empty_request_body,
timeout,
);
linked_list.data.context = .{ .tasklet = &linked_list.data };
return linked_list;
}
pub fn queue(
allocator: *std.mem.Allocator,
global: *JSGlobalObject,
method: Method,
url: ZigURL,
headers: Headers.Entries,
headers_buf: string,
request_body: ?*MutableString,
timeout: usize,
) !*FetchTasklet.Pool.Node {
var node = try get(allocator, method, url, headers, headers_buf, request_body, timeout);
node.data.promise = JSInternalPromise.create(global);
node.data.global_this = global;
node.data.http.callback = callback;
var batch = NetworkThread.Batch{};
node.data.http.schedule(allocator, &batch);
NetworkThread.global.pool.schedule(batch);
try VirtualMachine.vm.enqueueTask(Task.init(&node.data));
return node;
}
pub fn callback(http_: *HTTPClient.AsyncHTTP, sender: *HTTPClient.AsyncHTTP.HTTPSender) void {
var task: *FetchTasklet = @fieldParentPtr(FetchTasklet, "http", http_);
@atomicStore(Status.done, &task.status, Status.done, .Monotonic);
task.javascript_vm.pending_tasks.fetchAdd(.Monotonic, 1);
@atomicStore(Status, &task.status, Status.done, .Monotonic);
_ = task.javascript_vm.ready_tasks_count.fetchAdd(1, .Monotonic);
_ = task.javascript_vm.pending_tasks_count.fetchSub(1, .Monotonic);
sender.release();
}
};
@@ -466,8 +649,6 @@ pub const Fetch = struct {
url_str = getAllocator(ctx).dupe(u8, url_str) catch unreachable;
}
defer getAllocator(ctx).free(url_str);
NetworkThread.init() catch @panic("Failed to start network thread");
const url = ZigURL.parse(url_str);
@@ -478,6 +659,7 @@ pub const Fetch = struct {
var headers: ?Headers = null;
var body: string = "";
var method = Method.GET;
if (arguments.len >= 2 and js.JSValueIsObject(ctx, arguments[1])) {
var array = js.JSObjectCopyPropertyNames(ctx, arguments[1]);
@@ -524,7 +706,7 @@ pub const Fetch = struct {
defer js.JSStringRelease(string_ref);
var method_name_buf: [16]u8 = undefined;
var method_name = method_name_buf[0..js.JSStringGetUTF8CString(string_ref, &method_name_buf, method_name_buf.len)];
http_client.method = Method.which(method_name) orelse http_client.method;
method = Method.which(method_name) orelse method;
}
}
},
@@ -533,56 +715,39 @@ pub const Fetch = struct {
}
}
var header_entries: Headers.Entries = .{};
var header_buf: string = "";
if (headers) |head| {
http_client.header_entries = head.entries;
http_client.header_buf = head.buf.items;
header_entries = head.entries;
header_buf = head.buf.items;
}
var resolve = js.JSObjectMakeFunctionWithCallback(ctx, null, Fetch.FetchTasklet.FetchResolver.call);
var reject = js.JSObjectMakeFunctionWithCallback(ctx, null, Fetch.FetchTasklet.FetchRejecter.call);
if (fetch_body_string_loaded) {
fetch_body_string.reset();
} else {
fetch_body_string = MutableString.init(VirtualMachine.vm.allocator, 0) catch unreachable;
fetch_body_string_loaded = true;
}
js.JSValueProtect(ctx, resolve);
js.JSValueProtect(ctx, reject);
var http_response = http_client.send(body, &fetch_body_string) catch |err| {
const fetch_error = std.fmt.allocPrint(
getAllocator(ctx),
"Fetch error: {s}\nURL: \"{s}\"",
.{
@errorName(err),
url_str,
},
) catch unreachable;
return JSPromise.rejectedPromiseValue(VirtualMachine.vm.global, ZigString.init(fetch_error).toErrorInstance(VirtualMachine.vm.global)).asRef();
};
var response_headers = Headers.fromPicoHeaders(getAllocator(ctx), http_response.headers) catch unreachable;
response_headers.guard = .immutable;
var response = getAllocator(ctx).create(Response) catch unreachable;
var allocator = getAllocator(ctx);
var duped = allocator.dupeZ(u8, fetch_body_string.list.items) catch unreachable;
response.* = Response{
.allocator = allocator,
.status_text = allocator.dupe(u8, http_response.status) catch unreachable,
.body = .{
.init = .{
.headers = response_headers,
.status_code = @truncate(u16, http_response.status_code),
},
.value = .{
.Unconsumed = 0,
},
.ptr = duped.ptr,
.len = duped.len,
.ptr_allocator = allocator,
},
};
return JSPromise.resolvedPromiseValue(
// var resolve = FetchTasklet.FetchResolver.Class.make(ctx: js.JSContextRef, ptr: *ZigType)
var queued = FetchTasklet.queue(
default_allocator,
VirtualMachine.vm.global,
JSValue.fromRef(Response.Class.make(ctx, response)),
).asRef();
method,
url,
header_entries,
header_buf,
null,
std.time.ns_per_hour,
) catch unreachable;
queued.data.this_object = js.JSObjectMake(ctx, null, JSPrivateDataPtr.init(&queued.data.context).ptr());
js.JSValueProtect(ctx, queued.data.this_object);
var promise = js.JSObjectMakeDeferredPromise(ctx, &resolve, &reject, exception);
queued.data.reject = reject;
queued.data.resolve = resolve;
return promise;
// queued.data.promise.create(globalThis: *JSGlobalObject)
}
};
@@ -1511,6 +1676,7 @@ pub const FetchEvent = struct {
response: ?*Response = null,
request_context: *http.RequestContext,
request: Request,
pending_promise: ?*JSInternalPromise = null,
onPromiseRejectionCtx: *c_void = undefined,
onPromiseRejectionHandler: ?fn (ctx: *c_void, err: anyerror, fetch_event: *FetchEvent, value: JSValue) void = null,
@@ -1589,47 +1755,56 @@ pub const FetchEvent = struct {
if (this.request_context.has_called_done) return js.JSValueMakeUndefined(ctx);
// A Response or a Promise that resolves to a Response. Otherwise, a network error is returned to Fetch.
if (arguments.len == 0 or !Response.Class.loaded) {
if (arguments.len == 0 or !Response.Class.loaded or !js.JSValueIsObject(ctx, arguments[0])) {
JSError(getAllocator(ctx), "event.respondWith() must be a Response or a Promise<Response>.", .{}, ctx, exception);
this.request_context.sendInternalError(error.respondWithWasEmpty) catch {};
return js.JSValueMakeUndefined(ctx);
}
var resolved = JSInternalPromise.resolvedPromise(VirtualMachine.vm.global, JSValue.fromRef(arguments[0]));
var arg = arguments[0];
var status = resolved.status(VirtualMachine.vm.global.vm());
if (status == .Pending) {
VirtualMachine.vm.global.vm().drainMicrotasks();
if (!js.JSValueIsObjectOfClass(ctx, arg, Response.Class.ref)) {
this.pending_promise = this.pending_promise orelse JSInternalPromise.resolvedPromise(VirtualMachine.vm.global, JSValue.fromRef(arguments[0]));
}
status = resolved.status(VirtualMachine.vm.global.vm());
if (this.pending_promise) |promise| {
var status = promise.status(VirtualMachine.vm.global.vm());
switch (status) {
.Fulfilled => {},
else => {
this.rejected = true;
this.onPromiseRejectionHandler.?(
this.onPromiseRejectionCtx,
error.PromiseRejection,
this,
resolved.result(VirtualMachine.vm.global.vm()),
);
return js.JSValueMakeUndefined(ctx);
},
if (status == .Pending) {
VirtualMachine.vm.tick();
status = promise.status(VirtualMachine.vm.global.vm());
}
switch (status) {
.Fulfilled => {},
else => {
this.rejected = true;
this.pending_promise = null;
this.onPromiseRejectionHandler.?(
this.onPromiseRejectionCtx,
error.PromiseRejection,
this,
promise.result(VirtualMachine.vm.global.vm()),
);
return js.JSValueMakeUndefined(ctx);
},
}
arg = promise.result(VirtualMachine.vm.global.vm()).asRef();
}
var arg = resolved.result(VirtualMachine.vm.global.vm()).asObjectRef();
if (!js.JSValueIsObjectOfClass(ctx, arg, Response.Class.ref)) {
this.rejected = true;
this.pending_promise = null;
JSError(getAllocator(ctx), "event.respondWith() must be a Response or a Promise<Response>.", .{}, ctx, exception);
this.onPromiseRejectionHandler.?(this.onPromiseRejectionCtx, error.RespondWithInvalidType, this, JSValue.fromRef(exception.*));
return js.JSValueMakeUndefined(ctx);
}
var response: *Response = GetJSPrivateData(Response, arg) orelse {
this.rejected = true;
this.pending_promise = null;
JSError(getAllocator(ctx), "event.respondWith()'s Response object was invalid. This may be an internal error.", .{}, ctx, exception);
this.onPromiseRejectionHandler.?(this.onPromiseRejectionCtx, error.RespondWithInvalidTypeInternal, this, JSValue.fromRef(exception.*));
return js.JSValueMakeUndefined(ctx);
@@ -1650,6 +1825,7 @@ pub const FetchEvent = struct {
}
}
defer this.pending_promise = null;
var needs_mime_type = true;
var content_length: ?usize = null;
if (response.body.init.headers) |*headers| {