Compare commits

...

2 Commits

Author SHA1 Message Date
Jarred Sumner
b084199a26 Update module_loader.zig 2024-06-07 19:22:16 -07:00
Jarred Sumner
ffdb9211b4 Support cancelling transpilations 2024-06-07 03:09:47 -07:00
6 changed files with 93 additions and 9 deletions

View File

@@ -4,6 +4,8 @@ const JSC = bun.JSC;
pub const WeakRefType = enum(u32) {
None = 0,
FetchResponse = 1,
TranspilerJob = 5,
};
const WeakImpl = opaque {
pub fn init(globalThis: *JSC.JSGlobalObject, value: JSC.JSValue, refType: WeakRefType, ctx: ?*anyopaque) *WeakImpl {

View File

@@ -10,12 +10,18 @@ namespace Bun {
enum class WeakRefType : uint32_t {
None = 0,
FetchResponse = 1,
TranspilerJob = 5,
};
typedef void (*WeakRefFinalizeFn)(void* context);
// clang-format off
#define FOR_EACH_WEAK_REF_TYPE(macro) \
macro(FetchResponse)
macro(FetchResponse) \
macro(TranspilerJob)
// clang-format on
#define DECLARE_WEAK_REF_OWNER(X) \
extern "C" void Bun__##X##_finalize(void* context);
@@ -32,6 +38,9 @@ public:
case WeakRefType::FetchResponse:
Bun__FetchResponse_finalize(context);
break;
case WeakRefType::TranspilerJob:
Bun__TranspilerJob_finalize(context);
break;
default:
break;
}
@@ -52,6 +61,9 @@ static JSC::WeakHandleOwner* getWeakRefOwner(WeakRefType type)
case WeakRefType::FetchResponse: {
return getWeakRefOwner<WeakRefType::FetchResponse>();
}
case WeakRefType::TranspilerJob: {
return getWeakRefOwner<WeakRefType::TranspilerJob>();
}
default: {
RELEASE_ASSERT_NOT_REACHED();
}
@@ -74,9 +86,7 @@ public:
this->m_cell = JSC::Weak<JSC::JSObject>(object, getWeakRefOwner(kind), ctx);
}
WeakRef()
{
}
WeakRef() = default;
JSC::Weak<JSC::JSObject> m_cell;
};

View File

