aboutsummaryrefslogtreecommitdiff
path: root/src/install/install.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/install/install.zig')
-rw-r--r--src/install/install.zig241
1 files changed, 86 insertions, 155 deletions
diff --git a/src/install/install.zig b/src/install/install.zig
index 486613023..ab3bddb59 100644
--- a/src/install/install.zig
+++ b/src/install/install.zig
@@ -3138,15 +3138,13 @@ const PackageInstall = struct {
// Slower than clonefile
clonefile_each_dir,
- // Slow!
+ // On macOS, slow.
+ // On Linux, fast.
hardlink,
// Slowest if single-threaded
copyfile,
- copy_file_range,
- io_uring,
-
const BackendSupport = std.EnumArray(Method, bool);
pub const macOS = BackendSupport.initDefault(false, .{
@@ -3159,7 +3157,7 @@ const PackageInstall = struct {
pub const linux = BackendSupport.initDefault(false, .{
.io_uring = true,
.hardlink = true,
- .copy_file_range = true,
+ .copyfile = true,
});
pub inline fn isSupported(this: Method) bool {
@@ -3168,13 +3166,6 @@ const PackageInstall = struct {
return false;
}
-
- pub inline fn isSync(this: Method) bool {
- return switch (this) {
- .hardlink, .clonefile_each_dir, .clonefile => true,
- else => false,
- };
- }
};
pub fn verify(
@@ -3538,9 +3529,9 @@ const PackageInstall = struct {
// we'll catch it the next error
if (!skip_delete) this.destination_dir.deleteTree(std.mem.span(this.destination_dir_subpath)) catch {};
- if (comptime Environment.isMac) {
- switch (supported_method) {
- .clonefile => {
+ switch (supported_method) {
+ .clonefile => {
+ if (comptime Environment.isMac) {
// First, attempt to use clonefile
// if that fails due to ENOTSUP, mark it as unsupported and then fall back to copyfile
if (this.installWithClonefile()) |result| {
@@ -3558,8 +3549,10 @@ const PackageInstall = struct {
},
}
}
- },
- .clonefile_each_dir => {
+ }
+ },
+ .clonefile_each_dir => {
+ if (comptime Environment.isMac) {
if (this.installWithClonefileEachDir()) |result| {
return result;
} else |err| {
@@ -3575,36 +3568,34 @@ const PackageInstall = struct {
},
}
}
- },
- .hardlink => {
- if (this.installWithHardlink()) |result| {
- return result;
- } else |err| {
- switch (err) {
- error.NotSupported => {
- supported_method = .copyfile;
- },
- error.FileNotFound => return Result{
- .fail = .{ .err = error.FileNotFound, .step = .opening_cache_dir },
- },
- else => return Result{
- .fail = .{ .err = err, .step = .copying_files },
- },
- }
+ }
+ },
+ .hardlink => {
+ if (this.installWithHardlink()) |result| {
+ return result;
+ } else |err| {
+ switch (err) {
+ error.NotSupported => {
+ supported_method = .copyfile;
+ },
+ error.FileNotFound => return Result{
+ .fail = .{ .err = error.FileNotFound, .step = .opening_cache_dir },
+ },
+ else => return Result{
+ .fail = .{ .err = err, .step = .copying_files },
+ },
}
- },
- else => {},
- }
-
- if (supported_method != .copyfile) return Result{
- .success = void{},
- };
-
- return this.installWithCopyfile();
+ }
+ },
+ else => {},
}
- // TODO: linux io_uring
+ if (supported_method != .copyfile) return Result{
+ .success = void{},
+ };
+ // TODO: linux io_uring
+ return this.installWithCopyfile();
}
};
@@ -5922,6 +5913,7 @@ pub const PackageManager = struct {
node_modules_folder: std.fs.Dir,
};
+ /// Install versions of a package which are waiting on a network request
pub fn installEnqueuedPackages(
this: *PackageInstaller,
package_id: PackageID,
@@ -6197,125 +6189,64 @@ pub const PackageManager = struct {
.successfully_installed = try std.DynamicBitSetUnmanaged.initEmpty(lockfile.packages.len, this.allocator),
};
- // When it's a Good Idea, run the install in single-threaded
- // From benchmarking, apfs clonefile() is ~6x faster than copyfile() on macOS
- // Running it in parallel is the same or slower.
- // However, copyfile() is about 30% faster if run in paralell
- // On Linux, the story here will be similar but with io_uring.
- // We will have to support versions of Linux that do not have io_uring support
- // so in that case, we will still need to support copy_file_range()
- // git installs will always need to run in paralell, and tarball installs probably should too
- run_install: {
- if (PackageInstall.supported_method.isSync()) {
- sync_install: {
- const cwd = std.fs.cwd();
- while (iterator.nextNodeModulesFolder()) |node_modules| {
- try cwd.makePath(std.mem.span(node_modules.relative_path));
- // We deliberately do not close this folder.
- // If the package hasn't been downloaded, we will need to install it later
- // We use this file descriptor to know where to put it.
- var folder = try cwd.openDirZ(node_modules.relative_path, .{
- .iterate = true,
- });
-
- installer.node_modules_folder = folder;
-
- for (node_modules.packages) |package_id| {
- installer.installPackage(@truncate(PackageID, package_id), log_level);
- if (!PackageInstall.supported_method.isSync()) break :sync_install;
- if (this.pending_tasks > 16) {
- try this.runTasks(
- *PackageInstaller,
- &installer,
- PackageInstaller.installEnqueuedPackages,
- log_level,
- );
- }
- }
+ const cwd = std.fs.cwd();
+ while (iterator.nextNodeModulesFolder()) |node_modules| {
+ try cwd.makePath(std.mem.span(node_modules.relative_path));
+ // We deliberately do not close this folder.
+ // If the package hasn't been downloaded, we will need to install it later
+ // We use this file descriptor to know where to put it.
+ var folder = try cwd.openDirZ(node_modules.relative_path, .{
+ .iterate = true,
+ });
- try this.runTasks(
- *PackageInstaller,
- &installer,
- PackageInstaller.installEnqueuedPackages,
- log_level,
- );
- }
+ installer.node_modules_folder = folder;
- while (this.pending_tasks > 0) {
- try this.runTasks(
- *PackageInstaller,
- &installer,
- PackageInstaller.installEnqueuedPackages,
- log_level,
- );
- }
- break :run_install;
+ var remaining = node_modules.packages;
+
+ // cache line is 64 bytes on ARM64 and x64
+ // PackageIDs are 4 bytes
+ // Hence, we can fit up to 64 / 4 = 16 package IDs in a cache line
+ const unroll_count = comptime 64 / @sizeOf(PackageID);
+
+ while (remaining.len > unroll_count) {
+ comptime var i: usize = 0;
+ inline while (i < unroll_count) : (i += 1) {
+ installer.installPackage(remaining[i], comptime log_level);
+ }
+ remaining = remaining[unroll_count..];
+
+ // We want to minimize how often we call this function
+ // That's part of why we unroll this loop
+ if (this.pending_tasks > 0) {
+ try this.runTasks(
+ *PackageInstaller,
+ &installer,
+ PackageInstaller.installEnqueuedPackages,
+ log_level,
+ );
}
}
- // var install_context = try lockfile.allocator.create(PackageInstall.Context);
- // install_context.* = .{
- // .cache_dir = cache_dir,
- // .progress = progress,
- // .metas = metas,
- // .names = names,
- // .resolutions = resolutions,
- // .string_buf = lockfile.buffers.string_bytes.items,
- // .allocator = lockfile.allocator,
- // };
- // install_context.channel = PackageInstall.Task.Channel.init();
- // var ran: usize = summary.skipped + summary.success + summary.fail;
-
- // var tasks = try lockfile.allocator.alloc(PackageInstall.Task, toplevel_count - ran);
- // var task_i: usize = 0;
- // var batch = ThreadPool.Batch{};
- // var remaining_count = task_i;
- // while (toplevel_node_modules.next()) |package_id| {
- // const meta = &metas[package_id];
- // if (meta.isDisabled()) {
- // if (comptime log_level.showProgress()) {
- // install_node.completeOne();
- // install_node.setEstimatedTotalItems(installer.install_count);
- // }
- // continue;
- // }
-
- // tasks[task_i] = PackageInstall.Task{
- // .package_id = @truncate(PackageID, package_id),
- // .destination_dir = node_modules_folder,
- // .ctx = install_context,
- // };
- // batch.push(ThreadPool.Batch.from(&tasks[task_i].task));
- // task_i += 1;
- // }
+ for (remaining) |package_id| {
+ installer.installPackage(@truncate(PackageID, package_id), log_level);
+ }
- // this.thread_pool.schedule(batch);
-
- // while (remaining_count > 0) {
- // while (install_context.channel.tryReadItem() catch null) |item_| {
- // var install_task: *PackageInstall.Task = item_;
- // defer remaining_count -= 1;
- // switch (install_task.result) {
- // .pending => unreachable,
- // .skip => summary.skipped += 1,
- // .success => summary.success += 1,
- // .fail => |cause| {
- // Output.prettyErrorln(
- // "<r><red>error<r>: <b><red>{s}<r> installing <b>{s}<r>",
- // .{ @errorName(cause.err), install_task.ctx.names[install_task.package_id] },
- // );
- // summary.fail += 1;
- // },
- // }
- // }
- // if (comptime log_level.showProgress()) {
- // install_node.completeOne();
- // install_node.setEstimatedTotalItems(installer.install_count);
- // }
-
- // std.atomic.spinLoopHint();
+ try this.runTasks(
+ *PackageInstaller,
+ &installer,
+ PackageInstaller.installEnqueuedPackages,
+ log_level,
+ );
+ }
+
+ while (this.pending_tasks > 0) {
+ try this.runTasks(
+ *PackageInstaller,
+ &installer,
+ PackageInstaller.installEnqueuedPackages,
+ log_level,
+ );
}
- // }
summary.successfully_installed = installer.successfully_installed;
outer: for (installer.platform_binlinks.items) |deferred| {