diff options
author | 2023-04-28 17:46:43 -0700 | |
---|---|---|
committer | 2023-04-28 17:46:43 -0700 | |
commit | 994c715700555fab33e1d73b7ac3d2941e674559 (patch) | |
tree | 0ccc7064363345d631f7f9c263b226b8f825242a | |
parent | e3a0c4e06d4fb4f0c4d03f85bae45266415d4204 (diff) | |
download | bun-994c715700555fab33e1d73b7ac3d2941e674559.tar.gz bun-994c715700555fab33e1d73b7ac3d2941e674559.tar.zst bun-994c715700555fab33e1d73b7ac3d2941e674559.zip |
Fix race condition
-rw-r--r-- | .github/scripts/test-runner.ts | 3 | ||||
-rw-r--r-- | src/bundler/bundle_v2.zig | 49 |
2 files changed, 50 insertions, 2 deletions
diff --git a/.github/scripts/test-runner.ts b/.github/scripts/test-runner.ts index 1aaef2b31..a21cc818d 100644 --- a/.github/scripts/test-runner.ts +++ b/.github/scripts/test-runner.ts @@ -1,3 +1,6 @@ +// Dear reader +// +// The existence of this file is temporary, a hack. import { join, basename } from "node:path"; import { readdirSync, writeSync, fsyncSync, appendFileSync } from "node:fs"; import { spawn, spawnSync } from "node:child_process"; diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index a67ec7c2a..f778f160b 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -34,6 +34,13 @@ // data structures related to `BSSMap`. This still leaks memory, but not very // much since it only allocates the first time around. // +// +// In development, it is strongly recommended to use either a debug build of +// mimalloc or Valgrind to help catch memory issues +// To use a debug build of mimalloc: +// +// make mimalloc-debug +// const Bundler = bun.Bundler; const bun = @import("root").bun; const from = bun.from; @@ -305,6 +312,9 @@ pub const BundleV2 = struct { plugins: ?*JSC.API.JSBundler.Plugin = null, completion: ?*JSBundleCompletionTask = null, + // There is a race condition where an onResolve plugin may schedule a task on the bundle thread before it's parsing task completes + resolve_tasks_waiting_for_import_source_index: std.AutoArrayHashMapUnmanaged(Index.Int, BabyList(struct { to_source_index: Index, import_record_index: u32 })) = .{}, + /// Allocations not tracked by a threadlocal heap free_list: std.ArrayList(string) = std.ArrayList(string).init(bun.default_allocator), @@ -1037,6 +1047,11 @@ pub const BundleV2 = struct { ) void { debug("onLoad: ({d}, {s})", .{ load.source_index.get(), @tagName(load.value) }); defer load.deinit(); + defer { + if (comptime FeatureFlags.help_catch_memory_issues) { + this.graph.heap.gc(true); + } + } switch (load.value.consume()) { .no_match => { @@ -1089,6 +1104,12 @@ pub const BundleV2 = struct { defer this.graph.resolve_pending -= 1; debug("onResolve: ({s}:{s}, {s})", .{ resolve.import_record.namespace, resolve.import_record.specifier, @tagName(resolve.value) }); + defer { + if (comptime FeatureFlags.help_catch_memory_issues) { + this.graph.heap.gc(true); + } + } + switch (resolve.value.consume()) { .no_match => { // If it's a file namespace, we should run it through the resolver like normal. @@ -1191,8 +1212,23 @@ pub const BundleV2 = struct { if (out_source_index) |source_index| { if (resolve.import_record.importer_source_index) |importer| { - var import_record: *ImportRecord = &this.graph.ast.items(.import_records)[importer].slice()[resolve.import_record.import_record_index]; - import_record.source_index = source_index; + var source_import_records = &this.graph.ast.items(.import_records)[importer]; + if (source_import_records.len <= resolve.import_record.import_record_index) { + var entry = this.resolve_tasks_waiting_for_import_source_index.getOrPut(this.graph.allocator, importer) catch unreachable; + if (!entry.found_existing) { + entry.value_ptr.* = .{}; + } + entry.value_ptr.push( + this.graph.allocator, + .{ + .to_source_index = source_index, + .import_record_index = resolve.import_record.import_record_index, + }, + ) catch unreachable; + } else { + var import_record: *ImportRecord = &source_import_records.slice()[resolve.import_record.import_record_index]; + import_record.source_index = source_index; + } } } }, @@ -1592,6 +1628,15 @@ pub const BundleV2 = struct { } var import_records = result.ast.import_records.clone(this.graph.allocator) catch unreachable; + + if (this.resolve_tasks_waiting_for_import_source_index.fetchSwapRemove(result.source.index.get())) |pending_entry| { + for (pending_entry.value.slice()) |to_assign| { + import_records.slice()[to_assign.import_record_index].source_index = to_assign.to_source_index; + } + var list = pending_entry.value.list(); + list.deinit(this.graph.allocator); + } + for (import_records.slice(), 0..) |*record, i| { if (graph.path_to_source_index_map.get(wyhash(0, record.path.text))) |source_index| { record.source_index.value = source_index; |