@@ -283,11 +283,16 @@ pub const SavedSourceMap = struct {
entry.value_ptr.* = value.ptr();
}
pub fn getWithContent(
/// The mutex must be locked while this function is called.
/// The SourceMapping pointer must not be freed on another thread while it is in use.
fn getWithContent(
this: *SavedSourceMap,
path: string,
hint: SourceMap.ParseUrlResultHint,
) SourceMap.ParseUrl {
if (comptime Environment.allow_assert)
this.mutex.assertLocked("SavedSourceMap.getWithContent must be called with the mutex locked");
const hash = bun.hash(path);
const mapping = this.map.getEntry(hash) orelse return .{};
switch (Value.from(mapping.value_ptr.*).tag()) {
@@ -332,6 +337,14 @@ pub const SavedSourceMap = struct {
}
}
pub fn lock(this: *SavedSourceMap) void {
this.mutex.lock();
}
pub fn unlock(this: *SavedSourceMap) void {
this.mutex.unlock();
}
pub fn get(this: *SavedSourceMap, path: string) ?*ParsedSourceMap {
return this.getWithContent(path, .mappings_only).map;
}

View File

@@ -204,6 +204,8 @@ fn dumpSourceStringFailiable(vm: *VirtualMachine, specifier: string, written: []
Output.debugWarn("Failed to dump source string: writeFile {}", .{e});
return;
};
vm.source_mappings.lock();
defer vm.source_mappings.unlock();
if (vm.source_mappings.get(specifier)) |mappings| {
const map_path = std.mem.concat(bun.default_allocator, u8, &.{ std.fs.path.basename(specifier), ".map" }) catch bun.outOfMemory();
defer bun.default_allocator.free(map_path);
@@ -287,6 +289,8 @@ pub const RuntimeTranspilerStore = struct {
// immediately after this is called, the microtasks will be drained again.
}
const WeakPromise = JSC.Weak(TranspilerJob);
pub fn transpile(
this: *RuntimeTranspilerStore,
vm: *JSC.VirtualMachine,
@@ -304,7 +308,7 @@ pub const RuntimeTranspilerStore = struct {
.vm = vm,
.log = logger.Log.init(bun.default_allocator),
.loader = vm.bundler.options.loader(owned_path.name.ext),
.promise = JSC.Strong.create(JSC.JSValue.fromCell(promise), globalObject),
.promise = WeakPromise.create(promise.asValue(), globalObject, .TranspilerJob, job),
.poll_ref = .{},
.fetcher = TranspilerJob.Fetcher{
.file = {},
@@ -320,7 +324,7 @@ pub const RuntimeTranspilerStore = struct {
path: Fs.Path,
referrer: []const u8,
loader: options.Loader,
promise: JSC.Strong = .{},
promise: WeakPromise = .{},
vm: *JSC.VirtualMachine,
globalThis: *JSC.JSGlobalObject,
fetcher: Fetcher,
@@ -331,6 +335,7 @@ pub const RuntimeTranspilerStore = struct {
resolved_source: ResolvedSource = ResolvedSource{},
work_task: JSC.WorkPoolTask = .{ .callback = runFromWorkerThread },
next: ?*TranspilerJob = null,
cancelled: std.atomic.Value(bool) = std.atomic.Value(bool).init(false),
pub const Store = bun.HiveArray(TranspilerJob, 64).Fallback;
@@ -366,12 +371,47 @@ pub const RuntimeTranspilerStore = struct {
this.vm.eventLoop().enqueueTaskConcurrent(JSC.ConcurrentTask.createFrom(&this.vm.transpiler_store));
}
export fn Bun__TranspilerJob_finalize(this: *TranspilerJob) callconv(.C) void {
debug("TranspilerJob finalize", .{});
this.cancelled.store(true, .Release);
this.promise.deinit();
}
comptime {
_ = &Bun__TranspilerJob_finalize;
}
fn didCancel(this: *TranspilerJob, vm: *VirtualMachine) void {
this.resolved_source.source_code_needs_deref = false;
this.resolved_source.source_code.deref();
this.resolved_source.source_url.deref();
this.resolved_source.source_url = bun.String.empty;
this.resolved_source.specifier.deref();
this.resolved_source.specifier = bun.String.empty;
this.promise.deinit();
this.deinit();
_ = vm.transpiler_store.store.hive.put(this);
}
pub fn isCancelled(this: *TranspilerJob) bool {
return this.cancelled.load(.Acquire);
}
pub fn runFromJSThread(this: *TranspilerJob) void {
var vm = this.vm;
const promise = this.promise.swap();
const globalThis = this.globalThis;
this.poll_ref.unref(vm);
if (promise == .zero) {
debug("Promise already freed", .{});
this.didCancel(vm);
return;
}
bun.debugAssert(!this.isCancelled());
const referrer = bun.String.createUTF8(this.referrer);
var log = this.log;
this.log = logger.Log.init(bun.default_allocator);
@@ -426,13 +466,17 @@ pub const RuntimeTranspilerStore = struct {
var arena = bun.ArenaAllocator.init(bun.default_allocator);
defer arena.deinit();
const allocator = arena.allocator();
defer this.dispatchToMainThread();
if (this.generation_number != this.vm.transpiler_store.generation_number.load(.Monotonic)) {
this.parse_error = error.TranspilerJobGenerationMismatch;
return;
}
if (this.isCancelled()) {
debug("TranspilerJob cancelled", .{});
return;
}
if (ast_memory_store == null) {
ast_memory_store = bun.default_allocator.create(js_ast.ASTMemoryAllocator) catch bun.outOfMemory();
ast_memory_store.?.* = js_ast.ASTMemoryAllocator{
@@ -668,6 +712,11 @@ pub const RuntimeTranspilerStore = struct {
}
}
if (this.isCancelled()) {
debug("TranspilerJob cancelled (after parse)", .{});
return;
}
if (source_code_printer == null) {
const writer = try js_printer.BufferWriter.init(bun.default_allocator);
source_code_printer = bun.default_allocator.create(js_printer.BufferPrinter) catch unreachable;

View File

@@ -122,6 +122,12 @@ pub const Lock = struct {
@panic(message);
}
}
pub inline fn assertLocked(this: *Lock, comptime message: []const u8) void {
if (this.mutex.state.load(.Monotonic) == 0) {
@panic(message);
}
}
};
pub fn spinCycle() void {}

View File

@@ -369,7 +369,6 @@ pub const ByteRangeMapping = struct {
var executable_lines: Bitset = Bitset{};
var lines_which_have_executed: Bitset = Bitset{};
const parsed_mappings_ = bun.JSC.VirtualMachine.get().source_mappings.get(source_url.slice());
var functions = std.ArrayListUnmanaged(CodeCoverageReport.Block){};
try functions.ensureTotalCapacityPrecise(allocator, function_blocks.len);
@@ -387,6 +386,11 @@ pub const ByteRangeMapping = struct {
errdefer lines_which_have_executed.deinit(allocator);
var line_count: u32 = 0;
var vm = bun.JSC.VirtualMachine.get();
vm.source_mappings.lock();
defer vm.source_mappings.unlock();
const parsed_mappings_ = vm.source_mappings.get(source_url.slice());
if (ignore_sourcemap or parsed_mappings_ == null) {
line_count = @truncate(line_starts.len);
executable_lines = try Bitset.initEmpty(allocator, line_count);