Implement Workers (#3645)

* copy files

* format

* options

* Introduce `Worker`, `onmessage`, `onerror`, and `postMessage` globals

* Stub `Worker.prototype.ref` & `Worker.prototype.unref`

* Update web_worker.zig

* Worker works

* Add "mini" mode

* add wakeup

* Partially fix the keep-alive issue

* clean up refer behavior

* Implement `serialize` & `deserialize` in `bun:jsc` & add polyfill for `node:v8`

* Types & docs

* Update globals.d.ts

* Add mutex

* Fixes

---------

Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
This commit is contained in:
Jarred Sumner
2023-07-16 21:15:24 -07:00
committed by GitHub
parent 7fc392b182
commit 209dc981c0
46 changed files with 2796 additions and 75 deletions

View File

@@ -96,7 +96,7 @@ pub const GlobalConstructors = [_]type{
pub const GlobalClasses = [_]type{
Bun.Class,
WebCore.Crypto.Class,
EventListenerMixin.addEventListener(VirtualMachine),
// EventListenerMixin.addEventListener(VirtualMachine),
// Fetch.Class,
js_ast.Macro.JSNode.BunJSXCallbackFunction,
@@ -353,7 +353,8 @@ pub const ExitHandler = struct {
JSC.markBinding(@src());
var vm = @fieldParentPtr(VirtualMachine, "exit_handler", this);
Process__dispatchOnExit(vm.global, this.exit_code);
Bun__closeAllSQLiteDatabasesForTermination();
if (vm.isMainThread())
Bun__closeAllSQLiteDatabasesForTermination();
}
pub fn dispatchOnBeforeExit(this: *ExitHandler) void {
@@ -363,6 +364,8 @@ pub const ExitHandler = struct {
}
};
pub const WebWorker = @import("./web_worker.zig").WebWorker;
/// TODO: rename this to ScriptExecutionContext
/// This is the shared global state for a single JS instance execution
/// Today, Bun is one VM per thread, so the name "VirtualMachine" sort of makes sense
@@ -482,11 +485,16 @@ pub const VirtualMachine = struct {
parser_arena: ?@import("root").bun.ArenaAllocator = null,
gc_controller: JSC.GarbageCollectionController = .{},
worker: ?*JSC.WebWorker = null,
pub const OnUnhandledRejection = fn (*VirtualMachine, globalObject: *JSC.JSGlobalObject, JSC.JSValue) void;
pub const OnException = fn (*ZigException) void;
pub fn isMainThread(this: *const VirtualMachine) bool {
return this.worker == null;
}
pub fn setOnException(this: *VirtualMachine, callback: *const OnException) void {
this.on_exception = callback;
}
@@ -877,6 +885,8 @@ pub const VirtualMachine = struct {
&global_classes,
@intCast(i32, global_classes.len),
vm.console,
-1,
false,
);
vm.regular_event_loop.global = vm.global;
vm.regular_event_loop.virtual_machine = vm;
@@ -976,6 +986,110 @@ pub const VirtualMachine = struct {
&global_classes,
@intCast(i32, global_classes.len),
vm.console,
-1,
false,
);
vm.regular_event_loop.global = vm.global;
vm.regular_event_loop.virtual_machine = vm;
if (source_code_printer == null) {
var writer = try js_printer.BufferWriter.init(allocator);
source_code_printer = allocator.create(js_printer.BufferPrinter) catch unreachable;
source_code_printer.?.* = js_printer.BufferPrinter.init(writer);
source_code_printer.?.ctx.append_null_byte = false;
}
return vm;
}
pub fn initWorker(
allocator: std.mem.Allocator,
_args: Api.TransformOptions,
_log: ?*logger.Log,
env_loader: ?*DotEnv.Loader,
store_fd: bool,
worker: *WebWorker,
) anyerror!*VirtualMachine {
var log: *logger.Log = undefined;
if (_log) |__log| {
log = __log;
} else {
log = try allocator.create(logger.Log);
log.* = logger.Log.init(allocator);
}
VMHolder.vm = try allocator.create(VirtualMachine);
var console = try allocator.create(ZigConsoleClient);
console.* = ZigConsoleClient.init(Output.errorWriter(), Output.writer());
const bundler = try Bundler.init(
allocator,
log,
try Config.configureTransformOptionsForBunVM(allocator, _args),
null,
env_loader,
);
var vm = VMHolder.vm.?;
vm.* = VirtualMachine{
.global = undefined,
.allocator = allocator,
.entry_point = ServerEntryPoint{},
.event_listeners = EventListenerMixin.Map.init(allocator),
.bundler = bundler,
.console = console,
.node_modules = bundler.options.node_modules_bundle,
.log = log,
.flush_list = std.ArrayList(string).init(allocator),
.blobs = if (_args.serve orelse false) try Blob.Group.init(allocator) else null,
.origin = bundler.options.origin,
.saved_source_map_table = SavedSourceMap.HashTable.init(allocator),
.source_mappings = undefined,
.macros = MacroMap.init(allocator),
.macro_entry_points = @TypeOf(vm.macro_entry_points).init(allocator),
.origin_timer = std.time.Timer.start() catch @panic("Please don't mess with timers."),
.origin_timestamp = getOriginTimestamp(),
.ref_strings = JSC.RefString.Map.init(allocator),
.file_blobs = JSC.WebCore.Blob.Store.Map.init(allocator),
.parser_arena = @import("root").bun.ArenaAllocator.init(allocator),
.standalone_module_graph = worker.parent.standalone_module_graph,
.worker = worker,
};
vm.source_mappings = .{ .map = &vm.saved_source_map_table };
vm.regular_event_loop.tasks = EventLoop.Queue.init(
default_allocator,
);
vm.regular_event_loop.tasks.ensureUnusedCapacity(64) catch unreachable;
vm.regular_event_loop.concurrent_tasks = .{};
vm.event_loop = &vm.regular_event_loop;
vm.hot_reload = worker.parent.hot_reload;
vm.bundler.macro_context = null;
vm.bundler.resolver.store_fd = store_fd;
vm.bundler.resolver.prefer_module_field = false;
vm.bundler.resolver.onWakePackageManager = .{
.context = &vm.modules,
.handler = ModuleLoader.AsyncModule.Queue.onWakeHandler,
.onDependencyError = JSC.ModuleLoader.AsyncModule.Queue.onDependencyError,
};
vm.bundler.configureLinker();
try vm.bundler.configureFramework(false);
vm.bundler.macro_context = js_ast.Macro.MacroContext.init(&vm.bundler);
if (_args.serve orelse false) {
vm.bundler.linker.onImportCSS = Bun.onImportCSS;
}
var global_classes: [GlobalClasses.len]js.JSClassRef = undefined;
inline for (GlobalClasses, 0..) |Class, i| {
global_classes[i] = Class.get().*;
}
vm.global = ZigGlobalObject.create(
&global_classes,
@intCast(i32, global_classes.len),
vm.console,
@intCast(i32, worker.execution_context_id),
worker.mini,
);
vm.regular_event_loop.global = vm.global;
vm.regular_event_loop.virtual_machine = vm;
@@ -1753,7 +1867,7 @@ pub const VirtualMachine = struct {
return promise;
}
pub fn loadEntryPoint(this: *VirtualMachine, entry_path: string) !*JSInternalPromise {
pub fn loadEntryPoint(this: *VirtualMachine, entry_path: string) anyerror!*JSInternalPromise {
var promise = try this.reloadEntryPoint(entry_path);
// pending_internal_promise can change if hot module reloading is enabled