diff options
Diffstat (limited to 'src/javascript/jsc')
-rw-r--r-- | src/javascript/jsc/JavascriptCore.zig | 2 | ||||
-rw-r--r-- | src/javascript/jsc/api/router.zig | 4 | ||||
-rw-r--r-- | src/javascript/jsc/base.zig | 2 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/ZigGlobalObject.cpp | 15 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/ZigGlobalObject.h | 28 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/bindings.cpp | 6 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/bindings.zig | 20 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/exports.zig | 9 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/headers-cpp.h | 2 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/headers-handwritten.h | 1 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/headers.h | 8 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/headers.zig | 2 | ||||
-rw-r--r-- | src/javascript/jsc/javascript.zig | 94 | ||||
-rw-r--r-- | src/javascript/jsc/webcore/response.zig | 341 |
14 files changed, 429 insertions, 105 deletions
diff --git a/src/javascript/jsc/JavascriptCore.zig b/src/javascript/jsc/JavascriptCore.zig index e6ac23bb7..807ea0053 100644 --- a/src/javascript/jsc/JavascriptCore.zig +++ b/src/javascript/jsc/JavascriptCore.zig @@ -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; diff --git a/src/javascript/jsc/api/router.zig b/src/javascript/jsc/api/router.zig index 35bd2a865..3b3cf8f99 100644 --- a/src/javascript/jsc/api/router.zig +++ b/src/javascript/jsc/api/router.zig @@ -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(); } diff --git a/src/javascript/jsc/base.zig b/src/javascript/jsc/base.zig index 63e07b635..64a0f15a3 100644 --- a/src/javascript/jsc/base.zig +++ b/src/javascript/jsc/base.zig @@ -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 { diff --git a/src/javascript/jsc/bindings/ZigGlobalObject.cpp b/src/javascript/jsc/bindings/ZigGlobalObject.cpp index b8470ce4b..4d8a2a6da 100644 --- a/src/javascript/jsc/bindings/ZigGlobalObject.cpp +++ b/src/javascript/jsc/bindings/ZigGlobalObject.cpp @@ -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
\ No newline at end of file diff --git a/src/javascript/jsc/bindings/ZigGlobalObject.h b/src/javascript/jsc/bindings/ZigGlobalObject.h index 4b1e1e935..64ece0c64 100644 --- a/src/javascript/jsc/bindings/ZigGlobalObject.h +++ b/src/javascript/jsc/bindings/ZigGlobalObject.h @@ -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 diff --git a/src/javascript/jsc/bindings/bindings.cpp b/src/javascript/jsc/bindings/bindings.cpp index 674849fc6..f29cfe1e9 100644 --- a/src/javascript/jsc/bindings/bindings.cpp +++ b/src/javascript/jsc/bindings/bindings.cpp @@ -23,6 +23,7 @@ #include <JavaScriptCore/JSObject.h> #include <JavaScriptCore/JSSet.h> #include <JavaScriptCore/JSString.h> +#include <JavaScriptCore/Microtask.h> #include <JavaScriptCore/ObjectConstructor.h> #include <JavaScriptCore/ParserError.h> #include <JavaScriptCore/ScriptExecutable.h> @@ -37,6 +38,7 @@ #include <wtf/text/WTFString.h> extern "C" { + JSC__JSValue JSC__JSObject__create(JSC__JSGlobalObject *globalObject, size_t initialCapacity, void *arg2, void (*ArgFn3)(void *arg0, JSC__JSObject *arg1, JSC__JSGlobalObject *arg2)) { @@ -283,6 +285,10 @@ bWTF__String JSC__JSString__value(JSC__JSString *arg0, JSC__JSGlobalObject *arg1 // arg2->depen // } +void Microtask__run(void *microtask, void *global) { + reinterpret_cast<Zig::JSMicrotaskCallback *>(microtask)->call(); +} + bool JSC__JSModuleLoader__checkSyntax(JSC__JSGlobalObject *arg0, const JSC__SourceCode *arg1, bool arg2) { JSC::ParserError error; diff --git a/src/javascript/jsc/bindings/bindings.zig b/src/javascript/jsc/bindings/bindings.zig index 02f026ea5..4973a0400 100644 --- a/src/javascript/jsc/bindings/bindings.zig +++ b/src/javascript/jsc/bindings/bindings.zig @@ -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, diff --git a/src/javascript/jsc/bindings/exports.zig b/src/javascript/jsc/bindings/exports.zig index b325a1208..00f58e745 100644 --- a/src/javascript/jsc/bindings/exports.zig +++ b/src/javascript/jsc/bindings/exports.zig @@ -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 }); } }; diff --git a/src/javascript/jsc/bindings/headers-cpp.h b/src/javascript/jsc/bindings/headers-cpp.h index 8b050bbfe..c57f02fe9 100644 --- a/src/javascript/jsc/bindings/headers-cpp.h +++ b/src/javascript/jsc/bindings/headers-cpp.h @@ -1,4 +1,4 @@ -//-- AUTOGENERATED FILE -- 1636158703 +//-- AUTOGENERATED FILE -- 1639884060 // clang-format off #pragma once diff --git a/src/javascript/jsc/bindings/headers-handwritten.h b/src/javascript/jsc/bindings/headers-handwritten.h index a8d53f6e3..e07eca6b6 100644 --- a/src/javascript/jsc/bindings/headers-handwritten.h +++ b/src/javascript/jsc/bindings/headers-handwritten.h @@ -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 diff --git a/src/javascript/jsc/bindings/headers.h b/src/javascript/jsc/bindings/headers.h index 8c2586ba8..95c6b78ad 100644 --- a/src/javascript/jsc/bindings/headers.h +++ b/src/javascript/jsc/bindings/headers.h @@ -1,5 +1,5 @@ // clang-format: off -//-- AUTOGENERATED FILE -- 1636158703 +//-- AUTOGENERATED FILE -- 1639884060 #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); diff --git a/src/javascript/jsc/bindings/headers.zig b/src/javascript/jsc/bindings/headers.zig index e02044721..070d2ad05 100644 --- a/src/javascript/jsc/bindings/headers.zig +++ b/src/javascript/jsc/bindings/headers.zig @@ -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; diff --git a/src/javascript/jsc/javascript.zig b/src/javascript/jsc/javascript.zig index 03ef373f7..d864c2d17 100644 --- a/src/javascript/jsc/javascript.zig +++ b/src/javascript/jsc/javascript.zig @@ -727,6 +727,13 @@ pub const Module = struct { reload_pending: bool = false, }; +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 // We can see that it's sort of like std.mem.Allocator but for JSGlobalContextRef, to support Automatic Reference Counting // Its unavailable on Linux @@ -762,6 +769,52 @@ 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); pub threadlocal var vm_loaded = false; @@ -1212,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 }; @@ -1411,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) { @@ -1426,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; @@ -1446,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; @@ -1907,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; @@ -1918,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; diff --git a/src/javascript/jsc/webcore/response.zig b/src/javascript/jsc/webcore/response.zig index 8a5a0c1c1..248c3a4a7 100644 --- a/src/javascript/jsc/webcore/response.zig +++ b/src/javascript/jsc/webcore/response.zig @@ -5,9 +5,13 @@ const http = @import("../../../http.zig"); usingnamespace @import("../javascript.zig"); usingnamespace @import("../bindings/bindings.zig"); const ZigURL = @import("../../../query_string_map.zig").URL; -const HTTPClient = @import("../../../http_client.zig"); +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( @@ -411,6 +415,207 @@ 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: *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, + running, + 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, &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(); + } + }; + pub fn call( this: void, ctx: js.JSContextRef, @@ -444,17 +649,17 @@ 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); - var http_client = HTTPClient.init(getAllocator(ctx), .GET, ZigURL.parse(url_str), .{}, ""); - - if (http_client.url.origin.len > 0 and strings.eql(http_client.url.origin, VirtualMachine.vm.bundler.options.origin.origin)) { + if (url.origin.len > 0 and strings.eql(url.origin, VirtualMachine.vm.bundler.options.origin.origin)) { const fetch_error = fetch_error_cant_fetch_same_origin; return JSPromise.rejectedPromiseValue(VirtualMachine.vm.global, ZigString.init(fetch_error).toErrorInstance(VirtualMachine.vm.global)).asRef(); } 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]); @@ -501,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; } } }, @@ -510,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 resolve = FetchTasklet.FetchResolver.Class.make(ctx: js.JSContextRef, ptr: *ZigType) + var queued = FetchTasklet.queue( + default_allocator, + VirtualMachine.vm.global, + 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 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, - }, - }; + var promise = js.JSObjectMakeDeferredPromise(ctx, &resolve, &reject, exception); + queued.data.reject = reject; + queued.data.resolve = resolve; - return JSPromise.resolvedPromiseValue( - VirtualMachine.vm.global, - JSValue.fromRef(Response.Class.make(ctx, response)), - ).asRef(); + return promise; + // queued.data.promise.create(globalThis: *JSGlobalObject) } }; @@ -1488,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, @@ -1566,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 status = resolved.status(VirtualMachine.vm.global.vm()); + var arg = arguments[0]; - 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()); + } - var arg = resolved.result(VirtualMachine.vm.global.vm()).asObjectRef(); + 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(); + } 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); @@ -1627,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| { |