diff options
-rw-r--r-- | src/bun_js.zig | 5 | ||||
-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.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 | 87 | ||||
-rw-r--r-- | src/javascript/jsc/webcore/response.zig | 318 | ||||
-rw-r--r-- | src/js_ast.zig | 2 | ||||
-rw-r--r-- | src/pool.zig | 10 | ||||
-rw-r--r-- | src/runtime.version | 2 | ||||
-rw-r--r-- | src/thread_pool.zig | 2 |
18 files changed, 404 insertions, 115 deletions
diff --git a/src/bun_js.zig b/src/bun_js.zig index e5fb2bcd7..cb1d6c219 100644 --- a/src/bun_js.zig +++ b/src/bun_js.zig @@ -68,11 +68,10 @@ pub const Run = struct { pub fn start(this: *Run) !void { var promise = try this.vm.loadEntryPoint(this.entry_path); - - this.vm.global.vm().drainMicrotasks(); + this.vm.tick(); while (promise.status(this.vm.global.vm()) == .Pending) { - this.vm.global.vm().drainMicrotasks(); + this.vm.tick(); } if (promise.status(this.vm.global.vm()) == .Rejected) { 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.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..ec3a8cb2c 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 -- 1639731411 // 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..3633c7ca1 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 -- 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); 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 e05153366..d864c2d17 100644 --- a/src/javascript/jsc/javascript.zig +++ b/src/javascript/jsc/javascript.zig @@ -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; diff --git a/src/javascript/jsc/webcore/response.zig b/src/javascript/jsc/webcore/response.zig index d4c5f5936..248c3a4a7 100644 --- a/src/javascript/jsc/webcore/response.zig +++ b/src/javascript/jsc/webcore/response.zig @@ -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 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) } }; @@ -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 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()); + } + + 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); + }, + } - var arg = resolved.result(VirtualMachine.vm.global.vm()).asObjectRef(); + 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); @@ -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| { diff --git a/src/js_ast.zig b/src/js_ast.zig index 860e980e9..cebad7115 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -7101,7 +7101,7 @@ pub const Macro = struct { js.JSValueProtect(macro.vm.global.ref(), result.asRef()); defer js.JSValueUnprotect(macro.vm.global.ref(), result.asRef()); var promise = JSC.JSPromise.resolvedPromise(macro.vm.global, result); - macro.vm.global.vm().drainMicrotasks(); + _ = macro.vm.tick(); if (promise.status(macro.vm.global.vm()) == .Rejected) { macro.vm.defaultErrorHandler(promise.result(macro.vm.global.vm()), null); diff --git a/src/pool.zig b/src/pool.zig index b4fe0eb29..f37b62162 100644 --- a/src/pool.zig +++ b/src/pool.zig @@ -6,7 +6,9 @@ pub fn ObjectPool(comptime Type: type, comptime Init: (fn (allocator: *std.mem.A // mimalloc crashes on realloc across threads threadlocal var list: LinkedList = undefined; threadlocal var loaded: bool = false; - pub fn get(allocator: *std.mem.Allocator) *LinkedList.Node { + + pub const Node = LinkedList.Node; + pub fn get(allocator: *std.mem.Allocator) *Node { if (loaded) { if (list.popFirst()) |node| { node.data.reset(); @@ -14,8 +16,8 @@ pub fn ObjectPool(comptime Type: type, comptime Init: (fn (allocator: *std.mem.A } } - var new_node = allocator.create(LinkedList.Node) catch unreachable; - new_node.* = LinkedList.Node{ + var new_node = allocator.create(Node) catch unreachable; + new_node.* = Node{ .data = Init( allocator, ) catch unreachable, @@ -24,7 +26,7 @@ pub fn ObjectPool(comptime Type: type, comptime Init: (fn (allocator: *std.mem.A return new_node; } - pub fn release(node: *LinkedList.Node) void { + pub fn release(node: *Node) void { if (loaded) { list.prepend(node); return; diff --git a/src/runtime.version b/src/runtime.version index 9a96f51a3..936ae3d67 100644 --- a/src/runtime.version +++ b/src/runtime.version @@ -1 +1 @@ -54c050533edcf4b5
\ No newline at end of file +b79c80cf594c185e
\ No newline at end of file diff --git a/src/thread_pool.zig b/src/thread_pool.zig index 19ea14e2d..4df199006 100644 --- a/src/thread_pool.zig +++ b/src/thread_pool.zig @@ -247,7 +247,7 @@ noinline fn wait(self: *ThreadPool, _is_waking: bool) error{Shutdown}!bool { } } else { if (self.io) |io| { - const HTTP = @import("./http/http_client_async.zig"); + const HTTP = @import("http"); io.run_for_ns(std.time.ns_per_us * 100) catch {}; while (HTTP.AsyncHTTP.active_requests_count.load(.Monotonic) > 255) { io.tick() catch {}; |