mirror of
https://github.com/oven-sh/bun
synced 2026-02-10 10:58:56 +00:00
👶event loop
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//-- AUTOGENERATED FILE -- 1636158703
|
||||
//-- AUTOGENERATED FILE -- 1639731411
|
||||
// clang-format off
|
||||
#pragma once
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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| {
|
||||
|
||||
Reference in New Issue
Block a user